For the complete documentation index, see llms.txt. This page is also available as Markdown.

JWT Attacks

A practical guide for testing JSON Web Tokens (JWT)

Token Discovery

Find where tokens are used in the application:

  • Authorization: Bearer <token> headers

  • Cookie values (session, token, jwt, etc.)

  • URL parameters

  • Request bodies

Determine how the token is transmitted and capture a valid, signed JWT.


Token Inspection

Decode the token and inspect all three parts:

header.payload.signature

Check the header for:

  • alg — signing algorithm (HS256, RS256, none, etc.)

  • kid — key ID

  • jwk — embedded JSON Web Key

  • jku — JWK Set URL

  • x5c — X.509 certificate chain

  • cty — content type

Check the payload for sensitive claims:

  • sub, username, email

  • role, isAdmin

  • iat, exp, aud, iss

Confirm the signature exists and is non-empty.

The payload is only Base64url-encoded, not encrypted. Anyone can read it.


Signature Verification Bypass

Missing signature verification

  • Modify a claim such as sub, username, role, or isAdmin.

  • Leave the original signature unchanged.

  • Resend the request.

  • If the server accepts the modified token, the application likely uses decode() instead of verify().

Example in Python:

Algorithm none

  • Decode the header.

  • Change alg to none.

  • Modify the payload as desired.

  • Re-encode the header and payload.

  • Keep the trailing dot to preserve the header.payload. structure.

If the string none is blocked, try variants:

  • None, NONE, nOnE

  • Unexpected whitespace or encoding differences

Expected impact

  • Account takeover by changing user identifier claims

  • Privilege escalation by changing role or isAdmin

  • Session extension or replay by tampering with time claims


Weak Secret Recovery

Use this section when the token is signed with a symmetric algorithm (HS*).

Check whether the secret could be weak, default, or copied from documentation. Then run one of the following tools.

hashcat

Show previously cracked secrets without re-running:

flask-unsign

Useful for Flask sessions that are JWT-like:

If no wordlist works, assess whether a short secret can be brute-forced character by character.

After recovering the secret, re-sign a modified token with HS256 and confirm acceptance.


Header Injection

Embedded JWK (jwk)

Use when the token header contains a jwk parameter.

  • Generate a new RSA key pair.

  • Modify the payload, for example change role to administrator.

  • Sign the token with your private key.

  • Embed your public key in the jwk header.

  • Ensure the kid in the header matches the kid in the embedded JWK.

  • Send the forged token and verify acceptance.

Example in Python:

Burp JWT Editor can automate this via Attack → Embedded JWK.

External JWK Set (jku)

Use when the token header contains a jku parameter.

  • Host a JWK Set containing your public key on a server you control.

  • Modify the payload.

  • Sign the token with your private key.

  • Set jku to your hosted JWK Set URL.

  • Send the forged token.

  • If jku hosts are restricted, test for URL parsing discrepancies, open redirects, or SSRF-like bypasses.

Example JWK Set:

jku SSRF → Localhost Verification Bypass

The jku header can trigger SSRF to localhost-only endpoints. If the server performs side effects during key fetching (such as account verification), point jku to that endpoint.

Chain:

  • Generate an RSA key pair.

  • Forge a JWT with jku pointing to a localhost verification endpoint:

  • Sign with your private key.

  • Server validates JWT → fetches jku URL → triggers account verification → accepts token.

  • Verified account may unlock privileged endpoints.

Even if external jku URLs are blocked, localhost endpoints with side effects may still be reachable.

Key ID (kid) abuse

Use when the server selects keys based on kid.

  • Check whether kid is used as a filesystem path.

  • Test path traversal:

  • Try pointing kid to /dev/null and signing with an empty string.

  • Try predictable file contents such as ../../../proc/sys/kernel/hostname.

  • Test kid for SQL injection if it is used in a database lookup.

Other header parameters

  • Check for x5c and test self-signed certificate injection.

  • If signature verification is bypassable, test cty pivots:

    • text/xml → XXE

    • application/x-java-serialized-object → deserialization


Algorithm Confusion

Use this section when the token uses an asymmetric algorithm such as RS256.

  • Capture a legitimate JWT and confirm alg is RS256.

  • Obtain the server's public key from a JWK Set, certificate, or metadata endpoint.

  • Modify the payload as desired.

  • Change the header alg to HS256.

  • Sign the token using the server's public key as the HMAC secret.

  • Send the forged token.

  • If the server accepts it, algorithm confusion is present.

