cybersecurity · intermediate · ~15 min · safe pentest lab
Practice deny-by-default parsing of attacker-controlled token structure: validate the exact separator count and reject empty segments before trusting any part of the input, and understand that structural parsing is distinct from cryptographic verification.
A JSON Web Token (JWT) presented to an API is untrusted, attacker-controlled data until it is validated. Its serialized form is header.payload.signature — three Base64URL segments joined by exactly two dots. Before an API can even think about verifying the token, it must first parse this structure safely: a malformed token (wrong number of dots, an empty segment) must be rejected outright, never coerced into a partial parse.
The asset is the API's authentication path. The threat is a crafted token string — extra dots, missing segments, or an empty string — probing for a parser that mishandles structure and slips a half-formed token deeper into the auth logic. The insecure assumption is "any string with dots in it is a token I can split." Deny by default instead: accept only a token with exactly two dots and no empty segment.
Implement:
int jwt_segments(const char *token, size_t *h_len, size_t *p_len, size_t *s_len);
Split token on exactly two dots. Write the byte length of each segment into *h_len, *p_len, *s_len. Return 0 on success, -1 if the token is malformed (not exactly two dots, or any empty segment) or any pointer is NULL. Read the input as a NUL-terminated C string; do not read past the terminator.
Important: this only PARSES structure. Splitting or decoding a JWT is NOT verifying its signature — a well-formed token is not a trusted one.
no_dot — "abcdef" has zero dots -> reject.three_dots — "a.b.c.d" has three dots -> reject.empty_segment — ".b.c", "a..c", "a.b." each have an empty segment -> reject."" -> reject.A NUL-terminated C string token (the serialized JWT candidate) plus three non-NULL size_t* out-params h_len, p_len, s_len. The token contains ASCII bytes and may hold zero, one, two, or more '.' separators.
Return int 0 on success and write each segment's byte length to *h_len, *p_len, *s_len. Return -1 on any malformed input (not exactly two dots, or an empty segment) or NULL pointer; out-params are left unspecified on failure.
C11, freestanding function (harness supplies all includes). Treat token as read-only, NUL-terminated input; never read past the terminator and never write through a NULL out-pointer. No dynamic allocation is required. Do not modify the token buffer.
int jwt_segments(const char *token, size_t *h_len, size_t *p_len, size_t *s_len)
{
/* TODO: split the JWT on exactly two dots and reject malformed tokens.
This insecure stub trusts the input and reports success unconditionally. */
(void)token; (void)h_len; (void)p_len; (void)s_len;
return 0;
}
Counting dots but forgetting to reject empty segments; using strtok/strchr in a way that silently skips empty fields; splitting on the FIRST two dots and ignoring trailing junk; writing out-params before confirming validity; not checking for NULL pointers; assuming a parseable token is a verified token.
Zero dots; one dot; three or more dots; empty leading/middle/trailing segment; the empty string; a valid minimal token like "a.b.c"; NULL token or NULL out-param. Trailing extra dots such as "a.b.c.." must be rejected because they create empty segments and exceed two separators.
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.