Mon, 12 Mar 2007


Since PHP 5.2.0 there is not only the new filtering extension in PHP but also a new memory manager that allows exploiting the following ext/filter vulnerability.

When an all whitespace string is passed to some of the filter functions like FILTER_VALIDATE_INT this can result in a buffer underflow that allows remote code execution on atleast big endian systems like MacOS X on PPC.

Affected versions

Affected is PHP 5.2.0

Detailed information

PHP 5.2.0 comes with a brand new memory manager that is no longer a simple wrapper around malloc()/free() but implements a own heap implementation for the request memory pool. The new heap manager stores control information inbound and is therefore vulnerable to overflow attacks. Additionally it is unlike the previous memory manager vulnerable against one byte underflows.

When filters like FILTER_VALIDATE_INT, which is most probably the most often used filter, are used they first perform a whitespace trimming on the incoming user input. This is performed by the following macro.

#define PHP_FILTER_TRIM_DEFAULT(p, len, end) { \
        while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\v') { \
                p++; \
                len--; \
        } \
        start = p; \
        end = p + len - 1; \
        if (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\v') { \
                unsigned int i; \
                for (i = len - 1; i >= 0; i--) { \
                        if (!(p[i] == ' ' || p[i] == '\t' || p[i] == '\r' || p[i] == '\v')) { \
                                break; \
                        } \
                } \
                i++; \
                p[i] = '\0'; \
                end = p + i - 1; \
                len = (int) (end - p) + 1; \
        } \

The trimming macro first moves the start pointer forward as long there is whitespace and then does the same with the end pointer while moving backward. The string is then terminated by writing a single NULL byte to the end. Unfortunately the trimming macro does not work correctly on all whitespace strings, because the move backward does not stop at the beginning of the string. Therefore the trimming operation will write the NULL byte infront of the allocated buffer when the bytes before the buffer start contain ASCII characters belonging to the whitespace charset.

The new memory manager stores the size of the previous memory block infront of the buffer. On a little endian system it is therefore possible but unrealistic that the byte infront of the buffer contains a whitespace character. However on big endian systems like PPC it is possible for a remote attacker to create a heap layout that results in a whitespace character infront of the buffer. The trimming function will overwrite it with a NULL byte. The control information is therefore corrupted and a standard attack against the unlink from the linked list of free blocks is possible, which can result in remote code execution as demonstrated by the POC.

$ gdb ./php
(gdb) run MOPB-19-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-19-2007.php
Reading symbols for shared libraries . done
Using offsets 55555555 and 66666666

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x55555561
_zend_mm_free_int (heap=0x2000400, p=0xcbad90) at /Users/Benutzer/php-5.2.0/Zend/zend_alloc.c:480
480             prev->next_free_block = next;

This shows the typical crash inside the linked freelist unlink. By using good offsets it is possible to execute arbitrary code. The demonstration exploit works locally and can therefore determine the needed offsets automatically through the substr_compare() information leak vulnerability. This looks like this.

$ gdb ./php
(gdb) run MOPB-19-2007.php
Starting program: /Users/Benutzer/php-5.2.0/sapi/cli/php MOPB-19-2007.php
Reading symbols for shared libraries . done
Using offsets 00cd46e0 and 00cd16a0
Program received signal SIGINT, Interrupt.
0x00cd47a4 in ?? ()
(gdb) x/20x $pc
0xcd47a4:       0x44000002      0x7c000278      0x7c7e1b78      0x38a00002
0xcd47b4:       0x3800005a      0x7fc3f378      0x7ca42b78      0x44000002
0xcd47c4:       0x7c000278      0x38a5ffff      0x2c05ffff      0x4082ffe5
0xcd47d4:       0x38000042      0x44000002      0x7c000278      0x7ca52a79
0xcd47e4:       0x4082fffd      0x7c6802a6      0x38630028      0x9061fff8
(gdb) x/5i $pc
0xcd47a4:       sc
0xcd47a8:       xor     r0,r0,r0
0xcd47ac:       mr      r30,r3
0xcd47b0:       li      r5,2
0xcd47b4:       li      r0,90
(gdb) bt
#0  0x00cd47a4 in ?? ()
#1  0x00cd475c in ?? ()
warning: Previous frame identical to this frame (corrupt stack?)
#2  0x0020ffd8 in _zval_dtor_func (zvalue=0xb) at /Users/.../.../Zend/zend_variables.c:43
#3  0x00204144 in _zval_ptr_dtor (zval_ptr=0xcbb72c) at /Users/.../.../Zend/zend_variables.h:35
(gdb) continue

This example shows a successfull code execution exploit that currently waits for someone to connect to port 4444.

Proof of concept, exploit or instructions to reproduce

The attached exploit demonstrates locally that the buffer underflow is indeed exploitable and allows code execution on the demonstration platform MacOS X on PPC.

The exploit uses the substr_compare() information leak vulnerability to determine what offsets to overwrite. In a real remote exploit these offsets need to be either bruteforced or determined by some other means (like a remote information leak).

When successfull the attached exploit will spawn a shell on port 4444. Shellcode was borrowed from the Metasploit shellcode generator.


This remote vulnerability in PHP's new "security" feature was fixed with the PHP 5.2.1 update, after we disclosed it to the vendor.

As usual the vendor more or less hides this serious security vulnerability from PHP users by only mentioning it as "Fixed a number of input processing bugs inside the filter extension." in the release notes. We strongly believe that a responsible vendor must act in a different way.