cybersecurity · beginner · ~15 min · safe pentest lab

Validate CSRF Token Format

Practice deny-by-default input validation: enforce an exact length and a strict character allowlist on an attacker-controlled string, bounds-checking each byte before use, so malformed CSRF tokens are rejected before they reach trusted logic.

Challenge

CSRF token format validation

A CSRF (Cross-Site Request Forgery) defense hands each session a random anti-CSRF token, and the server must reject any request whose token is malformed before it is ever compared against the session secret or used to key any lookup. If a handler blindly trusts attacker-controlled token strings, an attacker can smuggle in oversized, empty, or injection-shaped values that slip past sloppy checks.

In this lab the asset is the fixed set of candidate token strings baked into the test harness. The insecure assumption you are fixing is "any non-empty string is a token": that lets "", a 33-character overflow, uppercase, whitespace, or <script> payloads through the gate.

Task

Implement valid_csrf_format(const char *tok) so it returns 1 iff tok is exactly 32 characters long and every character is lowercase hex (0-9 or a-f), and 0 otherwise. NULL must return 0. Deny by default and never read past the token: stop at the first '\0'.

Edge cases to handle

  • NULL input.
  • Empty string and tokens shorter than 32 characters.
  • Tokens longer than 32 characters (must be rejected, not truncated).
  • Uppercase hex, g-z, spaces, and injection payloads.

Input format

A single argument tok: a NUL-terminated C string, or NULL.

Output format

Return int 1 if tok is exactly 32 lowercase-hex characters, otherwise 0.

Constraints

Do not read past the terminating NUL. Treat NULL as invalid (return 0). Only characters 0-9 and a-f are accepted; length must be exactly 32. No allocation, no I/O.

Starter code

int valid_csrf_format(const char *tok){
    /* TODO: validate that tok is exactly 32 lowercase-hex chars.
       Insecure stub: trusts any non-NULL token. Replace me. */
    (void)tok;
    return 1;
}

Common mistakes

Using strlen without a NULL check (crashes on NULL). Accepting length >= 32 instead of exactly 32, so a 40-char token passes. Forgetting to verify the 33rd byte is the terminator, so any long string with a valid 32-char prefix is accepted. Allowing uppercase hex via isxdigit() when the spec demands lowercase. Returning early with 1 before scanning all characters.

Edge cases to handle

NULL pointer returns 0. Empty string returns 0. A 31-char token returns 0 (too short) and a 33-char token returns 0 (too long) — length must be exact, not "at least 32". Uppercase A-F is rejected because the spec requires lowercase. Any non-hex byte (g-z, punctuation, space, control chars) rejects the whole token. All-zero and all-f 32-char tokens are valid boundary accepts.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.