Example in Node.js:

Preconditions

  • The server reads the alg header from the token.

  • The same key material is used for both asymmetric and symmetric verification.

  • The public key is available to the attacker.

  • The verification library allows HMAC verification with an RSA public key.


Business Logic Attacks

JWT Balance Replay (MetaShop Pattern)

Use when the token contains mutable state such as a balance, points, or credits.

  • Sign up and capture the initial JWT with a high balance.

  • Spend the balance until it reaches zero.

  • Replace the current cookie with the saved JWT.

  • Return or refund items and observe whether the server recalculates the balance from the token without cross-checking history.

  • Repeat until the balance exceeds the target price.

The server trusts the balance in the JWT for return calculations but does not validate purchase history.


JWE Token Forgery

Use when the application uses JWE (JSON Web Encryption) instead of JWT. JWE tokens have five Base64url segments:

If the server's public RSA key is exposed (via /api/key, .well-known/jwks.json, or page source), you can encrypt arbitrary claims that the server will trust after decryption.

Example in Python:

  • Fetch the exposed public key.

  • Forge claims.

  • Encrypt with the public key.

  • Send the JWE token as cookie or header.

  • Confirm the server decrypts and accepts the forged claims.

JWE encryption does not equal authentication. If the server trusts any token it can decrypt, exposing the public key allows arbitrary claim forgery.


End-to-End Attack Flows

Account takeover via missing verification

  • Log in as a low-privilege user and capture the JWT.

  • Identify the user identifier claim (sub, username, etc.).

  • Change the claim to a target user.

  • Keep the original signature.

  • Send the token and confirm access as the target user.

Privilege escalation via alg: none

  • Capture a legitimate JWT.

  • Change alg to none.

  • Modify the payload to elevate privileges.

  • Re-encode and append a trailing dot.

  • Send the token and confirm elevated access.

Secret recovery and re-signing

  • Capture a valid HS256 JWT.

  • Run hashcat:

  • Recover the secret.

  • Modify the payload.

  • Re-sign with HS256 using the recovered secret.

  • Confirm the server accepts the re-signed token.

Self-signed JWT via jwk

  • Generate an RSA key pair.

  • Modify the payload.

  • Sign with your private key.

  • Embed your public key in the jwk header.

  • Match kid between header and embedded JWK.

  • Send and confirm acceptance.

Self-signed JWT via jku

  • Generate an RSA key pair.

  • Host a JWK Set with your public key.

  • Modify the payload.

  • Set jku to your hosted JWK Set.

  • Sign with your private key.

  • Send and confirm acceptance.

jku SSRF → localhost verification bypass

  • Generate an RSA key pair.

  • Find a localhost endpoint with side effects such as account verification.

  • Forge a JWT with jku pointing to that endpoint.

  • Sign with your private key.

  • Send and confirm the side effect triggers and the token is accepted.

kid path traversal

  • Capture an HS256 JWT.

  • Set kid to /dev/null or another known file path.

  • Modify the payload.

  • Sign with the file contents as the secret.

  • Send and confirm acceptance.

RS256 to HS256 confusion

  • Capture an RS256 JWT.

  • Obtain the server's public key.

  • Modify the payload.

  • Change alg to HS256.

  • Sign with the public key as the HMAC secret.

  • Send and confirm acceptance.

JWE token forgery

  • Identify a JWE token (five dot-separated segments).

  • Locate the exposed public key.

  • Forge claims.

  • Encrypt with the public key.

  • Send and confirm acceptance.


Tools

Tool
Purpose

Burp Suite + JWT Editor

Modify, re-sign, and inject JWTs

Burp Repeater

Replay requests with modified tokens

hashcat

Offline brute-force of HMAC secrets (-m 16500)

flask-unsign

Decode and brute-force Flask session cookies

jwt.io debugger

Manual encoding and decoding

Python jwt / jwcrypto libraries

Scripted token forging and JWE encryption

Python requests

Scripted automation (route through Burp proxy for logging)


Use this guide during web application penetration tests, CTF challenges, code reviews, and secure-design sessions. Attach evidence for each confirmed finding.

Last updated