The Anatomy of a JWT Hack

5 min read

May 25, 2025

The Anatomy of a JWT Hack

Table of contents

Hey everyone!
Big day: the first chapter of my Docker + Rust series is out, and this newsletter drops alongside it! 🎉
If you want to learn how container security works from the inside out, while watching me build a CLI tool (Valeris) from scratch, go check it out on the blog.

But today, we're shifting gears from containers to credentials.
Let’s talk about JSON Web Tokens (JWTs) those small Base64 blobs that silently carry identity and permissions across APIs. They're everywhere in modern web apps, and when implemented poorly, they open the door to serious attacks.

In this issue, we’ll explore what JWTs really are, how they work, and the most common ways they get hacked: from alg: none tricks to key injection and algorithm confusion attacks. Real bugs, real CVEs, real exploitation.

Let’s dive in 👇

🧩 What Are JWTs, and Why Do They Matter?

JSON Web Tokens (JWTs) are a compact way to represent claims between two parties. Think of them as signed JSON blobs used in web apps to handle sessions, permissions, and user identity without needing server-side state.

They look like this:

<base64url(header)>.<base64url(payload)>.<base64url(signature)>

Each section is Base64URL-encoded:

  • Header – specifies algorithm (alg) and token type (typ)
  • Payload – includes claims like sub, admin, exp, iat, etc.
  • Signature – ensures the data hasn’t been tampered with

Example payload:

{
  "sub": "user1",
  "admin": false
}

JWTs are usually sent in an Authorization: Bearer header. And since the payload is not encrypted by default, anyone with the token can read it but they shouldn’t be able to modify it without invalidating the signature. That’s the theory… let’s see what happens in practice 👇

🔓 Signature Not Verified

This is the deadliest and most common mistake. Some developers use decode() to parse JWTs without verifying the signature with verify().

If the backend skips signature verification:

  • You can edit any field (e.g., admin: true)
  • Keep or remove the signature altogether
  • And the server will accept it

📌 Result: total authentication and authorization bypass. You're basically the admin now.

alg: none Attack

JWTs specify the signing algorithm in the header, and alg: none means… no signature.

Originally intended for debugging, some libraries (or careless configs) still allow it.

Attack flow:

  1. Change "alg" in the header to "none"
  2. Remove the signature part
  3. Modify the payload however you like
  4. Reassemble: header.payload.
  5. Send the token

📌 If accepted, the app is trusting unsigned data. Game over.

🧨 Weak HMAC Secrets (HS256 Brute Force)

When apps use HS256, the same key signs and verifies the token. If that key is weak (e.g., secret, 123456, app name…), it can be brute-forced offline.

Steps:

  • Capture a valid JWT
  • Use tools like hashcat or jwt-tool to brute-force the key
  • Forge tokens with any payload you want

📌 This works especially well on dev/staging environments, open-source projects, and rushed setups.

🌀 Algorithm Confusion: RS256 ➜ HS256

This one is sneaky.

Suppose the app uses RS256, which relies on a private/public key pair:

  • The server signs with the private key
  • Verifies with the public key

But if it trusts the alg field from the JWT, you can:

  • Change RS256HS256
  • Use the public key (which you might have) as the HMAC secret
  • Sign a token with your payload

📌 The server thinks it’s verifying with RSA, but it’s actually verifying a forged HMAC. Access granted.

🔀 Algorithm Confusion: ES256 ➜ HS256

Same idea, different algorithm. Instead of RSA, the server uses ECDSA (ES256).

If the app doesn't enforce the expected algorithm:

  • Change alg: ES256 to HS256
  • Use the ECDSA public key as HMAC secret
  • Sign your payload

📌 Another case of mixing asymmetric and symmetric crypto. And attackers love it.

🪤kid Injection (Key ID Manipulation)

JWTs can include a kid field in the header to indicate which key should be used to verify the token.

But if the app:

  • Loads keys from file system using kid, or
  • Performs a raw DB query with kid

Then you can do things like:

  • kid: ../../../../dev/null → server reads empty key
  • kid: ' UNION SELECT 'fake-key' -- → SQL injection

📌 Used correctly, you can point the app to a key you control, or force it to use a blank key and sign your own tokens.

