← Back to case studies
CVE-2025-24813 CVSS 9.8 Critical Java · Apache Tomcat

Apache Tomcat Partial PUT RCE — Path Equivalence Meets Unsafe Deserialization

Tomcat's DefaultServlet executePartialPut() converts request paths into temp filenames via path.replace('/', '.') — creating a path equivalence issue (CWE-44). Combined with Tomcat's file-based session persistence, an attacker can upload a malicious serialized Java object and trigger RCE.

CVSS 9.8 / 10.0
CWE CWE-44 + CWE-502
Package Apache Tomcat (<9.0.99, <10.1.35, <11.0.3)
Published March 10, 2025
Fixed 9.0.99 / 10.1.35 / 11.0.3
KEV April 1, 2025 — actively exploited
// what happened
  • Affected: Apache Tomcat 9.0–9.0.98, 10.1–10.1.34, 11.0–11.0.2 — open-source Java servlet container used by millions of Java web applications globally. Widely deployed in enterprise, government, and cloud environments.
  • Attack vector: DefaultServlet.executePartialPut() creates temp files using new File(tempDir, path.replace('/', '.')). This encodes the request URL into the temp filename, enabling path traversal. An attacker can upload a malicious serialized Java session file and trigger deserialization by referencing the session ID in a GET request.
  • Requires: Write-enabled DefaultServlet + partial PUT feature + file-based session persistence + deserialization gadget (e.g. Commons-Collections 3.x) on classpath. All common in enterprise configs.
  • Impact: Pre-auth RCE — no credentials needed if the vulnerable config is exposed. Full server compromise when a deserialization gadget is available.
  • Fix: Use File.createTempFile("put-part-", null, tempDir) for cryptographically random temp filenames + explicit cleanup in a finally block. Fix commit: apache/tomcat@0a668e0c.

Base Score
9.8 Critical
Attack Vector (AV)
Network — HTTP PUT/GET requests, no local access required
Attack Complexity (AC)
Low — single crafted PUT request, no authentication needed
Privileges Required (PR)
None — pre-auth RCE, no credentials required
User Interaction (UI)
None — victim doesn't need to interact
Scope (S)
Changed — RCE crosses application boundary to system
Confidentiality (C)
High — full server compromise, read all files and secrets
Integrity (I)
High — arbitrary file write, webshell deployment
Availability (A)
High — RCE = full system takeover, process control
Vector String
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H → 9.8
CVSS v4.0
AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H → 9.2

org/apache/catalina/servlets/DefaultServlet.java — Tomcat <9.0.99 / <10.1.35 / <11.0.3 executePartialPut() in DefaultServlet — creates temp files using path.replace('/', '.') // VULNERABLE CODE: executePartialPut() stages partial PUT content in a temp file // The temp filename encodes the request path, creating a path equivalence issue (CWE-44) // Attacker can traverse directories by manipulating the path parameter // Path equivalence: /uploads/file.txt → ./uploads.file.txt // An attacker crafting path=..%2F..%2F..%2Fsessions/SESSION_ID..session // gets the temp file written to the sessions directory as SESSION_ID..session private void executePartialPut(HttpServletRequest req, HttpServletResponse resp, Resource resource) throws IOException { // ... validation code ... // VULNERABLE: temp filename encodes the request URL path // path.replace('/', '.') converts "/uploads/file.txt" → "./uploads.file.txt" // This is a CWE-44 path equivalence issue — the filename leaks path information // An attacker controlling 'path' can use ".." sequences to write outside temp dir File tempDir = (File) getServletContext().getAttribute(ServletContext.TEMPDIR); String convertedResourcePath = path.replace('/', '.'); File contentFile = new File(tempDir, convertedResourcePath); // createNewFile() fails if file exists — but doesn't prevent path traversal // The file is deleted on JVM exit (deleteOnExit()) — not cleaned up between requests if (contentFile.createNewFile()) { contentFile.deleteOnExit(); } // ... write content to contentFile, then commit to final path ... }
From partial PUT upload to remote code execution
1
Attacker identifies a Tomcat instance with partial PUT enabled and file-based session persistence. Common in enterprise configs where developers need to support resumable uploads.
2
Attacker crafts a partial PUT request targeting the session directory: PUT /work/..%2Fsessions/SESSION_ID..session. The path traversal (..%2F..%2Fsessions) combined with path.replace('/', '.') writes the serialized gadget chain to {tempDir}/sessions/SESSION_ID..session.
3
When Tomcat deserializes the session file (triggered by a GET request with Cookie: JSESSIONID=SESSION_ID), the magic method chain (__readObject() or similar) fires — executing arbitrary code if Commons-Collections 3.x or another gadget chain is on the classpath.
4
Attacker achieves RCE with Tomcat process privileges. From here they read $CATALINA_HOME/conf/catalina.properties for secrets, deploy a WAR backdoor, or pivot to adjacent services.

