JWT Misconfiguration: Detection and Defense
JWT misconfiguration detection and defense — alg:none, RS256-to-HS256 confusion, and kid injection, with header-logging detection, Sigma rules, and MITRE mapping.
JWT misconfiguration almost always reduces to one mistake: the verifier lets the
token choose how it is checked. Three variants follow — alg:none acceptance,
RS256-to-HS256 algorithm confusion, and kid injection. The fix is a single rule:
the server pins the algorithm and key, and never reads them from the token. The
detection is equally tight: log the JWT header on every auth event and alert on
any algorithm your service does not issue.
JSON Web Tokens carry identity, so a forged token is an authentication bypass. This bug class has existed since Tim McLean’s 2015 disclosure of critical vulnerabilities in JWT libraries (CVE-2015-9235), yet new instances keep shipping.
What is a JWT misconfiguration?
A JWT misconfiguration is any verifier setup that lets an attacker influence how their own token is validated. Because a JSON Web Token is just identity the server trusts, a verifier that reads the algorithm or key from the attacker-supplied header can be tricked into accepting a forged token — an authentication bypass with no password, no MFA prompt, and no lockout.
This maps to MITRE ATT&CK T1550.001 — Use Alternate Authentication Material: Application Access Token and, for the resulting access, T1078 — Valid Accounts. The damage looks like legitimate use, which is exactly why header-level detection matters.
What are the JWT algorithm confusion attacks?
Every variant manipulates the token header to control verification, and each leaves a loggable fingerprint.
| Variant | What the attacker changes | Header fingerprint | Defense |
|---|---|---|---|
alg:none | Sets alg to none, drops the signature | "alg":"none" (any casing), empty 3rd segment | Reject none unconditionally |
| RS256 → HS256 | Switches alg, signs with the public key | HS256 token at an RS256-only service | Pin one algorithm family |
kid injection | Points key lookup at a file/query they control | Traversal/SQL chars in kid | Validate/whitelist kid |
In RS256→HS256 confusion specifically, the server verifies RSA tokens with its
public key (published at /.well-known/jwks.json by design), and the attacker
signs the forgery with that public key as the HMAC secret. A verifier that reads
the algorithm from the token validates it.
How to detect JWT attacks from auth telemetry
Your service knows which algorithm and key IDs it legitimately issues. Anything else in an inbound token header is an attack or a bug. Log the decoded JWT header on every auth event, and the variants light up. This is the same auth-event logging that surfaces broken access control.
Detect unexpected algorithms
If your service issues only RS256, an inbound HS256 or none token is, by
definition, not yours — the highest-fidelity JWT detection you can deploy.
title: Inbound JWT With Unexpected Algorithm
id: 6c1f3a92-darkpwn-illustrative
status: experimental
logsource:
product: application
service: auth
detection:
selection:
jwt_header_alg:
- 'none'
- 'None'
- 'NONE'
- 'nOnE'
- 'HS256' # for a service that only issues RS256
condition: selection
falsepositives:
- Services that legitimately accept symmetric algorithms (tune per service)
level: high Detect kid injection
A kid value containing path-traversal or SQL metacharacters is never legitimate
— it means the attacker is probing the key-lookup mechanism.
title: JWT kid Header Containing Injection Characters
id: 1e8d5b27-darkpwn-illustrative
status: experimental
logsource:
product: application
service: auth
detection:
selection:
jwt_header_kid|contains: ['../', '..\\', "'", ' or ', 'union select', '/dev/null']
condition: selection
falsepositives:
- Unusual but legitimate key-id naming schemes (audit before enforcing)
level: high How to test your JWT detection
Validate the rules against your own staging service:
- Mint a normal token and confirm it passes and generates an issuance event.
- Submit an
alg:nonetoken (any casing) and confirm both rejection and an alert. - Submit an HS256 token signed with the public key and confirm rejection.
- Submit a
kidwith../and confirm the injection rule fires.
Bake those four cases into CI so a future library-default change fails the build, not production.
How to prevent JWT misconfiguration
One control closes the whole class; the rest are depth.
- Do not mix symmetric and asymmetric families on one verifier — that is the precondition for algorithm confusion.
- Validate
iss,aud, andexpon every token; missing issuer validation has enabled cross-realm token acceptance. - Keep JWT libraries patched and pinned — confusion bugs are fixed by upgrades, and the advisories are not always loudly announced.
- Use short
expwindows plus revocation for high-value sessions to shrink a forged token’s lifetime.
Common JWT security mistakes
- Calling
verify(token, key)without pinning the algorithm. The library infers it from the token — the root cause of the whole class. - Accepting both HS* and RS* on one path. The precondition for confusion.
- Skipping claim validation. No
iss/aud/expcheck means stolen or cross-tenant tokens sail through. - Long-lived tokens with no revocation. A forged token is valid until it expires.
JWT security checklist
Pin this next to every verifier you own:
- Pin the expected algorithm explicitly at every verifier — never infer it from the token.
- Reject
alg:noneunconditionally, in any casing (lowercase before comparing). - Never accept both symmetric and asymmetric families on one verification path.
- Validate
iss,aud, andexpon every token. - Log the decoded JWT header (alg, kid) on every authentication event.
- Alert on any algorithm you do not issue, and on
kidvalues with traversal/injection chars. - Use short
expwindows plus revocation for high-value sessions. - Keep JWT libraries patched and pinned; track their CVEs (e.g. CVE-2022-21449).
- Add a CI test asserting
alg:noneand HS256-signed-with-public-key tokens are both rejected.
The takeaway
JWT security is one decision repeated everywhere: the server chooses the algorithm
and key, never the token. Pin it, reject none, log the header, and alert on any
algorithm you do not issue. Round out your session-security coverage with
SQL injection detection and
the wider Detection Engineering pillar.
Training & tools referenced
Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.
- TryHackMeAuthorized labs to practice JWT attack detection and hardeningSecurity TrainingStart training
Frequently asked questions
What is the alg:none JWT attack?
The attacker sets the token's header algorithm to none and removes the signature. A verifier that trusts the header skips signature checking and accepts the forged token. The fix is to reject none unconditionally, in any casing, and to pin the expected algorithm.
How does RS256 to HS256 confusion work?
The server signs with an RSA private key and verifies with the public key, which is published by design. The attacker switches the header to HS256 and signs a forged token using that public key as the HMAC secret. A verifier that reads the algorithm from the token validates the forgery. Pin one algorithm family per verifier to close it.
How do you detect a forged JWT?
Log the decoded JWT header on every auth event and alert on any algorithm your service does not issue, on none in any casing, and on kid values containing traversal or injection characters. Forged sessions also show up as authenticated actions with no matching token-issuance event.
How do you prevent JWT algorithm confusion?
Always pass the expected algorithm and key explicitly to the verifier, never let the library infer them from the token, and never configure one verifier to accept both symmetric and asymmetric algorithm families. Validate iss, aud, and exp on every token.