Smasher
Summary
An attacker scanned the target and found two services: SSH on TCP 22 and a custom 'tiny' web server on TCP 1111.
The web server accepted raw and URL-encoded directory-traversal sequences in request paths, letting any unauthenticated caller read arbitrary files — first revealing two local user accounts in /etc/passwd, then downloading the server's own compiled binary and C source.
Offline analysis of the source exposed a stack buffer overflow in the URL-decoding function with no length check.
An amd64 ROP chain exploited this remotely to write an attacker SSH public key into the www user's home directory, yielding a shell.
From that low-privilege foothold, a service on loopback TCP 1337 encrypted a session token (containing the smasher user's password) with AES-CBC but leaked whether PKCS#7 padding was valid on each decryption attempt.
Iterative queries to this padding oracle recovered the plaintext password, which was used directly to SSH in as smasher and capture the user flag.
Finally, the SUID-root binary /usr/bin/checker checked file ownership by filename, then opened the same file in a separate step a fraction of a second later.
Replacing the file with a symlink to /root/root.txt between the check and the open caused the privileged binary to print the root flag — full system compromise without a root shell.
Attack path — how the box was taken
Mapped exposed services on the target, then Exploited path traversal on the web server to read /etc/passwd and map the local users, then Exfiltrated the server binary, source code, and system libc via the same path traversal, then Exploited the URL-decoder stack buffer overflow with an amd64 ROP chain to gain a shell as www, then Queried a CBC padding oracle on loopback TCP 1337 to recover the smasher user's SSH password, then Authenticated as smasher over SSH using the oracle-recovered password and captured the user flag, then Exploited a TOCTOU symlink race in the SUID-root checker binary to read the root flag.
Exact commands 2
nmap -Pn -sV --open -p 22,1111 $TARGETcurl -si http://$TARGET:1111/index.htmlExact commands 2
curl -s -i --path-as-is "http://$TARGET:1111/../../../../etc/passwd"curl -s -i --path-as-is "http://$TARGET:1111/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"Exact commands 2
mkdir -p /tmp/smasher/exploitcurl -sS --path-as-is "http://$TARGET:1111/../../../../home/www/tiny-web-server/tiny" -o /tmp/smasher/exploit/tinyExact commands 2
python3 /tmp/smasher/exploit_key.pyssh -i /tmp/smasher/wwwkey -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'id; pwd'Exact commands 2
ssh -i /tmp/smasher/wwwkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'ss -tlnp | grep 1337'ssh -i /tmp/smasher/wwwkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'python3 /tmp/padding_oracle.py 127.0.0.1 1337'Attack patterns used
The transferable techniques behind the compromise.
Path traversal / Local File InclusionEnumerationT1083
What it is
The tiny web server failed to sanitize '../' sequences and their URL-encoded equivalents (%2e%2e) before resolving request paths on the filesystem. A traversal request read /etc/passwd without any authentication, exposing every system account. Two interactive users were confirmed: www (uid 1000, home /home/www, running the web server) and smasher (uid 1001, home /home/smasher). Additional traversal requests retrieved /etc/rc.local and /home/www/restart.sh, confirming the www user owns and periodically restarts the tiny process.
Why it works
Fully decode and lexically normalize every request path before resolving it against the document root. Reject any normalized path that does not begin with the intended document root (a 'jail' check — return 403 if the resolved path escapes the root). Place sensitive files such as binaries, source code, configuration, and system files outside the web root entirely. Run the server process as a dedicated, minimally privileged account with read access restricted to the document root only.
Stack buffer overflow with amd64 ROP / ret2libcExploitationT1190
What it is
The url_decode() function copied URL-decoded bytes into a fixed stack buffer without bounding the output length. An oversized HTTP request overwrote the saved return address. The exploit ran in two stages: a stage-one ROP chain leaked a libc runtime address from the GOT over the open client socket to defeat ASLR, then a stage-two ret2libc payload called system() to append an attacker-controlled SSH public key to /home/www/.ssh/authorized_keys. The server process then restarted (as per restart.sh), and a matching private key established a shell as www.
Why it works
Enforce a hard bound on the url_decode() output: either validate that decoded output never exceeds the buffer capacity and return an error if it would, or allocate the output buffer dynamically to the input length. Compile the binary with stack-canary protection (-fstack-protector-strong), non-executable stack (NX/XD), position-independent executable (-fPIE -pie), and full RELRO (-Wl,-z,relro,-z,now) to raise the cost of exploitation even if a new overflow is introduced.
CBC padding oracle attackCredential AccessT1110
What it is
Post-foothold enumeration as www revealed a second service listening on loopback TCP 1337. The service encrypted a session blob — containing the smasher user's SSH password — with AES-CBC and returned different observable responses depending on whether the PKCS#7 padding of a submitted ciphertext was valid. By systematically flipping bytes in the ciphertext and watching the response, the attacker decrypted the token block by block without ever knowing the key, recovering the plaintext password: [REDACTED: recovered credential].
Why it works
Switch to an authenticated encryption scheme such as AES-256-GCM or ChaCha20-Poly1305, which provide both confidentiality and ciphertext integrity and eliminate padding as a concept. If CBC is retained for legacy reasons, apply Encrypt-then-MAC (compute an HMAC over the ciphertext and verify it in constant time before any decryption attempt), ensuring all error paths return identical responses and take identical time. Credentials must never be stored in a form accessible to low-privilege processes; consider a privilege-separated credential store instead.
SUID binary TOCTOU symlink racePrivilege EscalationT1548.001
What it is
The binary /usr/bin/checker ran with the SUID bit set, giving it root effective privileges. It accepted a filename argument, verified the calling user owned that file (the 'check' step), then opened and printed the file's contents (the 'use' step) — two separate filesystem operations with a brief window between them. The attacker created a legitimate file they owned, launched checker against it in the background, waited approximately 0.5 seconds for the ownership check to pass, then atomically replaced the file with a symlink pointing to /root/root.txt. When checker performed the open() a moment later, it followed the symlink and printed the root flag — reading a file the calling user has no direct access to, while the process ran as root.
Why it works
Open the file with a single open() call, then verify ownership on the returned file descriptor using fstat() — never re-resolve the filename after the initial open. Alternatively, drop the SUID effective UID to the calling user's UID immediately after the open(), so even a successful race only permits the caller to read files they already own. If the binary's purpose can be served another way (a privileged daemon with a well-defined API), remove the SUID bit entirely.
Findings
Exposed services
| External surface | A port scan of <retired-instance-ip> revealed two open TCP services: OpenSSH 7.6p1 on port 22 and an unidentified HTTP service on port 1111. Banner and content analysis confirmed port 1111 served the open-source shenfeng tiny web server. SSH offered no unauthenticated attack surface, so focus shifted to the custom web server. |