Lucene search

K
hackeroneKubabreckaH1:1356
HistoryDec 27, 2013 - 2:57 a.m.

Internet Bug Bounty: PHP Heap Overflow Vulnerability in imagecrop()

2013-12-2702:57:00
kubabrecka
hackerone.com
45

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.048 Low

EPSS

Percentile

91.8%

Overview:

PHP 5.5.0 added a function called imagecrop() in PHP’s gd extension. This function is implemented using the gdImageCrop() function, which creates a new gd image and crops the result by directly copying pixel data. However, this function contains multiple arithmetic operations prone to integer overflow, which can lead to copying memory of incorrect size. This can cause a heap overflow and/or other memory corruption.

Because PHP applications are quite likely to call the imagecrop() function with user-supplied data (e.g. an image-processing script can get both the image data and cropping dimensions as user input), this vulnerability should be considered as remotely exploitable.

Furthermore, the implementation of imagecrop() and gdImageCrop() contain several other bugs/vulnerabilities that can cause crashes, DoS or leak information (i.e. read process memory).

Details:

The imagecrop() function can be used to crop an image with the following call:

$dimensions = array("x" => 10, "y" => 10, "width" => 50, "height" => 50);
$new_image = image($image, $dimensions);

The implementation of imagecrop() in ext/gd/gd.c performs very little checking of the supplied dimensions:

gdRect rect;
...
if (zend_hash_find(HASH_OF(z_rect), "x", sizeof("x"), (void **)&tmp) != FAILURE) {
  rect.x = Z_LVAL_PP(tmp);
} else {
...

One issue here is that there is no check of tmp’s type nor a conversion. This means that if the dimensions array contains the key “x”, its zval’s value will be treated as an integer even if it’s really a string or an array. This can be used as an information leak vulnerability, because strings and array contain pointers which can be used for subsequent exploits. See POC 1.

The “rect” variable is then used in a call to “gdImageCrop”:

im_crop = gdImageCrop(im, &rect);

This function then uses the user-supplied dimensions for various calculations:

if (src->trueColor) {
  dst = gdImageCreateTrueColor(crop->width, crop->height);
  gdImageSaveAlpha(dst, 1);
} else {
  dst = gdImageCreate(crop->width, crop->height);
  gdImagePaletteCopy(dst, src);
}
dst->transparent = src->transparent;

The gdImageCreateTrueColor() and gdImageCreate() functions are smart enough to block any attempt to overflow the width and height parameters, returning a NULL pointer when this happens. However, this code has an issue of not checking the return value of these functions, using the “dst” variable for memory writes unconditionally. This means it can cause a NULL pointer (or a close-to-NULL pointer) write, which is probably just crash the process, but since the gd image structure is very large, it could happen to even touch allocated memory. See POC 2.

The function then performs some bounds checks:

if (src->sx < (crop->x + crop->width -1)) {
  crop->width = src->sx - crop->x + 1;
}
if (src->sy < (crop->y + crop->height -1)) {
  crop->height = src->sy - crop->y + 1;
}

These are using signed integer arithmetics and can be overflown and tricked into incorrect calculations. Later, for true-color, the pixels are copied with this code:

int y = crop->y;
...
unsigned int dst_y = 0;
while (y < (crop->y + (crop->height - 1))) {
  /* TODO: replace 4 w byte per channel||pitch once available */
  memcpy(dst->tpixels[dst_y++], src->tpixels[y++] + crop->x, crop->width * 4);
}

Remember that crop->x and crop->y are completely user-supplied values and we can supply negative values. This way we can force the copying code to read outside of the source image pixel data, causing a crash or an information leak. See POC 3.

We must however keep the crop->width and crop->height positive, and reasonable, because they are used at the beginning of the function to create a destination bitmap. We can however trick the already mentioned bounds checking code:

if (src->sx < (crop->x + crop->width -1)) {
  crop->width = src->sx - crop->x + 1;
}

When supplying a very large crop->x value, we can make the condition pass, assigning a value to crop->width which is larger than the real destination’s pixel buffer width. The memcpy will then copy more data than the heap-based buffers can hold, causing a heap-based buffer overflow. See POC 4.

All the supplied POCs will cause a crash on 32-bit systems. First the POCs will segfault due to invalid memory read, the last one will crash due to a heap overflow (gdb output attached, but the backtrace is useless because the crash occurs much later, at PHP’s shutdown and cleanup). Tested on a 32-bit Ubuntu Server machine. All versions of PHP containing the imagecrop() function are vulnerable, i.e. PHP 5.5.0 and newer.

<?
// POC 1
$img = imagecreatetruecolor(10, 10);
$img = imagecrop($img, array("x" => "a", "y" => 0, "width" => 10, "height" => 10));
<?
// POC 2
$img = imagecreatetruecolor(10, 10);
$img = imagecrop($img, array("x" => 0, "y" => 0, "width" => -1, "height" => 10));
<?
// POC 3
$img = imagecreatetruecolor(10, 10);
$img = imagecrop($img, array("x" => -20, "y" => -20, "width" => 10, "height" => 10));
<?
// POC 4
$img = imagecreatetruecolor(10, 10);
$img = imagecrop($img, array("x" => 0x7fffff00, "y" => 0, "width" => 10, "height" => 10));

Fix:

Resolved in PHP Version 5.5.9, bug [#66356] (http://bugs.php.net/66356) (Heap Overflow Vulnerability in imagecrop()).

https://github.com/php/php-src/commit/2938329ce19cb8c4197dec146c3ec887c6f61d01
https://github.com/php/php-src/commit/8f4a5373bb71590352fd934028d6dde5bc18530b

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.048 Low

EPSS

Percentile

91.8%