Thursday, February 5, 2015

Exploiting memory corruption bugs in PHP (CVE-2014-8142 and CVE-2015-0231) Part 1: Local Exploitation

Update: Part 2 here!

Many people don't consider memory corruption bugs to be an issue for web-based applications. With XSS and SQL injection still being so wide spread, there is little room for concern for these types of bugs, as they're written off as "unexploitable" or plain ignored. However, these types of attack are much worse than SQLi or XSS, as
  • The attacker gets guaranteed system access
  • It can be difficult to identify the malicious traffic
  • Requires a patch from the maintainer/vendor, and with the hope that it was patched correctly. 
This post will be the first post in a three part series, starting with how to exploit CVE-2014-8142(and CVE-2015-0231), followed by remotely leaking arbitrary information, and ending with getting control of the PHP Interpreter. Stefan Esser(@i0n1c) is the security researcher who originally found both CVEs, and was also the first one to talk about controlling the PHP Interpreter(dubbed ret2php) at SyScan 2010.

This all starts back in 2004, when Esser found a Use After Free (#7) in unserialize(). This was part of the Hardened-PHP project, and no code was ever publicly released. In 2010, Esser found another use after free in SPLObjectStorage's unserialize(), which lead to a presentation at Syscan. Again, no code was released. Finally, CVE-2014-8142 was found and patched, but not patched correctly, which lead to CVE-2015-0231.

Luckily, Stefan provided us a POC that seg faults the interpreter. The following code will produce a seg fault on vulnerable versions of PHP.

Here's how it works: we update the value(s) associated with the "aaa" object by adding it again (with different values) within the same object. Next, the "ccc" object must point to the one of the values within the original "aaa" object.

Now that we know how it works at a high level, let's try and find the culprit. We know to look in process_nested_data, and after a quick glance, a certain section stands out:

Let's step through the script to identify what's actually happening here. We'll want a break on line 337 in var_unserializer.c (which is within the process_nested_data function).



Let's go ahead and step through the code found here:

Once we run the above script, continue past the first break point. Once in the second break point, run the following command
printzv *(var_entries*)var_hash->first

And we'll get an array of addresses, which are pointing to variables that have been parsed by unserialize(). We're interested in the fifth one (As our code uses R:5). Now that we know what address to watch, let's go ahead and step through this break point.


Since we've called the suspect function, let's go ahead and re-check our address that we selected above:


Success! However, let's make sure our address is still in the var_hash list, and then continue executing.



And when we continue:


Sweet! We can leak the data at the previous address. So, we can now leak the length of the supplied string. But that's not interesting. What about leaking arbitrary memory? Here's the code:

And here's the output:





What we're doing here is (hopefully) straightforward. We're creating our own ZVAL, which is the internal data struct that PHP uses. We define a few things for the pack() function to get our code execution. In order, they are:

  1. Type (unsigned int in our case)
  2. Address (The address we'd like to start the leak from)
  3. Length (How much memory we'd like to leak)
  4. # of References (0)
  5. Data Type (6, for String)

Of course, these values would change if we weren't faking a string ZVAL. Our for loop does the actual overwriting of the freed memory, which leads us to the above output. I left the $i value larger than usually needed, to make sure it works "everywhere", but most of the machines I've tested on are fine with 2 instead of 5 iterations.

Well, now that we can leak random data, how about upgrading to CVE-2015-0231? It's simple: Just replace "aaa" with "123". See the resulting output below:


Excellent! Now we have a working local POC that can leak arbitrary memory, for both CVEs. Our next goal is to do this same thing, only remotely. Stay tuned for Part 2, where we do just that!