September 18, 2021

SSRF in PDF export with PhantomJs

SSRF in PDF export with PhantomJs
TL;DR: A PDF exporting feature running with PhantomJs cannot read local files with iframes with file protocol, but with a simple AJAX read we can get the content of any local file or service.

Hi folks, hope you are having a great day, this is a new write-up I’m writing about a recent SSRF I was able to find in PhantomJs.

The story begins when I got a new invite to a private program on HackerOne, and the program looked interesting to me as I like the type of program, which is about dashboards and panels…

Anyway, I started to check what features can be abused in the program and was able to find some XSSes and Broken Access Control issues, but I wanted to find something critical in that program, so I started to check more features, and I came across a page that logs the user’s history in his account and it has a table with some information like (name, time, page, action, etc…) and there is a button to export this log as CSV or PDF.

There was for sure a CSV injection issue, but I was interested in the PDF, I thought that if I can find an XSS in one of these columns I’ll be able to inject my custom HTML/Js inside the PDF export so I tested all the columns that can be controlled by the user… Unfortunately, they were all sanitized, I thought again maybe the PDF export isn’t connected with the form and while exporting I can execute HTML, so I tested my name and exported but it didn’t work :(

The second day I started again thinking that I must test more into this one as I did already cover most of the application and this was the last part, so I thought again to test all the columns but with the PDF export… and it worked 😎, One column was storing some values from a post request which was hard to track down but finally when I did, it didn’t do anything until I export the PDF and I saw my <img src=xhzeem> payload showing an image in the PDF file.

Now it was time to check what am I dealing with so I started by injecting an iframe to hookbin.com and I got this:

Hookbin screenshot

I confirmed the iframe is loaded from the server’s IP address, and I found that they are using PhantomJs to export this PDF file, and now I thought about trying to read the /etc/passwd file directly, so I tried to iframe file:///etc/passwd but it was just returning a blank page :(

I thought I must test javascript now so I tested first with a simple payload <script>document.write('xhzeem')</script>, and it worked!

The column exported only had the word xhzeem which means that my javascript is supported, Here the first thing I tested was to check if I’m in a file or http protocol, so I used the following payload

<script>document.write(JSON.stringify(window.location))</script>

I got the whole location object and I saw that I’m at file:///tmp/export/[UUID].html and every time I export it again a new unique UUID is created, I then thought about testing the iframe on the current page using

<script>document.write('<iframe src="'+window.location.href+'"></iframe>')</script>

And the whole page was iframed inside itself… So I should be able to access the file:// protocol, but for some reason here PhantomJs was blocking this access because I wasn’t able to understand why I cannot iframe the page I was left with XHR to read the files for me and I started creating the payload which was

<script>    
xhzeem = new XMLHttpRequest();
xhzeem.open("GET","file:///etc/passwd");
xhzeem.send();
xhzeem.onload = function(){document.write(this.responseText);
}    
xhzeem.onerror = function(){document.write('failed!')}
</script>

Hope you learned something from this, and the most important thing is never to test some endpoints and expect the rest of the application to behave similarly, always test each and every possible bug by its own.