php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #73185 Buffer overflow in HTTP parse_hostinfo()
Submitted: 2016-09-27 13:19 UTC Modified: 2017-02-13 01:16 UTC
From: hlt99 at blinkenshell dot org Assigned: mike (profile)
Status: Closed Package: pecl_http (PECL)
PHP Version: master-Git-2016-09-27 (Git) OS: Linux
Private report: No CVE-ID: 2016-7961
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: hlt99 at blinkenshell dot org
New email:
PHP Version: OS:

 

 [2016-09-27 13:19 UTC] hlt99 at blinkenshell dot org
Description:
------------
The parsing functions of the PECL HTTP extension allow overflowing
a buffer with data originating from an arbitrary HTTP request. Affected
is the `parse_hostinfo()` function in php_http_url.c that is called when
instantiating/initializing an HTTP message object.
The problem occurs because in the main processing loop `char *ptr` may
get incremented past the corresponding end pointer `char *end` used as
the end marker. Thus the parser loop may continue to execute and buffer
`state->buffer` may overflow.  

Relevant code snippet from php_http_url.c:1096:

    static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *ptr)
    {
    [...]
        if (ptr != end) do {
        	switch (*ptr) {
                [...]
                case '0': case '1': case '2': case '3': case '4': case '5': case '6':
                case '7': case '8': case '9':
                    /* allowed */
                    if (port) {
                        state->url.port *= 10;
                        state->url.port += *ptr - '0';
                    } else {
                        label = ptr;
           				state->buffer[state->offset++] = *ptr;
                    }
                    break;
                [...]
                default:
                [...]
                    } else if (!(mb = parse_mb(state, PARSE_HOSTINFO, ptr, end, tmp, state->flags & PHP_HTTP_URL_SILENT_ERRORS))) {
                        if (!(state->flags & PHP_HTTP_URL_IGNORE_ERRORS)) {
                            return FAILURE;
                        }
                        break;
                    }
                    label = ptr;
                    ptr += mb - 1;  // ptr increased here as in various other locations
            }
        } while (++ptr != end);     // ptr pre-incremented + check condition may be missed!
    [...]


# Security impact and PoC

Since this bug allows to overwrite quite large parts of memory, arbitrary
code execution seems very likely. In [1] you'll find a malformed HTTP
request that demonstrates the issue:

    $ cat http_message_parse.php
    /*
            http_message_parse.php
            005-bugXXXXX.bin:
            http://hlt99.blinkenshell.org/php/005-bugXXXXX.bin
    */
    <?php
    	$http_msg = new http\Message(file_get_contents("005-bugXXX.bin"), false);
    ?>

    $ ./configure --enable-raphf --enable-propro --with-http && make
    $ gdb ./sapi/cli/php
    gdb> r http_message_parse.php
    [...]
    Fatal error: Uncaught http\Exception\BadMessageException: http\Message::__construct(): Could not parse HTTP protocol version 'HTTP/1.rdrd-vvv5:##HT
    [...] // garbled output
    85:#~t? HTT in http_message_parse.php on line 7
    
    Program received signal SIGSEGV, Segmentation fault.
    0x00000000006d6ef3 in _php_stream_free (stream=<optimized out>, close_options=11)
        at /home/rc0r/tmp/php-src/main/streams/streams.c:467
    467			ret = stream->ops->close(stream, preserve_handle ? 0 : 1);
    gdb> i r
    rax            0x4142434445464748	4702394921427289928
    rbx            0xb	11
    rcx            0x1	1
    rdx            0x0	0
    rsi            0x1	1
    rdi            0x7ffff42ad300	140737289835264
    rbp            0x7ffff42ad300	0x7ffff42ad300
    rsp            0x7fffffffb150	0x7fffffffb150
    r8             0x0	0
    r9             0x1	1
    r10            0x3d3	979
    r11            0x7ffff58ad760	140737312905056
    r12            0x0	0
    r13            0x1	1
    r14            0x0	0
    r15            0x0	0
    rip            0x6d6ef3	0x6d6ef3 <_php_stream_free+307>
    eflags         0x10202	[ IF RF ]
    cs             0x33	51
    ss             0x2b	43
    ds             0x0	0
    es             0x0	0
    fs             0x0	0
    gs             0x0	0
    gdb> x/i $rip
    => 0x6d6ef3 <_php_stream_free+307>:	callq  *0x10(%rax)

The attempt to parse the supplied HTTP request fails at some point
and used resources are freed by the extension. It was possible to
overwrite a `php_stream` structure including its pointer to a
`php_stream_ops` structure containing function pointers that are
about to be called from within `_php_stream_free()` as shown above.
Register `rax` contains data from the malformed HTTP request starting
at offset 0x2139. 

[1] http://hlt99.blinkenshell.org/php/005-bugXXXXX.bin


# Patch

After careful review by the project maintainers the following patch may be used
to fix the reported issue.  

    From ec2d2e1648127b2a0bb15f10144daca59bc6f03c Mon Sep 17 00:00:00 2001
    From: rc0r <hlt99@blinkenshell.org>
    Date: Mon, 26 Sep 2016 21:34:29 +0200
    Subject: [PATCH] Buffer overflow in parse_hostinfo() fixed
    
    ---
     src/php_http_url.c | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/src/php_http_url.c b/src/php_http_url.c
    index 2332fb5..70e4c2c 100644
    --- a/src/php_http_url.c
    +++ b/src/php_http_url.c
    @@ -1107,7 +1107,7 @@ static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *pt
        }
     #endif
     
    -	if (ptr != end) do {
    +	if (ptr < end) do {
            switch (*ptr) {
            case ':':
                if (port) {
    @@ -1235,7 +1235,7 @@ static ZEND_RESULT_CODE parse_hostinfo(struct parse_state *state, const char *pt
                label = ptr;
                ptr += mb - 1;
            }
    -	} while (++ptr != end);
    +	} while (++ptr < end);
     
        if (!state->url.host) {
            len = state->offset - len;
    -- 
    2.10.0




Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-09-27 14:22 UTC] hlt99 at blinkenshell dot org
The same problem occurs in `parse_userinfo()`.

From cb3088a7449716d4cd53ac6c14e07eb7b66996b3 Mon Sep 17 00:00:00 2001
From: rc0r <hlt99@blinkenshell.org>
Date: Tue, 27 Sep 2016 16:18:10 +0200
Subject: [PATCH 2/2] Buffer overflow in parse_userinfo() fixed

---
 src/php_http_url.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/php_http_url.c b/src/php_http_url.c
index 70e4c2c..e82ad97 100644
--- a/src/php_http_url.c
+++ b/src/php_http_url.c
@@ -846,7 +846,7 @@ static ZEND_RESULT_CODE parse_userinfo(struct parse_state *state, const char *pt
 			break;
 
 		}
-	} while(++ptr != end);
+	} while(++ptr < end);
 
 
 	state->buffer[state->offset++] = 0;
-- 
2.10.0
 [2016-09-28 07:37 UTC] remi@php.net
-Assigned To: +Assigned To: mike
 [2016-10-04 15:50 UTC] mike@php.net
-Status: Assigned +Status: Closed -Type: Security +Type: Bug
 [2016-10-05 06:26 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2016-7961
 [2016-10-05 06:29 UTC] mike@php.net
-Type: Bug +Type: Security
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Oct 06 13:01:27 2024 UTC