Apache Tomcat, colloquially known as Tomcat Server, is an open-source Java Servlet container developed by a community with the support of the Apache Software Foundation (ASF). It implements several Java EE specifications, including Java Servlet, JavaServer Pages (JSP), Java Expression Language (EL), and WebSocket, and provides a "pure Java" HTTP web server environment in which Java code can run.
On April 15, Nightwatch Cybersecurity published information on CVE-2019-0232, a remote code execution (RCE) vulnerability involving Apache Tomcat’s Common Gateway Interface (CGI) Servlet. This high severity vulnerability could allow attackers to execute arbitrary commands by abusing an operating system command injection brought about by a Tomcat CGI Servlet input validation error. This blog entry delves deeper into this vulnerability by expounding on what it is, how it can be exploited, and how it can be addressed.
The CGI is a protocol that is used to manage how web servers interact with applications. These applications, called CGI scripts, are used to execute programs external to the Tomcat Java virtual machine (JVM). The CGI Servlet, which is disabled by default, is used to generate command line parameters generated from a query string. However, Tomcat servers running on Windows machines that have the CGI Servlet parameter enableCmdLineArguments enabled are vulnerable to remote code execution due to a bug in how the Java Runtime Environment (JRE) passes command line arguments to Windows.
In Apache Tomcat, the file web.xml is used to define default values for all web applications loaded into a Tomcat instance. The CGI Servlet is one of the servlets provided as default. This servlet supports the execution of external applications that conform to the CGI specification. Typically, the CGI Servlet is mapped to the URL pattern “/cgi-bin/*”, meaning any CGI applications that are executed must be present within the web application.
A new process in Windows OS is launched by calling the CreateProcess() function, which takes the following command line as a string (the lpComandLine parameter to CreateProcess):
- int CreateProcess( ..., lpComandLine, ... )
In Windows, arguments are not passed separately as an array of strings but rather in a single command-line string. This requires the program to parse the command line itself by extracting the command line string using GetCommandLine() API and then parsing the arguments string using CommandLineArgvW() helper function.
This is depicted in the flowchart shown below:
Cmdline = “program.exe hello world”
Figure 1. Command line string for Windows
The vulnerability occurs due to the improper passing of command line arguments from JRE to Windows.
For Java applications, ProcessBuilder() is called before CreateProcess() function kicks in. The arguments are then passed to the static method start of ProcessImpl(), which is a platform-dependent class. In the Windows implementation of ProcessImpl(), the start method calls the private constructor of ProcessImpl(), which creates the command line for the CreateProcess call.
Figure 2. Command line string for Java apps
ProcessImpl() builds the Cmdline and passes it to the CreateProcess() Windows function, after which CreateProcess() executes the .bat and .cmd files in a cmd.exe shell environment.
If the file that is to be run contains a .bat or .cmd extension, the image to be run then becomes cmd.exe, the Windows command prompt. CreateProcess() then restarts at Stage 1, with the name of the batch file being passed as the first parameter to cmd.exe.
This results in a 'hello.bat …' becoming 'C:\Windows\system32\cmd.exe /c "hello.bat …"'. Because the quoting rules for CommandLineToArgvW differ from those of cmd’s, this means that an additional set of quoting rules would need to be applied to avoid command injection in the command line interpreted by cmd.exe.
Since Java (ProcessImpl()) does no additional quoting for this implicit cmd.exe call promotion on the passed arguments, arguments processed by cmd.exe is now used to execute, presenting inherent issues if arguments are not passed to cmd.exe properly.
Argument parsing by cmd.exe
We begin with the understanding that cmd is essentially a text preprocessor: Given a command line, it makes a series of textual transformations then hands the transformed command line to CreateProcess().
Some transformations replace environment variable names with their values. Transformations such as those triggered by the &, ||, && operators, split command lines into several parts. All of cmd’s transformations are triggered by the presence of one of the following metacharacters: (, ), %, !, ^, ", <, >, &, and |.
The metacharacter " is particularly interesting: When cmd is transforming a command line and sees a ", it copies a " to the new command line then begins copying characters from the old command line to the new one without seeing whether any of these characters is a metacharacter. This continues until cmd either reaches the end of the command line, runs into a variable substitution, or sees another ".
If we rely on cmd’s "-behavior to protect arguments, using quotation marks will produce unexpected behavior. By passing untrusted data as command line parameters, the bugs caused by this convention mismatch become a security issue.
Take for example, the following:
hello.bat “dir \”&whoami”
Here, cmd is interpreting the & metacharacter as a command separator because, from its point of view, the &character lies outside the quoted region. In this scenario, 'whoami’ can be replaced by any number of harmful commands. When running the command shown above with hello.bat, we get the following output.
Figure 3. The resulting output when running “hello.bat”
The issue shown in the screenshot is used in Apache Tomcat to successfully perform command execution, which is shown in the following image:
Figure 4. Performing command execution in Apache Tomcat
To successfully perform command injection, we need to add a few parameters and enable CGI Servlet in the web.xml file.
Figure 5. Snapshot of web.xml
The Apache Software Foundation has introduced a new parameter, cmdLineArgumentsDecoded, in Apache Tomcat CGI Servlet that is designed to address CVE-2019-0232. cmdLineArgumentsDecoded is only used when enableCmdLineArguments is set to true. It defines a regex pattern “[[a-zA-Z0-9\Q-_.\\/:\E]+]” that individual decoded command line arguments must match or else the request will be rejected. The introduced patch will eliminate the vulnerability that arises from using spaces and double quotes in command line arguments.
Figure 6. The Apache Tomcat patch, which can be found in the codebase
Recommendations and Trend Micro Solutions
Apache Software Foundation recommends that users running Apache Tomcat upgrade their software to the latest versions:
|Apache Tomcat 9||Apache Tomcat 9.0.18 or later|
|Apache Tomcat 8||Apache Tomcat 8.5.40 or later|
|Apache Tomcat 7||Apache Tomcat 7.0.93 or later|
Furthermore, users should set the CGI Servlet initialization parameter enableCmdLineArguments to false to prevent possible exploitation of CVE-2019-0232.
Developers, programmers, and system administrators using Apache Tomcat can also consider multilayered security technology such as Trend Micro™ Deep Security™ and Vulnerability Protection solutions, which protect user systems from threats that may exploit CVE-2019-0232 via the following Deep Packet Inspection (DPI) rule:
- 1009697 - Apache Tomcat Remote Code Execution Vulnerability (CVE-2019-0232)
- 315387 - HTTP: Apache Tomcat Remote Code Execution on Windows