JSON Web Tokens (JWTs)

A JSON Web Token (JWT) is a compact, URL-safe token format for passing signed claims between parties, defined by RFC 7519. Format:

$HEADER.$PAYLOAD.$SIGNATURE

Each segment is base64url-encoded. JWTs are commonly passed as a cookie, an HTTP Authorization: Bearer header, or pulled from browser local storage.

  • $HEADER is a JSON blob like { "alg": "RS256", "typ": "JWT" }, where alg names the signing algorithm the server expects.
  • $PAYLOAD is a JSON blob of claims — user identity, roles, expiry (exp), issuer (iss), etc. See jwt.io for a detailed breakdown of registered claims.
  • $SIGNATURE is binary data: the signature of the string $HEADER.$PAYLOAD (both already base64url-encoded) using alg and a server-side secret — often an RSA/EC private key, but sometimes just a symmetric string.

Use basenc --base64url / basenc -d --base64url (rather than plain base64) to encode/decode URL-safe base64 on the command line, and strip the trailing = padding.

Attack: the “none” algorithm

The JWT spec includes a none algorithm intended for cases where integrity is provided by other means. If a server accepts alg: "none", the signature segment is empty and the token is just $HEADER.$PAYLOAD. (note the trailing dot). An attacker can then edit the $PAYLOAD arbitrarily — escalate privileges, impersonate other users — and the server will accept the forged token.

The base64url encoding of {"typ":"JWT","alg":"none"} is eyJ0eX...lIn0.

Mitigation: explicitly allowlist acceptable algorithms server-side and never accept none in production.

Attack: RS256 → HS256 confusion (public key as HMAC secret)

If a server uses RS256 (asymmetric) but doesn’t strictly pin the algorithm, an attacker can flip alg to HS256 (symmetric HMAC). The server then verifies the signature using the public key as the HMAC secret — and public keys are, by definition, public. If the public half of the keypair is available (for example, re-used as the server’s TLS certificate), harvest it and forge valid JWTs at will.

The base64url encoding of {"typ":"JWT","alg":"HS256"} is eyJ0eX...J9Cg.

Sign $HEADER.$PAYLOAD with the PEM public key using:

echo -n "$HEADER.$PAYLOAD" | \
openssl dgst -sha256 -mac HMAC -macopt hexkey:$(cat $PUBLIC_KEY_FILE | xxd -p | tr -d '\n') | \
sed -e 's/.*= //' | \
tr -d '\n' | \
xxd -p -r | \
basenc --base64url | \
sed -e 's/=*$//'

Mitigation: pin a single algorithm per key, and never use the same key for both asymmetric signing and symmetric verification.

Attack: brute-forcing weak HMAC secrets

When alg is HS256 and the signing secret is a simple string (not a high-entropy key), it can be brute-forced offline: take a captured token, compute HMAC-SHA256 of $HEADER.$PAYLOAD with candidate secrets, and compare to the signature. Tools like JWT-Cracker or hashcat mode 16500 automate this. Any secret short enough to appear in a wordlist is fair game.

Mitigation: use ≥256-bit random secrets, and prefer asymmetric algorithms (RS256/ES256) so the signing key never has to be distributed.

Defenses (summary)

  • Pin accepted algorithms server-side; reject none and any algorithm you didn’t explicitly configure.
  • Use strong, high-entropy secrets for HMAC; prefer asymmetric signing when keys cross trust boundaries.
  • Validate exp, nbf, iss, and aud claims on every token.
  • Don’t store sensitive data in the payload — it’s base64, not encrypted.

Sources

Related: burp-suite, xss-attacks, http