Fix commit apache/tomcat@0a668e0c — two changes
// CHANGE 1: Replace path-encoded temp filename with cryptographically random name // File.createTempFile() uses Random.nextLong() — no path traversal possible File tempDir = (File) getServletContext().getAttribute(ServletContext.TEMPDIR); - String convertedResourcePath = path.replace('/', '.'); - File contentFile = new File(tempDir, convertedResourcePath); - if (contentFile.createNewFile()) { - contentFile.deleteOnExit(); - } + File contentFile = File.createTempFile("put-part-", null, tempDir); // CHANGE 2: Explicit cleanup in finally block — not just deleteOnExit() // Ensures files are deleted between requests, not only on JVM shutdown try { // ... write content to contentFile ... } finally { if (tempContentFile != null) { tempContentFile.delete(); } }

Why this works: File.createTempFile("put-part-", null, tempDir) uses a cryptographically random suffix — an attacker cannot predict or control the filename. The explicit delete() in a finally block ensures cleanup happens immediately after the request, not only on JVM shutdown. Together, these changes eliminate both the path equivalence attack surface and the stale-file persistence issue.


🔴 PullLight — Critical Finding
[CRITICAL] Path-dependent temp file creation in DefaultServlet partial PUT handler

executePartialPut() creates temp files using new File(tempDir, path.replace('/', '.')) — the filename encodes the request path. This is a path equivalence issue (CWE-44): an attacker controlling the path can traverse directories or overwrite sensitive files.

Additionally, createNewFile() + deleteOnExit() does not guarantee cleanup — files persist on disk between requests. This enables the session-deserialization attack chain: upload a malicious serialized object to the session directory, then trigger deserialization via a GET request with the matching JSESSIONID.

This is CWE-44 (Path Equivalence) + CWE-502 (Deserialization of Untrusted Data) with a CVSS 9.8 rating. Requires only that partial PUT is enabled and file-based session persistence is configured.
→ Fix: Use File.createTempFile("put-part-", null, tempDir) for a cryptographically random name. Add explicit delete() in a finally block. Fix commit: apache/tomcat@0a668e0c.

// root cause
DefaultServlet.executePartialPut() uses path.replace('/', '.') to construct a temp filename from the request URL. This is a CWE-44 path equivalence: the filename is derived from external input, enabling an attacker to manipulate the path to write outside the intended temp directory. Combined with Tomcat's file-based session persistence (which deserializes session files on demand), an attacker can plant a malicious serialized object in the sessions directory and trigger RCE.

The secondary issue: createNewFile() + deleteOnExit() does not guarantee cleanup between requests. Stale temp files can accumulate on disk, creating a window for race-condition attacks. The fix uses File.createTempFile() for a random name and explicit delete() in a finally block — guaranteeing cleanup after each request.

// what you can do

Are default Tomcat installations vulnerable?
Mostly no. readonly="true" is the default on DefaultServlet. Enterprises enabling write for file upload functionality create the vulnerable config. If you have readonly="false" and allowPartialPut="true", you may be exposed.
How to check my Tomcat version?
java -cp catalina.jar org.apache.catalina.util.ServerInfo. Vulnerable: 9.0.0-M1 through 9.0.98, 10.1.0-M1 through 10.1.34, 11.0.0-M1 through 11.0.2.
Is this being actively exploited?
Yes. CISA added it to KEV on April 1, 2025. Active exploitation observed since March 12, 2025 — just two days after disclosure.
Can a WAF stop this?
Partially. Block unusual PUT requests and .. sequences. Patching is the real fix — WAF rules can be bypassed.

March 10, 2025 Apache Tomcat security advisory published. Versions 9.0.99, 10.1.35, and 11.0.3 released with patch. CVE-2025-24813 assigned. Fix commit apache/tomcat@0a668e0c lands.
March 12, 2025 Active exploitation observed in the wild — just 2 days after disclosure.
April 1, 2025 CISA adds CVE-2025-24813 to Known Exploited Vulnerabilities (KEV) catalog.
June 22, 2026 PullLight documents CVE-2025-24813 as case study #16 — demonstrating path equivalence detection via temp file analysis in PR code review.

Don't let path-dependent temp files land in your PRs

More CVE Case Studies