cybersecurity · intermediate · ~15 min · safe pentest lab

Constant-Time CSRF Token Comparison

Understand why secret-comparison must be constant-time and be able to implement a branchless, all-bytes byte comparison that eliminates a timing side channel, while still bounds-checking length and rejecting NULL by default.

Challenge

Constant-Time CSRF Token Check

A CSRF defense stores an expected anti-forgery token server-side and compares it against the token the browser submits with each state-changing request. If that comparison uses a normal strcmp/memcmp or bails out on the first mismatching byte, an attacker who can measure response time can recover the secret token one byte at a time: guesses that match a longer prefix take measurably longer to reject. Recovering the token lets the attacker forge requests and defeat CSRF protection entirely.

Asset: the secret expected CSRF token (a fixed n-byte buffer baked into the harness). Threat: a timing side channel that leaks how many leading bytes of a guess were correct. Insecure assumption: "it's fine to return early once the bytes differ."

Your task: implement ct_token_eq(a, b, n) that compares all n bytes of the two tokens in constant time — it must inspect every byte regardless of where (or whether) they differ, so the running time reveals nothing about the contents. Return 1 if the two n-byte tokens are equal, else 0.

Deny by default: if either pointer is NULL, reject (return 0). Never short-circuit on a mismatch, and never stop early at an embedded NUL — these are raw n-byte tokens, not C strings. An n of 0 means "nothing to compare", which is trivially equal (1).

Example

Given expected and a copy match of the same n bytes, and forged differing in one byte:

ct_token_eq(expected, match, n)  -> 1
ct_token_eq(expected, forged, n) -> 0

Input format

The function receives two token buffers a and b (each at least n bytes, or NULL) and a byte count n (size_t). Buffers are raw bytes and may contain any value including 0x00.

Output format

Return int 1 if the first n bytes of a and b are byte-for-byte equal, otherwise 0. Return 0 if either pointer is NULL.

Constraints

a and b are token buffers of length n bytes.

Starter code

int ct_token_eq(const char *a, const char *b, size_t n){
    /* TODO: compare ALL n bytes of a and b in constant time.
       Reject NULL by default; never return early on a mismatch.
       Return 1 if equal, else 0. This insecure stub does neither. */
    (void)a; (void)b; (void)n;
    return -1;
}

Common mistakes

Returning early on mismatch, introducing timing side-channels.

Edge cases to handle

a == NULL, b == NULL, tokens identical, tokens differing in first byte.

Background lessons

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