Monday, February 23, 2015

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

In Part 1, we figured out how to locally exploit CVE-2014-8142 and CVE-2015-0231. In Part 2, we'll discuss remotely exploiting this vulnerability, and what we can steal from the application using the methods we discover. However, we will be focusing solely on CVE-2015-0231. Feel free to make the necessary changes as outlined in Part 1 to get CVE-2014-8142 working.

If you recall, Esser gave us code that leaked data at a non-attacker controlled address, which is shown below.

While this is useful, it's not as useful as the script we just wrote! We want to leak arbitrary memory remotely, not just random and basically useless memory. To do this, we need a way to:
  1. Write arbitrary data (without crashing PHP)
  2. Read arbitrary data (without crashing PHP)
As with anything in life, it's easier to tackle these problems one at a time! So, let's start with #1. We can write whatever we want, since we're sending our own object. However, we need a way to write useful information. Here's our last example:

We'll want to focus on the $fakezval variable. Is there someway we could write this zval remotely within a serialized object?? (Hint: It's a "feature" :D!)
As an aside, don't be stupid and lazy like I was. Read ALL of the code you're working on and around. I wasted a good 5-6 hours trying to figure this part out, until I face-punched myself a for missing this obvious portion of code.
Thankfully, there is, the 'S' character. The S character in a serialized string allows us to serialize and unserialize binary data. Let's play around with an S object, to get a better feel for how it works and how we can abuse it!

And let's go ahead and run our code, so we can see the awesomeness!


Hmm, this isn't exactly awesome. We're clearly doing something wrong here. To save everyone some face-palming, this error has to do with our S object, but feel free to count out the bytes by hand! And yes, I know PHP error messages suck.

In a normal serialized string, such as s:3:"123", the integer 3 is the number of characters that the string contains. However, in our above code, we have S:43:"\00\01\00\00AAAA\00\01\01\00\01\00\0B\BC\CC", which also has an integer (43), which is the length of our string, right?

Well, not exactly. We're not wanting a literal string here, we're wanting PHP to interpret this as binary data, as our string isn't actually 43 characters long, but 17. Let's try changing 43 to 17.


Excellent! But why did we use 17? Well, each \xx is considered to be 1 "character", which would leave us with 13. The "AAAA" characters are considered normal characters, so we add 4 to account for these. In short: Each \xx "triplet" is considered one character. Ok, now we can send this string, but how do we get information from the interpreter?

Before we leak arbitrary addresses, let's try to learn more about the server itself, as this will help us write a more reliable exploit (More on this in Part 3). To start, let's learn how to determine endianness of the server. To do so, we'll use a fake integer zval.

The idea stays the same:
  • Create an array of integers
  • Free the array we just created
  • Create our string from the previous example
  • Point to the reference of an integer that we just freed
Why use an integer zval instead of a string? Well, if you recall the zval struct, an integer will look like the following:
  • We set the value of the integer
    • 00 01 00 00
      • In Little Endian, this is 0x100
      • In Big Endian, this is 0x1000
  • We then fill the next 8 bytes with junk
    • 41 41 41 41 (Or: AAAA)
  • The next 8 bytes are the reference counter
    • 00 01 01 00
  • The final 8 bytes are 01 (Type Integer) and then we fill the rest with junk
    • 01 00 bc cc
 Putting this all together gives us our "S" value! But how does it tell us the endianness of the server? Well, in the server response, if it returns 0x100 (256), we'll know it's little endian! If it returns 65536, we'll know it's big endian! Let's see it in action:

And the application response:

Excellent! We now know the endianness of the server! Of course, we're trying to leak arbitrary data still, so let's figure that part out next. Since we can successfully leak data we supplied, is it possible to leak data at an arbitrary address?

Since the blog didn't stop here, it is assumed the answer is yes! However, instead of a fake integer zval, we'll use a fake string zval instead, which will look like the following:

  • We set the pointer to our string data
    • 00 80 04 08
  • We set the length of the string that we wish to extract (1024)
    • 00 04 00 00
  • We set the reference count to be not zero (0x101)
    • 00 01 01 00
  • Finally, we set the type to string, and fill the rest with junk
    • 06 00 0b bc
Here's what our new script looks like:

And when we run it, we get:


Excellent! We can now dump arbitrary memory, but we're required to know the address, which isn't practical. How do we extract addresses remotely? We could use the code from Part 1 to leak addresses, but the data leaked there isn't pointing to anything significant. Is there some other mechanism we can abuse to extract information?

Thankfully, there is! See the code below:


And when we run it:

Now, this is a rather big array, so let's break it down. The general idea is:
  • Create Integer Array #1
    • This will empty the memory cache
  • Create Integer Array #2
    • Fill in the variable table
  • Free Integer Array #2
    • Free spots in the variable table
  • Create an array of objects mixed with the S object
  • Free that Array of mixed objects
    • See comment below
  • Point to an integer value in Array #2 that was freed and overwritten
  • Response contains valuable data

We free the Object array so that the first 4 bytes in the array are overwritten by memory cache, since it was just freed (and therefore available for writing). In doing so, the string pointer (Which was previous 0x41414141) now points to the previously freed memory object. TL;DR - We get legit addresses!

But which addresses do we want? We're looking for the address that proceeds: "\x00\x00\x00\x00\x05\x00". This will be an object handler address, which is a struct in the data segment. Now, we can read the entire object handler table, and get information into the code segment of PHP(which is what we're interested in, since we want to pop a shell).

