Web Cache Deception & BAC in /uploads
E-commerce ficticio que encadena dos fallos: una SPA en React que decide el rol del usuario leyendo `GET /api/auth/me` (CWE-602), y un middleware de cache propio que normaliza la query string (strip de `utm_*`, `ref`, `cb`, `_`, `t`), no varía sobre `Authorization`/`Cookie`, e ignora `Cache-Control: private, no-store` (CWE-524). Un bot interno de soporte (Puppeteer-core) navega URLs same-origin con cookies de admin, lo que permite sembrar el cache con la respuesta privilegiada y leerla anónimamente.
Accede a este lab
Entorno seguro y aislado
Crea una cuenta y suscríbete para acceder a todos los labs. Practica en un entorno real y seguro.
Hunters que lo han resuelto· 8
Objetivos
Logro que recibirás
Cuando resuelvas este lab desbloqueas este logro compartible
Web Cache Deception & BAC in /uploads
Writeups de la comunidad
Lumora Market es un mini e-commerce (Express + TypeScript + better-sqlite3 + React 18 + Vite + Puppeteer-core, puerto 2300) diseñado como playground para una cadena de dos vulnerabilidades reales:
Capa 1 — Confianza ciega del cliente en la respuesta del servidor (CWE-602). La SPA hace
GET /api/auth/meal cargar y persiste elroledevuelto. El navbar y el routing del panel/adminse renderizan solo a partir de ese campo. Una regla de Burp que reescriba"role":"user"→"role":"admin"en la respuesta hace aparecer la UI de admin con la lista de "Internal Reports", incluyendo la URL/internal/credentials-report.pdf. Al pedirla directamente, el middlewarerequireAdmindel backend devuelve403— esa puerta es honesta. El bypass solo sirve como recon: revela el nombre del recurso sensible.Capa 2 — Web Cache Deception por normalización de la cache key (CWE-524). El middleware de cache propio (
server/src/middleware/cache.ts) tiene tres defectos combinados: (a) reescribe la query string eliminandoutm_*,ref,cb,_,tantes de calcular la key, (b) no incluyeAuthorizationniCookieen la key (no hay vary por identidad), y (c) almacena toda respuesta2xxdurante 60 s ignorandoCache-Control: private, no-storeque pone el origen. Resultado: dos URLs vistas como distintas por el origen (porque la cookie del bot autentica al admin) terminan en la misma bucket que la URL "limpia" anónima.Gadget — el bot de soporte. El formulario público
/supportadmite un camposcreenshot_url. El procesoserver/src/bot/supportBot.ts(Puppeteer-core) hace login como admin con credenciales aleatorias por boot, polling de ticketsstatus=new, lanza Chromium, setealm_token=<JWT admin>como cookie enlocalhost, y hacepage.goto(screenshot_url, { waitUntil: 'domcontentloaded' }). Rechaza orígenes que no sean los del propio lab (localhost:2300,127.0.0.1:2300,lumora:2300), por lo que no es un SSRF clásico — pero sí un primitivo perfecto para envenenar el cache con la respuesta privilegiada del propio sitio.
La cadena completa:
- Login como
attacker@lumora.market / password123. - Burp Match & Replace sobre body de respuesta:
"role":"user"→"role":"admin". - Recargar SPA → aparece
/admincon la URL/internal/credentials-report.pdf. - Confirmar normalización con
curl -i 'http://localhost:2300/internal/credentials-report.pdf?cb=test'y ver queX-Cache-Key: /internal/credentials-report.pdf(sin la query). - Abrir
/supporty enviar un ticket conscreenshot_url: http://localhost:2300/internal/credentials-report.pdf?cb=poison. - Esperar ~10 s (ciclo de polling del bot). El bot navega la URL → el origen sirve el PDF al admin con
Cache-Control: private, no-store→ el middleware lo guarda igual bajo la key normalizada. - Anónimamente:
curl -i http://localhost:2300/internal/credentials-report.pdf -o leaked.pdf→X-Cache: HIT,200 OK. pdftotext leaked.pdf - | grep -oE 'FLAG\{[a-f0-9]{32}\}'→ flag.
Por qué encaja en la familia "Web Cache Deception". El paper original de Omer Gil (2017) abusa de un CDN que cachea por extensión /account.php/foo.css mientras el origen ignora la parte cosmética y devuelve la página sensible. Aquí el twist es el mismo en otra dirección: dos URLs (...?cb=poison y ...) son distintas para el origen (autenticación + branching) pero iguales para el cache (la query se descarta antes de keyear). Una víctima privilegiada visita la "ruidosa" y la víctima anónima cosecha la "limpia".