Page 1
🛠️ 🔐 JWT Penetration Testing Checklist (Fully Detailed) 📘 1. 🔎 Basic Enumeration ✅ Test: Token Structure: Is it a JWT? Format: header.payload.signature Decode JWT (Base64): Header: algorithm used? Payload: roles, expiry, userID, admin flag? 📌 Tools: # Decode manually echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d Or use: https://jwt.io Burp Suite extension: JWT Editor, Hackvertor ⚠️ 2. 🔥 Algorithm-Based Attacks 🧨 a. alg: none attack 🚩 When vulnerable: JWT header: "alg": "none" Server doesn't verify signature 🧪 Test: Change header to: { "alg": "none", "typ": "JWT" } Remove the signature (or leave it empty). Modify payload (e.g., "admin": true) Base64 encode and send token: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJhZG1pbiI6dHJ1ZX0. ✅ If accepted: Critical vulnerability. 🧨 b. Algorithm Confusion (RS256 → HS256) 🚩 When vulnerable: Server expects asymmetric RS256 (public/private key), but allows symmetric HS256 (HMAC). 🧪 Test: Change alg to HS256 Use server's public key as the secret key to sign Resign the token with HMAC using public key ✅ If accepted: Server confused symmetric vs. asymmetric algorithms. 📦 Tools: jwt_tool.py → --exploit alg_none, --exploit alg_hs256 📆 3. 🔒 Signature Key Bruteforce 🧨 a. Weak secret / brute force 🚩 When vulnerable: HS256, HS384, HS512 used with a guessable shared secret. 🧪 Test: python3 jwt_tool.py <token> -d -S wordlist.txt Or use: jwtcrack.py Burp Suite Intruder + SecLists weak passwords list ✅ If cracked, attacker can forge arbitrary tokens. ⏳ 4. 🧪 Expiry / Time-Based Attacks ⚙️ a. Modify exp, nbf, iat fields Try to bypass expiry: { "exp": 9999999999 } Try pre-validating tokens: { "nbf": 0, "iat": 0 } ✅ If server does not verify these fields → logic flaw. 🧍 5. 👑 Privilege Escalation 🎯 Target fields like: isAdmin role userid permissions access_level 🧪 Test: Change payload: { "role": "admin", "userid": 1 } Resign (if possible) or use none/bruteforced key ✅ If access granted → vertical privilege escalation. 🔗 6. 🔄 Replay Attack 🧪 Test: Use captured JWT on another account/session See if same token grants access → token not scoped to user ✅ If works → JWT not bound to session/IP/device 🎣 7. 🪝 Injection in Payload 🧨 a. SQL Injection / NoSQL Injection JWT is used in database queries? Payload: { "username": {"$ne": null} } or { "userid": "1' OR '1'='1" } ✅ If DB logic accepts it → critical injection risk. 🪤 8. 🧪 IDOR + JWT 🎯 Use JWT to access other user’s data: { "userid": 1234 } → Try other user IDs ✅ If no access control enforced → Insecure Direct Object Reference. 🧩 9. 📦 JWT in Cookies / Storage 🧪 Test: Can you overwrite the cookie? Is the JWT in localStorage or sessionStorage? Vulnerable to XSS? Can you refresh expired tokens? (Look for refresh tokens) ✅ If client has full control → combine with XSS 🔁 10. Refresh Token Exploits 🧪 Test: Capture refresh token Try replaying Try CSRF on refresh endpoint Does it return new access token? ✅ If refresh token never expires / is not bound → long-term hijack possible. 💥 11. Advanced & Real-World Exploits 🚩 a. Key Disclosure via LFI If the private key is accessible via LFI → attacker can sign own tokens. 🚩 b. Kid Header Injection { "kid": "../../../../../../etc/passwd" } ✅ If used to load keys from disk, this can lead to path traversal + key loading. 🚩 c. JWT in Authorization header (CSRFable) Authorization: Bearer <token> ✅ If the token is used in a header without SameSite protection, you may be able to CSRF it from another origin. 🚩 d. JWK Injection If app supports jku or x5u in JWT header: { "jku": "https://attacker.com/mykey.json" } ✅ If accepted → attacker can inject their key + sign arbitrary tokens. 🧰 Tools & Wordlists 🔧 jwt_tool 🔧 jwt-cracker 🔧 JOSEPH 📂 Wordlists: SecLists (Passwords, JWT Secrets, etc.) 🧱 Mitigation Cheatsheet (for defenders) Issue Mitigation alg: none Always enforce algorithm server-side Weak secret Use long, random keys (256+ bits) Exp tampering Enforce expiration strictly Forged roles Validate roles on backend Token reuse Use short-lived tokens + refresh flow Key confusion (HS256/RS256) Don’t allow multiple algs Kid/path injections Use static key loading, not dynamic
Last updated