Here's the command to see the hex values returned by PHP
cphp newLeak.php | xxd -ps | sed 's/[[:xdigit:]]\{2\}/\\x&/g'
Let's go ahead and let it run, and when we grep for "\x00\x00\x00\x00\x05\x00", we find the following address:



If we load this into GDB, we also see that it is in fact a pointer into our object handler. Don't forget to set a breakpoint (I set one in var_unserializer.c:337) before running!


And what we see is:

Before we get too excited, let's make sure these are actually pointing to interesting things, let's just take the first entry: 0x0830a640. Here's what's stored at this address:


Awesome! We can now see everything that we need to! Thanks to these methods, we can now steal:
  • The entire PHP binary (and it's data)
  • SSL Certs (via mod_ssl)
  • PHP Symbols
  • Addresses of other modules (and their data as well)
Stay tuned for Part 3, where we pop a shell! This technique can also be used for CVE-2015-0273, as well as other UAF exploits in PHP.

Part 3 will take a while longer to get out, as I need to play around with PHP some more (and do some reading) before I can finish the exploit. But it will come!

    29 comments:

    1. Great Job! i have sent you a small tip, i am awaiting for some more coins to come in from a recent purchase and i will send more!
      One question, the file determineEndianness isn't working, here is the output :
      Parse error: parse error in endianness.php on line 5
      What am i doing wrong ? is there a typo in the code ?

      Kudos and can't wait for part 3!
      Cheers

      ReplyDelete
      Replies
      1. Thanks :). What version of php are you running?

        Delete
    2. 5.4.30 on my mac, and 5.5.9 on a ubuntu testbed

      ReplyDelete
      Replies
      1. I'm running 5.4.34 on kali in the example above. Is it the only one that doesn't run, or do the others not run as well?

        Delete
    3. There seem to be a problem with this line in the file i mentioned ( following examples suffer from the same problem ) :

      $data.='s:3:"123";a:40:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i$

      Do you notice the end of the line is a $ ? is that normal ?

      ReplyDelete
      Replies
      1. No, it's not normal, I see what I did wrong :|. I've now updated the gists, let me know if the new ones don't work!

        Delete
      2. Thank you for fixing that. determineEndinanness now seems to work without errors but it does produce a different output from what you show in the screenshots :

        $ php determineEndianness.php
        string(4) "b:0;"

        Delete
      3. I just noticed that this stands true for Mac OS but on linux the intended output is right.

        Delete
      4. This comment has been removed by the author.

        Delete
      5. (Removed previous comment to fix a typo)

        I am getting :
        string(79) "O:8:"stdClass":3:{s:3:"123";i:0;s:1:"0";s:17:" AAAA
        ��";s:1:"1";i:10;}"
        Does that make sense ? shouldn't it be either 256 or 65536?

        Delete
    4. I have just installed an even earlier version and the output is the same :

      $ php -v
      PHP 5.3.2-1ubuntu4 with Suhosin-Patch (cli) (built: Apr 9 2010 08:18:14)
      Copyright (c) 1997-2009 The PHP Group

      $ php determineEndinanness.php
      string(67) "O:8:"stdClass":3:{i:123;i:0;i:0;s:17:" AAAA
      ��";i:1;i:10;}"

      ReplyDelete
      Replies
      1. Hmm, not sure why it doesn't work on OSX, however you might have to play around with the reference's value, unfortunately I don't have a mac to test it on.

        Delete
    5. "Fill in the variable table" ??? what do you mean?

      ReplyDelete
      Replies
      1. Variable Table is an internal PHP struct that is leveraged by unserialize() to create the object and it's properties

        Delete
    6. determineEndianness script doesn't work.
      Notice: unserialize(): Error at offset 200 of 201 bytes in /var/www/html/exp/test.php on line 9
      string(4) "b:0;"

      Tested on php 5.4.35 and php-5.6.3

      Problem in this place 'i:1;r:12;}' If 12 to replace for example on 11, the error isn't present

      ReplyDelete
      Replies
      1. Right, as I said in part 1: The values will change depending on which version of PHP you're using. Since I'm using 5.4.34-dev, my values will be larger than a non-dev version of php.

        Delete
    7. I have some btc ready for you when the third part is published. I love your work

      ReplyDelete
      Replies
      1. Thanks! I've finished the exploit itself, hopefully can finish the majority of the blog this weekend.

        Delete
      2. Great, any chance you are done by now ?

        Delete
      3. I have just sent you another small donation

        https://blockchain.info/tx/01666c4e492d83dcadf6abf79ba5315087125ca92f0a64a7118a0b0b5807add6

        Delete
    8. There is a typo on the SendBinaryData code that the last line should be unserialize($data) instead of serialize(). BTW, you seem to be working using a 32bit PHP binary. Need some changes to fit 64bit PHP for the addresses.

      ReplyDelete
      Replies
      1. Good catch! And yes, that is done intentionally :)

        Delete
    9. Hi
      Did you had time to finish the part 3? Eager to see it!

      ReplyDelete
    10. I unfortunately haven't gotten to it until about an hour ago. Thankfully I just need to get the shell to pop, so Soon(tm)!

      ReplyDelete
    11. Please publish the exploit before i finish all my bitcoin :D

      ReplyDelete
    12. Are you kidding me with that? That's your explation for how to do it remotely?!? That..."its a feature"? Oh gee, thanks a mill brah.

      ReplyDelete