Tuesday, October 4, 2016

How I hacked Pornhub for fun and profit - 10,000$

A few months ago I was planning a long vacation and looked for some pocket money. Pornhub’s bug bounty program and its high rewards caught my attention.
In addition it is really cool to hack a site like Pornhub. 

TL;DR

This is about how I managed to successfully execute code on www.pornhub.com.

I exploited the callback parameter on video upload and was able to perform an elaborate form of object injection in multiple Pornhub sites. By using the SimpleXMLElement class in a specific flow, I was able to perform an Out-Of-Band XXE attack and by so, fetch the full content of private, local files on the server.

In addition to the local file disclosure, by altering the created class slightly, I was also able to achieve the following core abilities on the server:

* SSRF
* Blind Sql execution (Execute query on every Pornhub’s DB without receiving the output of the command)

By utilizing the combination of all 3 flows, I was eventually entirely capable to execute arbitrary code on pornhub.com.

Full path disclosure and some unserialize


Pornhub is an account based service site, which entails uploading files to the server as an vital feature of the site. In the eyes of a hacker, file uploads are mostly weak spot, generally a potential lead towards an exploit.

I decided to research a feature in pornhub.com, allowing a registered user to upload an image file to be used as his profile image.
The server successfully revokes most of my trivial attempts to manipulate the upload. However - once the image is uploaded, pornhub crops the uploaded image to a valid size (in order to match the user page's template). Once I uploaded the image, and attempted to crop the image, I managed to cause the server to display the following exception:


{"success":"ERROR","message":"Unable to make handle from: \/home\/web1\/upload.pornhub.com\/htdocs\/temp\/images\/1517\/avatarOriginal158936891.png"}.

As you can clearly pick up from the highly-descriptive error message, the exception leaked a full path to my uploaded file:

 /home/web1/upload.pornhub.com/htdocs/temp/images/1517/avatarOriginal158936891.png

Now that I was able to leak the exact, physical path to my uploaded file, I had at my disposal a directory on the server that is writable. Unfortunately, after contacting Pornhub team regarding to this issue, they responded that an identical issue has been previously reported by another researcher.

After a further and deeper dive into pornhub's upload feature, I managed to detect that the server accepts a cookie as one of the upload parameters. This cookie parameter contains by default, a serialized PHP array of user cookies. 
Apparently, the code responsible to process the upload eventually enumerates over the items in the list and adds an HTTP set-cookie header if a specific item does not exists in the Request's Cookie header.
What I stumbled upon happens to actually be a useful unserialize exploit, fully loaded from user input. In order to widen my attack vector - I would need a class with an interesting __toString function (this one called automatically by php when object converts to string). 
Basing on the fact I am in a BlackBox exploit research - I obviously do not have the source code and thus - I'm not familiar with any php classes implemented by pornhub - so I am forced to use only native PHP built-in classes. Unfortunately, I was not able to find any class with an interesting __toString function,which does not require calling to the class's constructor - So I decided to abandon the flow, despite it's large potential.
Although the flow was partially-useless for me, those guys did find this highly useful https://www.evonide.com/how-we-broke-php-hacked-pornhub-and-earned-20000-dollar/. Lucky for them I couldn't leverage that so I didn't report for "unserialize from user".

Upload video and interesting callback


As the next stage towards RCE, I decided to focus on the video uploading feature for the same reasons specified in the intro.
As in the previous file upload, all my attempts to exploit the uploading feature failed miserably - pornhub do know how to partially protect their server from user-uploaded files. However, during my attempts, I noticed one interesting parameter called "callbackUrl", and sure enough - this parameter contained a rather interesting URL. Naturally, I tried accessing this URL and got an exception specifying that a "job" is invalid. I decided to explore this "job" parameter further.

I changed the URL in "callbackUrl" to my own URL and hoped this parameter is actually used and isn't a deprecated/white listed parameter left as legacy. Surprisingly, after changing the URL to my local server, I did get a connection:



This seems like a potential SSRF, but the origin of the request is a 3rd party server, not related to the Pornhub domain or in its IP scope. This means I would not be able to access internal pornhub services with it, and researching the 3rd party site is a little out of scope.

However this request did give me vital information about the expected structure of the "job" parameter. It appears that "job" is a very long JSON array specifying the uploaded file for further processing, probably encoding. My first attempt was to manipulate the json and by so - upload a php shell, but I failed and I was inches away from giving up on the hunt, until…

Object injection? 


I began examining the "type" item in the JSON - Which appeared to be a class name. I changed it and sent the json into the original url and the result was rather surprising:
The response received specified that the class does not exist. That led me to the conclusion that it is a class name and I might be able to, once again, perform object injection. Unfortunately, a blind object injection at this point is virtually impossible...

Soapclient:

Personally, SoapClient is my favorite choice when dealing with unserialize data: In most cases, it has the potential to expose critical information about the system behavior (i.e. function names, parameters) and often allows you return any value you want, including inputs the programmer never intended to provide.

I used the SoapClient as my injected class and changed the parameters which I presumed were related to the original class. As a response,the server threw an unusual error:

 "SoapClient::SoapClient(): $wsdl must be string or null"

This error is unusual since it is a constructor error which cannot be called from unserialize, probably leading to the fact that Pornhub implemented their own unserialize method.

I tried to comprehend how this unfamiliar unserialize mechanism works and after various attempts with some indicative errors, I discovered I could control all the arguments sent to the constructor. With this knowledge I was able to create a request with SoapClient set to my server instead of the original class.

The request:

The SoapClient request as received on my server:

Using SoapClient I now had the ability to :
  • make SSRF (Server-side request forgery)
  • fetch the exact PHP version on the server (which turned out to be version 5.6.17) 
  • Use the function called on the original class (getResult, without parameters).
I tried to return a large variety of answers to "getResult" but it failed every time.
In order to broaden my vector, I figured there might be a better class than SoapClient, so I tried using php's default DirectoryIterator class.
The request and response of DirectoryIterator on "/":

At this point, I didn’t have access to “/” but thanks to the object injection along with the DirectoryIterator, I managed to discover the exact list of directories I have access to, and check whether a directory exists.

Attempting to put DirectoryIterator as one of SoapClient properties failed, and it seems that not every item in the JSON is decoded as an object.

Encouraged by the hefty results I did manage to bring, I kept searching for other built-in PHP classes that might be useful.

SQLite and PDO


I stumbled across the SQLite3 class, constructed with a "filename" parameter.
I used the writable path from the first vulnerable and by so, I was able to create an empty file. Why an empty file? simply because I needed it to run a query and potentially insert content into the file (potentially using it as a shell).
I needed to call an additional function in order to perform the actual query, to write into the file, but that wasn’t possible.

I also looked into PDO, as a potential class injection, however I realized it acts similarly to SQLite3 class.

Despite being disappointed from the results above, I searched yet again for another useful built-in PHP class.

SimpleXMLElement

As a last resort, I decided to take a hard long look at the native SimpleXMLElement class,

In hacking terminology, XML is almost immediately associated with XXE. However, as I managed to fetch the php version installed the server, PHP version 5.6.17 have managed to "immune" the SimpleXMLElement class to XXE - if an external entity exists, the class throws an exception and stops the XML processing. Thus, I swiftly realized a basic XXE will not be of any use in this case.

Despite of the poor success in XXE exploitation so far, SimpleXMLElement constructor does contain an optional parameter named "options", which is used to specify additional Libxml parameters.
One of those parameters is "LIBXML_DTDLOAD" - which later on enabled me to load external DTD and make XXE Out-Of-Band attack after all.

After sharpening my XML-writing skills and deploying a myriad of attempts, I managed to successfully fetch my first file!!!


I ended up running two servers, one for the XMLs and the second for the received file (NetCat was used, as most of the files were too large for normal servers to receive via URL)

The first XML contained two external parameter entities:
  • The local file wrapped with PHP filter of base64 - payload
  • The remote DTD – the second xml on my server - dtd
The second XML contained one parameter entity (all) that creates new parameter entity (send) that combines my URL with the file payload.

Summed up, the steps towards my attack:
  1. Perform a POST request to Pornhub's server.
  2. Pornhub's server downloads xml.xml – the first XML
  3. Pornhub's server downloads xml2.xml – the second XML
  4. Pornhub's server is lead to make a GET request to the second server's URL with parameter "a" that contains the base64 content of the local file on Pornhub server

Get config file 


From this moment onward, my blind blackbox research transformed into a white-box vulnerability research, and at that point - I decided it is time to contact pornhub.com and share my findings with them.

Upon publishing my report to pornhub, I downloaded parts of their sources and stumbled across pornhub's master DB credential

PDO's MySQL driver has "PDO::MYSQL_ATTR_INIT_COMMAND" option which allowed me to execute query using the PDO's constructor. 

After I fetched the connection string I was able to send request with PDO class and hence - run any query on every database of Pornhub. This was later on added to the first report.

Code execution on www.pornhub.com


Armed with my fresh copy of the site's code, as well as my full access over pornhub's database, I figured it would be rather easy finding my remote code execution on the main site.
well - not really…
I did find a call to php's exec function in the GIF generator mechanism:
apparently you can make a GIF from any video on pornhub.com and the file path of the video is taken from the DB and is not escaped in any form.

By the time I managed to reach this flow, Pornhub team blocked the URL on upload.pornhub.com in order to temporarily mitigate this vulnerability (according to my first report sent to them) but the URL was still accessible on www.pornhub.com. However this site was blocked for outgoing connections – so, I could not leak any more information.
As my POC of an imminent RCE on pornhub.com, I changed the “filename” in the database to `sleep 60`, however I neglected to check that the main DB has a 30 minutes cache...
While waiting patiently for the cache to expire I searched for a writable path using my recently-found SQLite and DirectoryIterator native class injections - but i could not find any…

I kept trying to run a command and get the output through the DB, during my tests Pornhub team blocked the URL so I had to summarize my entire research with a successful RCE with no output (the 'sleep' command did work after waiting for the 30 minute cache to perish)
The details of the RCE were also added to my original report on pornhub.com

Conclusion



Object injections, as opposed to sql injections, are relatively unknown - developers usually do not realize they can lead to dangerous vulnerabilities, so they do not bother securing their code from them. However, if used correctly, they can lead a researcher to an endless number of exploitation flows. In my case - the flow led me to fund my long needed holiday abroad, but also helped me prove that if you set your mind on a target - you can reach it (or in my case - hack the almighty pornhub.com)

Timeline


  • 2016-05-10 - discovered the unserialize - didn't report
  • 2016-05-15 - report about path disclosure 
  • 2016-06-01 - report about file disclosure 
  • 2016-06-02 - added the full DB control
  • 2016-06-06 - added the code execution
  • 2016-07-06 - 10,000$ reward from Pornhub
  • 2016-09-12 - Pornhub resolves the report
  • 2016-10-03 - the report has been publicly disclosed