Security Advisory

Name         : PHP
Threat level : LOW/MED
Class        : Denial of Service
Discovered   : 2009-08-07
Published    : ---
Credit       : Martin Noga, Gynvael Coldwind, Matthew "j00ru" Jurczyk
Vulnerable   : PHP 5.2.10 / 5.3.0, all platforms

==[ Abstract ]==

PHP is a well-known script programming language, commonly used by the Apache web server,
present on most machines running under Linux/Unix kernel control. It is mostly used to run
scripts on the WWW server side, but it can be also used from the command-line level. Since the
PHP server is running in the context of Apache, an exception generated by PHP will most likely
result in the entire apache process going down. By using the vulnerability describing here, one
is able to crash the PHP server (in its default configuration), both locally and remotely.

==[ Details ]==

The vulnerability makes use of the lack of a special sanity check regarding the user-supplied data,
inside the default EXIF extension. The module intends to allow an external programmer to obtain
every possible information existing inside the EXIF tag inside a JPEG / TIFF image file. Such
information is often stored by modern cameras, providing additional information about the specified

To be more exact, the unsecure code is listed below:

--- cut ---

static void exif_process_APP1(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement TSRMLS_DC)
/* Check the APP1 for Exif Identifier Code */
static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
if (memcmp(CharBuf+2, ExifHeader, 6)) {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Incorrect APP1 Exif Identifier Code");
exif_process_TIFF_in_JPEG(ImageInfo, CharBuf + 8, length - 8, displacement+8 TSRMLS_CC);

--- cut ---

The bug is not really obvious here, though it is easily exploitable under certain conditions. The whole idea
relies on the fact that the function loads a section of data into a newly allocated memory block, and then
compares the memory with a contant EXIF signature. The unsecure part of the concept is that it does not check
whether a necessary number of bytes has been read from the file - the only validation part is the memcmp()
function call.

By using various methods of spamming the memory with user-specified data (like heap-spraying), it is possible
to create a situation illustrated below:

[(WORD) Section Size: 7][(5 BYTE) Section Data: Signature][(BYTE) Last Signature Byte: \0][(DWORD) TIFF Signature]

Having such a memory layout, all the sanity checks would pass throught, even though only 7 bytes are really
read from the file (8 is the minimum for Length+Signature). The ramaining \0 byte, as well as the TIFF
signature has to be put in the place by the attacker, somehow. According to the author's research, it is
possible to create such a situation, by allocating and deallocating properly-sized memory buffers (PHP arrays
filled with string objects).

The most interesting part is:

--- cut ---
exif_process_TIFF_in_JPEG(ImageInfo, CharBuf + 8, length - 8, displacement+8 TSRMLS_CC);
--- cut ---

As one can see, it uses the (length-8) value, without making sure the length variable is actually
greater or equal to 8, letting one pass a negative value as a 'size' paremeter!
The last thing to explain is how are the Denial of Service conditions triggered during the function execution.
To understand it, one must dive deeper into the code, and take a look at the following "if" statement:

--- cut ---
if (offset_of_ifd > length) {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid IFD start");
--- cut ---

IFD offset (the next DWORD value after the TIFF signature) is compared to the length parameter we've discussed
about before. There would be nothing really bad about this comparison (it would actually work in some cases) if the
offset_of_ifd local variable wasn't typed as "unsigned int". Because of how this is now, the length value is also
treated as an unsigned value (0xFFFFFFFF is the greatest 32-bit unsigned number), consequently passing through
this sanity check.

Everything that is needed to crash the application is to pass a specially crafted offset_of_ifd value poiting to
an invalid address, eventually resulting in the PHP crash. The author has not been able to control the offset_of_ifd
value directly, though it is very likely that after a number of this function's calls, some trash data will be
used, leading to Denial of Service conditions.

==[ Solution ]==

The simplest (and probably best) solution is to check not only if the memory bytes are as supposed, but also if
these bytes have been really read from a file, not some remaining trash memory that the PHP user could be controlling.
In this particular case, the "length" field, being part of the section data should be validated to ensure the proper
amount of bytes have been read from the file.

==[ Proof of Concept ]==

The PoC file should be enclosed to this advisory file, in a ready-to-use state.

==[ Vendor Status ]==

The vendor has not yet been informed about this issue.

==[ Time line ]==

2009-08-07 : The vulnerability has been discovered
2009-xx-xx :

== Disclaimer ==

This document and all the information it contains is provided "as is",
without any warranty. Author is not responsible for the misuse
of the information provided in this advisory. The advisory is
provided for educational purposes only.

Permission is hereby granted to redistribute this advisory, providing
that no changes are made and that the copyright notices and
disclaimers remain intact.

Copyright (C) 2009 Hispasec Sistemas

Add a comment:

URL (optional):
Math captcha: 9 ∗ 7 + 10 =