前言
今年 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
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!