1. 简介
glibc(GNU C Library)是 GNU 项目的一部分,主要为基于 Unix 的系统(如 Linux)提供 C 标准库和底层系统调用接口。
CVE-2024-3961 是 @cfreal_ 在对 PHP filters协议(用于对文件流或字符串流进行操作。通过该伪协议,开发者可以在文件读取或写入时对数据进行转换或过滤。)进行模糊测试时发现的一个影响 GNU C 库(glibc) 的严重安全漏洞,其在将字符串转换为 ISO-2022-CN-EXT 字符集时,GNU C 库版本 2.39 及更早版本中的 iconv() 函数可能会使传递给它的输出缓冲区溢出最多 4 个字节,这可能导致应用程序崩溃或覆盖相邻变量。
在PHP中可能通过CVE-2024-3961漏洞将文件读取原语提升到远程代码执行,简单来说可以构造php://filter的数据使PHP在转换ISO-2022-CN-EXT字符集时触发CVE-2024-3961完成利用。本文对@cfreal_ 的博客中提到的部分场景进行复现:
在博客中第一部分是针对带文件读取回显的RCE利用,第三部分是在没有文件读取回显的情况下读取文件或RCE。
2. 文件读取回显RCE
测试环境可以直接使用vulhub/php/CVE-2024-2961 at master · vulhub/vulhub。但如果自己搭建环境,使用ubuntu作为基础镜像nginx和php7.3.22环境,会出现对CVE-2024-2961利用失败情况,也微调了一些版本进行测试,但都测试失败(通过之前的一个环境测试成功了,想将该环境精简一下,遇到了这个问题,可能需要手动调整二进制漏洞利用的相关参数):

这里成功的环境是之前搭建的一个靶场环境,这个环境使用的phpstudy搭建:
wget -O install.sh https://notdocker.xp.cn/install.sh && sudo bash install.sh
查看该环境的glibc版本,可使用ldd --version命令查看:

然后需要编写php的漏洞代码,需要一个读取文件内容进行输出的脚本。在poc中给出了代码提示:

可以编写如下PHP代码然后上传到搭建好的容器中:
<?php echo "File contents: " .file_get_contents($_REQUEST['file']);?>

测试环境已经准备好,再来看POC代码,代码中使用了Python3.10后的语法,相关文件操作路径也是Linux环境,所以最好的环境就是在kali中操作:
$ git clone --recurse-submodules https://github.com/ambionics/cnext-exploits.git
$ pip install -r requirements.txt
环境配置好以后,执行脚本进行测试:
./cnext-exploit.py http://10.1.1.11/1.php "whoami > 1.txt"
执行时遇到了错误[-] Remote.download did not return the test string,错误提示说没有返回测试字符串,在下面添加的打印字符串中,发现返回的字符少了末尾两个字符:

对应的脚本内容如下,脚本会先测试目标对协议的支持情况和zlib扩展是否正常工作:

在Python代码中加入burp代理进行测试:
import socket, socks
#socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 10808) #socks5代理
socks.set_default_proxy(socks.HTTP, "127.0.0.1", 8080) # http代理
socket.socket = socks.socksocket
测试代码会发送下列payload,表示将源数据编码为Base64,源数据来自data:协议,所以这里会先通过data:协议解码数据再使用php://filter对原始数据进行Base64编码,返回的Base64编码数据理论上应该是一致的:
php://filter/convert.base64-encode/resource=data:text/plain;base64,xxxxx
但是在burp中发现php返回的编码数据相对原始数据少了末尾几个字符,末尾的字符带=号,应该是base64的填充字符:

对poc脚本进行检测,发现代码会对随机生成的50个字节进行base64编码,转换函数中对是否存在填充字符进行了判断,但是代码中没有执行到这个逻辑( base64 = b64(text, misalign=True).decode()):

将代码misalign设置False时base64 = b64(text, misalign=True).decode(),就抛出异常,提示说原始字符串编码后存在=,说明需要填充:

所以这里尝试能否控制随机字节的长度避免Base64填充,chatGPT回复:
为了保证两次 Base64 编码后的字符串没有填充字符,第一次编码后的字节数必须是 3 的倍数。第二次编码则会继承这个规则,只要第一次编码后的字节流长度是 3 的倍数,第二次编码的结果就不会有填充字符。
修改对应的代码,发现对data://和php://fillter的测试成功了,但还存在报错:

