CPTS Prep - Trick - Easy Linux
HTB Trick: How a DNS Zone Transfer Unraveled an Entire Payroll System
A walkthrough of HackTheBox's "Trick" machine, or, how I learned to stop trusting a blank front page and start enumerating harder.
First Impressions
Every box starts the same way: point Nmap at it and see what talks back.
sudo nmap -sC -sV -A 10.129.227.180
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
25/tcp open smtp?
53/tcp open domain ISC BIND 9.11.5-P4-5.1+deb10u7 (Debian Linux)
80/tcp open http nginx 1.14.2
Four open ports, and on paper none of them looked exciting. SSH was a modern-enough OpenSSH build with no obvious CVE. Port 80 rendered a generic "Coming Soon" placeholder page, a Bootstrap template with nothing behind it. Port 25 barely wanted to talk to Nmap's SMTP script at all. A quick manual telnet session onto port 25 confirmed a user called root existed on the mail server, which, on a Linux box, is about as surprising as finding out water is wet.
That left DNS. Port 53, running BIND. This is usually the port everyone glances at and moves past, and that instinct nearly cost me the whole box.
The Port Everyone Skips
I started with the obvious BIND version-disclosure query, which came back empty:
dig CH TXT version.bind 10.129.227.180
No dice. SERVFAIL across the board. So I tried something more direct: does the domain trick.htb even resolve against this server?
dig any trick.htb @10.129.227.180
;; ANSWER SECTION:
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
It did. trick.htb and root.trick.htb both existed, which meant this box was serving its own DNS zone. I dropped trick.htb into /etc/hosts and poked around, and got nothing new. root.trick.htb was a dead end too.
At this point I genuinely thought I was stuck on a box with basically no attack surface. That feeling was the tell. When a box looks empty, the answer is almost never "the box is empty," it's "you haven't found where the content actually lives yet." So I went back to DNS and asked the one question I hadn't asked yet: will this server just hand me its entire zone file if I ask nicely?
dig axfr trick.htb @10.129.227.180
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.
There it was. A zone transfer, the DNS equivalent of asking a filing cabinet for its complete index and having it just comply, handed over preprod-payroll.trick.htb, a subdomain that appeared nowhere else. No link on the front page pointed to it. No certificate mentioned it. It only existed because the server was misconfigured to allow unrestricted zone transfers. Lesson filed away: don't consider DNS enumeration "done" until you've tried an AXFR, even against a server that gave you nothing on the first pass.
A Login Page and an Old Trick
Navigating to preprod-payroll.trick.htb dropped me in front of a login form for what looked like an internal payroll management system. I pulled the page source looking for anything obviously wrong: comments, exposed JS logic, hidden fields. Came up empty. The application looked clean on the surface.
So I fell back on the oldest trick in the book: a basic OR-based SQL injection against the login form, paired with the one username I already had in my pocket from the SMTP banner grab earlier, root.
It worked. Authentication bypassed, logged in as root.
This is worth sitting with for a second, because it's a small but real lesson in how reconnaissance compounds. The SMTP enumeration that felt like a complete waste of time ("great, there's a user called root, everyone has a user called root") turned out to be exactly the username that made the SQL injection payload land somewhere meaningful, rather than just proving the vulnerability existed in the abstract.
Turning a Foothold Into a File Read
Once inside the payroll application, there wasn't much of immediate value sitting on the surface. But I had a confirmed, working SQL injection point, which meant it was time to stop doing this by hand and let sqlmap do the heavy lifting.
sqlmap -r request.txt --privileges
The database user, remo@localhost, had the FILE privilege, meaning I could read arbitrary files off the underlying filesystem through the database connection itself, no shell required. The first stop for any privilege like that is always the webserver's own configuration, because it tells you exactly what else is running on the box that you can't see from the outside:
sqlmap -r request.txt --file-read="/etc/nginx/sites-enabled/default"
Buried in that config was a third virtual host nobody had linked to anywhere: preprod-marketing.trick.htb. Another subdomain, another web application, another round of enumeration.
LFI, By the Numbers
The marketing site's source didn't scream "vulnerable" any louder than the payroll app had. So rather than guess, I fuzzed it, specifically for local file inclusion, using the Jhaddix LFI wordlist from SecLists against the page parameter:
ffuf -w ~/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ \
-u 'http://preprod-marketing.trick.htb/index.php?page=FUZZ' -fs 0
Dozens of traversal-depth variants all landed on the same result:
....//....//....//....//etc/passwd [Status: 200, Size: 2351, ...]
Confirmed LFI. Reading /etc/passwd turned up a local user, michael. Where there's a named user and a working LFI, the next move writes itself: go looking for his SSH private key.
http://preprod-marketing.trick.htb/index.php?page=../../../../home/michael/.ssh/id_rsa
One small but important detail here: I fetched that file with curl rather than copy-pasting it out of the rendered browser page. Browsers are perfectly happy to mangle whitespace and line endings when you select-and-copy pre-formatted text, and an SSH private key that's had even one newline character subtly altered will fail to parse. Grabbing it as a raw HTTP response sidesteps that entirely.
curl "http://preprod-marketing.trick.htb/index.php?page=../../../../home/michael/.ssh/id_rsa"
Key in hand, I SSH'd in as michael. User flag secured.
The Path to Root: Fail2Ban as a Loaded Gun
With a foothold established, the next habit is always the same: check what the current user can run as someone else.
sudo -l
User michael may run the following commands on trick:
(root) NOPASSWD: /etc/init.d/fail2ban restart
Michael could restart Fail2Ban as root, no password required. On its own, "restart a service" doesn't sound like much. But Fail2Ban's entire design is the interesting part here. It watches log files for suspicious activity (repeated failed logins, for instance), and when a rule trips, it executes a configured action, typically a firewall command to ban the offending IP. Crucially, those actions are just shell commands sitting in config files on disk.
Checking permissions inside /etc/fail2ban/, everything was root-owned and root-locked down, except the action.d directory, which belonged to the security group. Michael, it turned out, was a member of that group.
That's the whole vulnerability in one sentence: a low-privileged user can write to the file that defines what root-owned commands get executed when Fail2Ban reacts to an event, and that same user can trigger the restart that reloads those definitions.
The exploitation was straightforward from there. Pull a copy of the relevant action config out to a writable location:
cp /etc/fail2ban/action.d/iptables-multiport.conf /tmp
Edit the actionban directive, the line that fires whenever an IP gets banned, and replace it with something that reads root's flag instead of blocking traffic:
actionban = cat /root/root.txt
Swap the modified file back into place:
rm /etc/fail2ban/action.d/iptables-multiport.conf
cp /tmp/iptables-multiport.conf /etc/fail2ban/action.d/
Force Fail2Ban to pick up the new configuration:
sudo /etc/init.d/fail2ban restart
And finally, give it something to actually ban. A handful of deliberately failed SSH logins against the box (a quick throwaway hydra run works fine, since the point isn't to guess a password, it's just to trip enough failed attempts to trigger a ban) and the jail fires, actionban executes as root, and the modified command runs in root's context instead of the original iptables call.
If we wanted to continue further in this engagement to get forms of persistance as this priviledged user you could also spawn a root shell, but as this is just a cheapshot.
Anyways..
Root, secured.
What Trick Actually Teaches
Stepping back, this box isn't really about any one clever exploit. SQL injection, LFI, and the Fail2Ban misconfiguration are all individually well-known, textbook techniques. What made Trick worth doing was how much of the machine stayed invisible until enumeration was pushed one step further than felt necessary:
- A "blank" port 53 wasn't blank. It just needed an AXFR attempt after the version query came back empty.
- A dead-end username from SMTP paid off two steps later, as the credential that made a SQL injection payload actually authenticate as something.
- A database file-read privilege turned an app-level bug into full infrastructure visibility, by reading the webserver's own config back to itself.
- A privilege escalation vector hid inside "just a service restart," because the real question was never what Michael could restart, but what files that restart would read and execute.
If there's one habit this box reinforces above all others, it's this: when a target looks quiet, that's not a signal to move on, it's a signal you haven't found the interesting part yet. Every stage of this chain was gated behind one extra command that most scans stop just short of running.
Machine: Trick (HackTheBox). SQL injection, LFI, and a Fail2Ban privilege escalation, tied together by an old-fashioned DNS zone transfer.