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。