解析 `.tar/.zip/.phar` 文件时, 堆边界条件控制不严,导致可能`堆溢出`.
新建一个空文件"aaaa"(0 byte), 打包成 "aaaa.tar"文件,未压缩前`aaaa`的文件大小为`0`。通过`PharFileInfo`对象的`getContent()`方法获取`aaaa`文件的内容,例如: `var_dump($phar['aaaa']->getContent());`
查看`getContent`内部实现源码如下:
```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` 文件大小为`0`, 所以传递给`php_stream_copy_to_mem`函数进行处理,如下:
```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);
...
}
```
从上面代码可以看出, `If maxlen == 0`, 它将返回 0, 这是ok的, 但是该函数的第二个参数`char **buf`, 将在堆上分配一个空间,从当前文件指针处读数据。
现在返回上一层函数进行分析, 变量 zval `return_value` 是未初始化的变量, `return_value->str.val`将是一个指针,指向之前调用函数分配的堆地址所指向的空间, gdb调试如下:
```
=> 0x817aacc <zim_PharFileInfo_getContent+300>: call 0x8267ad0 <_php_stream_copy_to_mem>
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
Guessed arguments:
arg[0]: 0xf7bd9a04 --> 0x8806a40 --> 0x826cad0 (<php_stdiop_write>: 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);
```
从上面可以看出当前`return_value->str.val`就是`0xf7bdd56c`.
正如之前所分析的那样,`maxlen==0`,`_php_stream_copy_to_mem`返回0这是ok的, 但是调用该函数之后,参数`*buf` => `return_value->str.val`保留了函数调用过程中的数据,如下:
```
=> 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.0010109254636237e-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` 仍然保留在`ZVAL(return_value)`.
之后, 这个`str.val`指针将被传递到 `_efree` 去清理 vm stack.
以某种方式, 我能管理`0xf7bdd56c`之前的地址,也绝对能管理 `mm_block(ESI register)` 的下一个地址, 如下:
```
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);
}
...
```
我们能接管它的下一个地址和进一步控制堆
暂无评论