cybersecurity · beginner · ~15 min · safe pentest lab
Enforce a strict allowlist (deny-by-default) charset check on attacker-controlled bytes, using explicit length rather than NUL-termination, so an API gateway rejects malformed JWT segments before any decode step.
A JWT arrives as three dot-separated segments (header.payload.signature). Each segment MUST be base64url encoded, meaning every byte comes from the charset [A-Za-z0-9_-] — note the URL-safe - and _ instead of + and /, and no = padding.
An API gateway that decodes a segment without first validating its charset is trusting attacker-controlled bytes. Sneaking in +, /, =, a . delimiter, an embedded \0, or high-bit bytes can confuse a permissive decoder, smuggle data past a WAF, or trigger parser differentials between your gateway and your auth service.
Implement a strict validator that operates only on a fixed in-harness buffer:
int is_base64url(const char *seg, size_t n);
Return 1 iff every one of the n bytes of seg is in [A-Za-z0-9_-], otherwise 0. Deny by default: n == 0 (empty segment) returns 0, and a NULL pointer returns 0.
The check must be exact — inspect all n bytes, never stop at a \0, and reject anything outside the allowed set.
Given header = "eyJhbGciOiJIUzI1NiJ9" (a real base64url header, 20 bytes):
is_base64url(header, 20) -> 1is_base64url(header, 0) -> 0A byte pointer seg and a length n (size_t). Bytes are raw and may include any value 0-255, including embedded NUL. Only the first n bytes are in scope.
int: 1 if all n bytes are base64url characters [A-Za-z0-9_-]; otherwise 0.
C11, freestanding logic (no I/O, no allocation). Treat bytes as unsigned when range-checking. Do not read past index n-1. Do not stop early at a NUL byte. n can be 0. seg can be NULL.
int is_base64url(const char *seg, size_t n){
/* TODO: verify every one of the n bytes of seg is in the
base64url charset [A-Za-z0-9_-]. Reject '+','/','=', '.',
embedded NUL, and high-bit bytes. Deny by default:
n==0 -> 0, NULL -> 0. Treat bytes as UNSIGNED.
This insecure stub trusts the input and always allows it. */
(void)seg; (void)n;
return 1;
}Using strlen or stopping at the first '\0' instead of honoring n; forgetting to reject '+' and '/' (accepting standard base64 instead of base64url); allowing '=' padding; comparing chars as signed so bytes >=0x80 sneak through as negative; forgetting the n==0 and NULL deny-by-default cases; using isalnum() which is locale-dependent and does not include '-' or '_'.
n==0 returns 0; NULL pointer returns 0; embedded '\0' inside the n bytes must be rejected (not treated as end-of-string); '+', '/', and '=' from standard base64 must be rejected; the '.' JWT delimiter must be rejected; high-bit / non-ASCII bytes (>=0x80) must be rejected; a single valid char returns 1.
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.