前言

今年 5 月份看到的 bug,当时试了一下可以用来绕过 PHP 7.4.3 版本的 disable_functions,本来打算搞在校赛里水一题的。

但是校赛时间离那时太久了,现在有了更丝滑的,出的题目也更新换代了,这个题目也就没用了,所以就写篇文章总结一下吧。


BUG

bug 发生在 php 底层的 convert_libmagic_pattern 函数中:

t = zend_string_alloc(len * 2 + 4, 0);

ZSTR_VAL(t)[j++] = '~';

for (i = 0; i < len; i++, j++) {
    switch (val[i]) {
    case '~':
        ZSTR_VAL(t)[j++] = '\\';
        ZSTR_VAL(t)[j] = '~';
        break;c
    case '\0':
        ZSTR_VAL(t)[j++] = '\\';
        ZSTR_VAL(t)[j++] = 'x';
        ZSTR_VAL(t)[j++] = '0';
        ZSTR_VAL(t)[j] = '0';
        break;
    ...
    }
...

函数对输入字符做了转换然后放到一个新的内存块里,但是申请内存的时候默认将每个字符转换后的长度上限认定为 2,而在实际的转换过程中,却将 \x00 转换为了 4 个字符,这就造成缓冲区溢出,数据从这个 php 堆块覆盖到了下一个 php 堆块里。

利用

参考给出的触发代码,设置自定义类的属性数量和 \x00 的数量,让自定义对象能够被分配到溢出堆块的正上方,从而达到类似 UAF 的效果。

之后的做法就跟前面写过的 UAF 做法一致了,只不过在尝试修改字符串写内存的时候,会出现一个错误:

Fatal Error: Allowed memory size of xxxxxx bytes exhausted

原因就是 php 有一个写时拷贝,修改字符串的时候会检查它的引用计数,如果该字符串的引用计数 >0,则会进行拷贝,而由于之前缓冲区溢出覆盖自定义对象的时候,将长度和引用计数都覆盖成了一个较大的数,所以写入时就会导致这个申请内存失败的错误。要解决这个问题也很简单,找到输入字符中对应引用计数的地方,将引用计数覆盖为 FFFFFFFF,然后用等号复制一次,通过整数溢出就可以将引用计数变为 0。


Orz


Web PHP PHPUAF

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Fastjson反序列化机制和autotype观测
struts2系列漏洞 S2-057