🧬 Embedded JWK (CVE-2018-0114)

JWTs also support an optional jwk field that embeds the public key directly in the token header.

If the server accepts any key from this field without validation, it’s vulnerable.

Attack:

  • Generate your own RSA key pair
  • Sign the JWT with your private key
  • Embed the public key in the jwk header
  • Send it

📌 The app uses your embedded key to verify your fake token. Total bypass.

🌐 JKU / X5U Header Abuse

The jku and x5u fields let a token point to external URLs for keys or certs.

If the backend:

  • Fetches these keys dynamically
  • Doesn’t validate where they come from

Then attackers can:

  • Host their own JWKS or X.509 cert
  • Sign the token with their private key
  • Insert jku/x5u to point at their hosted key
  • Send the forged token

📌 This isn’t just key injection it can also be a vector for SSRF.

🧪 Claim Confusion (Missing aud, iss, sub Checks)

If an app doesn’t validate claims like audience or issuer, attackers can:

  • Use tokens issued for a different service
  • Reuse tokens across microservices or APIs
  • Escalate privileges horizontally

📌 This is surprisingly common in modern, distributed architectures.

⏳ No Expiration / Long-Lived Tokens

JWTs should expire fast. If a token has:

  • No exp, or
  • An exp set to years in the future

Then:

  • It can be reused forever
  • Attackers can persist access indefinitely

📌 Often paired with other techniques to maintain access after compromise.

🛠 Tools for JWT Hacking

Keep it lean, sharp, and fast. These are the essentials:

  • jwt-tool: All-in-one CLI to decode, tamper, brute-force, and test JWT vulnerabilities.
    👉 ticarpi/jwt_tool
  • Hashcat: Brute-force HMAC secrets (HS256/HS512) offline with GPU power.
    👉 Mode 16500 for HS256
  • Burp Suite + JWT Editor: Decode, modify, re-sign JWTs on the fly. Great for testing alg, kid, jku, and more.
  • TruffleHog / Gitleaks: Scan repos for leaked JWTs or weak secrets. Great for recon.

🧪 Where to Practice

Want to test these attacks in the wild (safely)? Here’s where to train:

  • 🎯 PortSwigger Web Security Academy
    • Realistic JWT labs: signature bypass, weak key brute-forcing, and header abuse
    • 👉 Great for hands-on skill building
  • 🎮 Hack The Box – Craft
    • Combine Git recon with JWT forging and signature cracking
    • 👉 Perfect mix of theory + practice
  • 🎮 Hack The Box – Awkward
    • Crack JWTs with Hashcat, forge tokens, and exploit logic flaws
    • 👉 Covers HS256 abuse, scripting, and privilege escalation
  • 🎮 Hack The Box – Yummy
    • Exploit weak RSA keys using RsaCtfTool to forge JWTs
    • 👉 Learn how broken crypto affects JWTs in real apps
  • 🎮 Hack The Box – CyberMonday
    • Perform an RS256 ➝ HS256 algorithm confusion attack using the server's public key
    • 👉 Realistic and highly relevant vulnerability
  • 🎮 Hack The Box – Luke
    • Find and crack the JWT secret, forge tokens, and access protected APIs
    • 👉 End-to-end JWT manipulation and scripting practice
  • 🎮 Hack The Box – Blazorized
    • Extract hardcoded JWTs from DLLs and inspect traffic for insecure storage
    • 👉 Mix of reverse engineering and token abuse

🧭 Final Thoughts

JWTs are everywhere: APIs, mobile apps, single sign-on flows. And when mishandled, they can be your easiest way in.

Start by understanding how they work. Then test. Tamper. Re-sign. Exploit.

Whether you're a pentester, bug hunter, or dev, knowing how to break JWTs means knowing how to protect them.

Until next time,
Stay sharp, stay curious,
Ruben 🚀

Chapters

Botón Anterior
🐙 Hacking GitHub – A Beginner’s Guide to Finding the (Not So) Hidden Stuff

Previous Issue

Enjoyed the article?

Subscribe to the newsletter and get technical insights, cybersecurity tips, and development content straight to your inbox. Or support my work with a coffee ☕ if you found it useful!

📫 Subscribe now ☕ Buy me a coffee