这个错误是在使用php://filter/zlib.inflate时,按照之前的分析开启了填充字符串的验证,这里是使用zlib进行压缩测试用于后续下载二进制文件,简单把misalign设置为True即可:

修改对应代码后,成功执行了命令:

检测协议支持后,/proc/self/maps定位libc文件:

然后下载libc-2.31.so文件,构造CVE-2024-3961利用:

触发漏洞执行命令:

可以使用echo写入php文件:

3. 盲文件读取利用
当目标函数操作文件内容时,利用 PHP 的流包装器(wrapper)功能,通过构造特殊的 URI,利用 PHP 过滤器链(php://filter)中基于错误的 Oracle 漏洞来读取文件。即使服务器不会直接返回文件内容,只要某些功能处理了文件内容,并且完整的 URI 可控,就可以通过此工具来泄露本地文件内容。具体原理和使用方法可参考 Synacktiv 的博客文章。包括但不限于以下函数:
file()hash_file()file_get_contents()copy()
对于这些函数的利用,可以使用如下两个工具:
- synacktiv/php_filter_chains_oracle_exploit: A CLI to exploit parameters vulnerable to PHP filter chain error based oracle.
- Ambionics/Lightyear:Lightyear 是一种使用 PHP 过滤器在繁琐(盲目)条件下转储文件的工具
lightyear的作者给出了一个docker环境,使用这个环境来测试文件泄露效果:

使用 php_filter_chains_oracle_exploit进行测试:
./filters_chain_oracle_exploit.py --target http://127.0.0.1 --file '/etc/passwd' --parameter file

lightyear工具需要在remote.py中指定目标地址和参数,然后可以执行./lightyear.py test测试目标:

同样尝试泄露/etc/passwd:
./lightyear.py /etc/passwd -o 1.txt
lightyear具有缓存转储功能,如果转储出现了乱码,可以删除输出文件后重新运行:

测试下来,lightyear工具对转储文件输出较为完整。对作者第三部分文章进行复现时,会导致服务器崩溃,没有成功触发RCE:

以下是作者使用的php代码:
<?php
// echo "File contents: " .file_get_contents($_REQUEST['file']);
// 如果未传入 'srand' 参数,则显示当前文件的源代码
if (!isset($_GET['srand'])) {
highlight_file(__FILE__);
exit();
}
// 生成随机大小的分配,并随机释放内存
if ($_GET['srand']) { // DEBUG: 此处仅作测试用
srand($_GET['srand']);
$strs = [];
// 内存分配
for ($i = 0; $i < rand(0, 1000); $i++) {
$strs[] = str_shuffle(str_repeat(chr($i), rand(0, 0xC00 - 0x19))); // 分配字符串
}
// 内存释放
for ($i = 0; $i < rand(0, 1000); $i++) {
$idx = rand(0, count($strs));
if (isset($strs[$idx])) {
unset($strs[$idx]); // 释放随机分配的内存
}
}
}
// 文件校验(基于 MD5 哈希值)
if (md5_file($_REQUEST['file']) === '8f199aebac0036c0c1fa23134eecc3d54') {
echo "Valid file";
} else {
echo "Invalid file";
}
?>
4. 总结
在受到影响的平台上,PHP 的每个标准文件读取方法都会受到影响,例如:file_get_contents()、file()、readfile()、fgets()、getimagesize()、SplFileObject->read() 等。文件写入方法也同样受到影响,包括 file_put_contents() 及其相关方法,这对拓展了一些环境的漏洞利用方式。
但经过搭建环境对该漏洞利用进行测试,对于存在文件读取回显的RCE利用并不总是成功,可能还需要调整二进制漏洞的利用代码。
对于盲文件读取则是利用了 PHP 内置的过滤器和内存限制机制,将其作为一个错误“预言机”。通过反复应用 convert.iconv.L1.UCS-4LE 过滤器,每次使用该过滤器都会将字符串的长度增加 4 倍。如果字符串非空,最终会导致内存限制被突破,从而产生 500 错误。通过这种爆破的方式可以逐字节解析出文件的内容。盲文件读取RCE利用原理中使用到了上述这种字节泄露方式,通过泄露内存地址来获得二进制漏洞利用前提,完成RCE。