← all walkthroughs

Smasher

Linux· Insane· Credential Access· Privilege Escalation
owned
2026-07-12
time to own
20m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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.

1ReconnaissanceNetwork port scanning and service fingerprinting (T1046)
Mapped exposed services on the target
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.
Exact commands 2
Fingerprint the two exposed services.
nmap -Pn -sV --open -p 22,1111 $TARGET
Confirm the port 1111 service serves HTTP and identify any server headers.
curl -si http://$TARGET:1111/index.html
2EnumerationPath traversal / Local File Inclusion (CWE-22, T1083)
Exploited path traversal on the web server to read /etc/passwd and map the local users
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.
curl --path-as-is 'http://<retired-instance-ip>:1111/../../../../etc/passwd' returned HTTP 200 with full file contents; www:x:1000 and smasher:x:1001 confirmed. URL-encoded variant %2e%2e/%2e%2e/... also returned HTTP 200.
Exact commands 2
Plain dot-dot traversal — server returns /etc/passwd contents without authentication.
curl -s -i --path-as-is "http://$TARGET:1111/../../../../etc/passwd"
URL-encoded variant — both forms were accepted, confirming incomplete input sanitization.
curl -s -i --path-as-is "http://$TARGET:1111/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"
3EnumerationPath traversal file exfiltration for offline binary analysis (CWE-22)
Exfiltrated the server binary, source code, and system libc via the same path traversal
Using the same traversal primitive, the attacker downloaded the running tiny web server ELF binary (45,456 bytes, x86-64), its C source file tiny.c, and the system libc (libc-2.27.so). Source analysis of tiny.c pinpointed the url_decode() function, which decoded URL-escaped bytes into a fixed-size stack buffer with no bounds check on the output length — a classic stack buffer overflow reachable from any unauthenticated HTTP request.
/home/www/tiny-web-server/tiny HTTP=200 size=45456 bytes, confirmed ELF 64-bit LSB executable x86-64; tiny.c HTTP=200; libc retrieved for gadget and symbol offsets.
Exact commands 2
Create a local working directory for the retrieved artifacts.
mkdir -p /tmp/smasher/exploit
Exfiltrate the running ELF binary for disassembly and gadget search.
curl -sS --path-as-is "http://$TARGET:1111/../../../../home/www/tiny-web-server/tiny" -o /tmp/smasher/exploit/tiny
4ExploitationStack buffer overflow with amd64 ROP / ret2libc (CWE-121, T1190)
Exploited the URL-decoder stack buffer overflow with an amd64 ROP chain to gain a shell as www
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.
Kill chain foothold step: 'timeout 20 python3 /tmp/smasher/exploit_key.py' completed successfully; subsequent SSH with the planted key returned uid=1000(www) /home/www.
Exact commands 2
Two-stage ROP exploit: leaks a libc GOT address, then writes attacker SSH public key to /home/www/.ssh/authorized_keys.
python3 /tmp/smasher/exploit_key.py
Verify foothold — expects uid=1000(www) /home/www.
ssh -i /tmp/smasher/wwwkey -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'id; pwd'
5Credential AccessCBC padding oracle attack (CWE-326, T1110)
Queried a CBC padding oracle on loopback TCP 1337 to recover the smasher user's SSH password
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].
Kill chain step 8: sshpass -p '[REDACTED: recovered credential]' ssh smasher@<retired-instance-ip> succeeded immediately after the www foothold, confirming the credential was oracle-derived.
Exact commands 2
From the www foothold — confirm the padding oracle service is listening on loopback TCP 1337.
ssh -i /tmp/smasher/wwwkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'ss -tlnp | grep 1337'
Run the padding oracle exploit script from the www foothold; output contains the decrypted smasher credential.
ssh -i /tmp/smasher/wwwkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null www@$TARGET 'python3 /tmp/padding_oracle.py 127.0.0.1 1337'
6Lateral MovementValid account authentication with stolen credential (T1078)
Authenticated as smasher over SSH using the oracle-recovered password and captured the user flag
The plaintext password recovered from the padding oracle was used directly to authenticate to SSH as the smasher user, providing a stable user-level interactive shell. /home/smasher/user.txt was read, and local privilege-escalation opportunities were enumerated.
Kill chain: sshpass -p '[REDACTED: recovered credential]' ssh smasher@<retired-instance-ip> 'id; cat /home/smasher/user.txt' returned uid=1001(smasher) and the user flag.
7Privilege EscalationSUID binary TOCTOU symlink race (CWE-367, T1548.001)
Exploited a TOCTOU symlink race in the SUID-root checker binary to read the root flag
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.
Kill chain root step: checker ran against 'file'; sleep 0.5; rm -f file; ln -s /root/root.txt file; wait; cat result — root flag captured.

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

Sanitize URL paths in the web server to prevent directory traversalCritical
The tiny web server resolved '../' sequences and their URL-encoded equivalents (%2e%2e) in request paths without restriction, letting any unauthenticated caller read arbitrary files the server process could access — including /etc/passwd and the server's own binary and C source.
Fix the stack buffer overflow in the URL-decoding functionCritical
The url_decode() function in tiny.c wrote decoded bytes into a fixed-size stack buffer without checking output length, allowing a remote attacker to overwrite the saved return address with a crafted HTTP request and execute arbitrary code as the www user.
Replace CBC-mode encryption on the loopback service with authenticated encryption to eliminate the padding oracleCritical
The service on loopback TCP 1337 encrypted user credentials with AES-CBC and returned observably different responses for valid versus invalid PKCS#7 padding, creating a padding oracle. Any process with loopback access could iteratively query it to decrypt the ciphertext and recover plaintext credentials without knowing the key.
Eliminate the TOCTOU race in /usr/bin/checker by using file-descriptor-based ownership checksHigh
The SUID-root binary /usr/bin/checker checked file ownership by resolving the caller-supplied filename (stat/access on the path), then opened the same filename again in a separate step. The window between check and open allowed an attacker to atomically replace the file with a symlink to any root-readable path, causing checker to read privileged files on the caller's behalf.

Exposed services

External surface