Vulnerability Background This isn’t a vulnerability with a large impact, but it’s one that I thoroughly enjoyed exploiting. Hence this blogpost. The issue is in the PHP function imagerotate() :
The function takes in an $image, rotates it by $angle degrees and fills any empty space left over as a side-effect of the rotation with $bgd_color.
Here’s an example of how the function is used in practice:
The code creates an image as follows:
The crux of the vulnerability is in the function’s handling of the $bgd_color parameter to imagerotate(). That is an index to the Color Pallette. Looking at the PHP code, we can see that the palette is stored within the image’s gdImageStruct structure as red, green, blue and alpha arrays:typedef struct gdImageStruct { /* Palette-based image pixels */ unsigned char **pixels; int sx; int sy; /* These are valid in palette images only. See also 'alpha', which appears later in the structure to preserve binary backwards compatibility */ int colorsTotal; int red[gdMaxColors]; int green[gdMaxColors]; int blue[gdMaxColors]; int open[gdMaxColors]; ........... ........... int alpha[gdMaxColors]; The size of each array is gdMaxColors, defined elsewhere as 255. The function does not check that the passed in $bgd_color falls between these arrays’ valid ranges of 0 - 255, potentially resulting in an out of bound lookup to the arrays. Thus, if we run the following code, where $bgd_color is a large number (0x6667), we get the following image: $mypic2=imagerotate($mypic,45,0x6667); Notice the maroon color in the background? The color came from the undefined memory located at red[0x6667], blue[0x6667], green[0x6667] and alpha[0x6667].
By "deciphering" the background color, we can attempt to determine the bytes that were at that memory location, allowing us to perform an arbitrary memory read!
Exploiting the Vulnerability to Read MemoryHere's a high level visualization of how the image's color palette looks like in memory:
gdTrueColorAlpha(r, g, b, a) (((a) << 24) + \ ((r) << 16) + \ ((g) << 8) + \ (b)) Thus given a background color, we can obtain the underlying bytes of memory that were at that color’s memory location by:
Step 1 should be easy right? Looking at the PHP code above, breaking a color back into its RBG Alpha compoments should be as easy as a couple of bit shifting here and a couple of bit shifting there. Apparently Not! This is where it gets tricky (and fun)! There are a couple of issues:
Issue 1: PHP's RGBA internal representation
By convention, each RGBA component are typically represented by 1 single byte. For example, this color with 50% opacity is represented as 0x80FF9000 (ARGB convention) where:
If PHP had used this typical convention of 1 byte per component, reversing a background color back into the underlying memory would be extremely trivial. Instead, PHP stores each RGBA component as 32-bit Integers (4 bytes), despite only requiring 1 byte! Thus, this is a more accurate high level visualization of a PHP image's color palette, shown when computing the background color at index x:
Since PHP uses 4 bytes per color component, the formula to calculate the background color can be better visualized as:
The trick is to correlate a whole bunch of background colors. For example, given a background color computed from palette[x], we will know the LSB of Red[X]. This is denoted by ! in cell AX[1st Byte]:
Now what if we increase the palette index by 256 and compute the background from index x+256? This will give us BX[1st Byte] (This is the LSB of RED[X]). Since we now know BX[1st Byte], we can infer AX[2nd Byte] because background color Palette[X][2ndByte]= AX[2nd Byte] + BX[1st Byte] :
If we continue on another 256 bytes and compute the background from the x+512 index, we can infer even more bytes:
Thus this is how we can solve the issue of PHP using 32-bit integers for RGBA component representation and infer bytes in the original bytes in out-of-range memory. The only other tricky thing to take note of when inferring the bytes is to consider potential carry bits.Issue 2: PHP converts the palette-based image to a true color image after rotation
The vulnerability is triggered by imagerotate() and the vulnerable path is only taken when the image is a palette-based image. This is how the code is implemented in PHP:
Notice the following:
The Implication of line 9 is that for each image, you can only trigger the vulnerable code once before it is being coverted to a true color. You might ask, why can't we just generate a bunch of different images and trigger the vulnerable code once on each image? This is because, each image gets allocated in different parts of PHP heap, meaning that it would virtually be impossible to read from the same contiguous out-of-range memory using the above technique. To solve this issue, here is my pseudo code:
Issue 3:Alpha array is at a weird offset Notice that the arrays for Red, Blue, Green are all contiguous ? Not so for the Alpha array. It is at an offset which makes things a pain. This is something that has to be considered when writing the exploit. Original bug report can be found here. |
Blog >