Mehmet Ergene

Detecting BadSuccessor: Shorcut to Domain Admin

Yuval Gordon from Akamai recently introduced BadSuccessor, a critical attack technique that enables low-privileged users to escalate privileges to Domain Admin by abusing the Delegated Managed Service Accounts (dMSA) feature in Windows Server 2025.

This post explains the practical implications of the attack, how it evades detection in default auditing configuration, and how defenders can reliably detect such abuse by configuring targeted object-level auditing in Active Directory.

Understanding dMSA

Delegated Managed Service Accounts (dMSA) are a new feature in Active Directory, introduced in Windows Server 2025, designed to replace and modernize legacy service accounts to reduce attack surface. To leverage dMSA, all domain controllers and participating servers that want to use dMSA must be running Windows Server 2025, and the domain functional level must be Windows Server 2025.

A typical dMSA migration of a legacy service account involves three steps (the svc_account01 in the examples is the legacy service account):

 1. Create the dMSA:

This creates the account under the Managed Service Accounts OU. As of now, there is no documented parameter to override this OU path. (This information will be important for detection) 

 2. Initiate the migration:

3. Complete the migration:

Once complete, the legacy account (svc_account01) is disabled, and the new dMSA takes over.

dMSA Telemetry with Recommended Audit Configuration

With the default recommended audit configuration (applied via GPO), a standard dMSA migration generates limited telemetry on the domain controller. For instance, Event ID 5137 (object creation) is not generated as seen in the image below. 
This results from default object level auditing on the domain, which is insufficient. (Or, I had an issue on a freshly migrated domain which is less likely)

BadSuccessor: Exploiting dMSA

P.S.: I continued testing without modifying the audit settings since I wasn't aware during the test. New accounts were used: sus_dMSA and svc_account06(svcAccount06)

There are currently two public tools, SharpSucessor and BadSuccessor.ps1, to exploit the dMSA feature by:
  1. Creating a dMSA in an OU where the attacker(low-privileged user) has permissions.
  2. Linking it to a high-privilege account via msDS-ManagedAccountPrecededByLink.
  3. Faking migration completion by setting msDS-DelegatedMSAState to 2.

Example BadSuccessor usage:

With all recommended audit policies enabled, this activity produces no relevant events on the domain controller as seen below:

Improving Object Auditing

On top of recommended audit policies(applied via GPO), object-level auditing must be manually configured to monitor dMSA operations effectively and detect abuse:
Note that there is currently no option to enable Write operation for the msDS-ManagedAccountPrecededByLink and msDS-DelegatedManagedServiceAccount. Therefore, I enabled "Write all properties" option. 

Telemetry After Object Auditing Enhancements

After adding the new auditing configuration and testing the malicious dMSA abuse (with new accounts), all telemetry is generated:
Telemetry from dMSA abuse by BadSuccessor
Not surprisingly, legitimate dMSA migration generates more events:
Telemetry from legitimate dMSA migration

Detecting dMSA Abuse

Based on differences in telemetry between legitimate dMSA migration and malicious dMSA activity, the following detection ideas (and maybe more) can be produced:

1. dMSA Creation Outside the Managed Service Accounts OU

Legitimate dMSAs are created in the Managed Service Accounts OU. Assuming a low-privileged user wouldn't have enough permissions on this OU, we can flag any dMSA creation outside this OU.
let domain_dns_name = "otrf.local"; // replace with your domain dns name
let msa_cn = "CN=Managed Service Accounts";
// print replace_string(domain_dns_name, ".", ',DC=')
let  msa_dn = strcat(msa_cn, ',DC=', replace_string(domain_dns_name, ".", ',DC='));
SecurityEvent
| where EventID == 5137
| where EventData has "msDS-DelegatedManagedServiceAccount"
| extend EventData = parse_xml(EventData)
| extend EventData = EventData.EventData.Data
| mv-apply EventData on (
    project k = tostring(EventData['@Name']), v = EventData['#text']
    | project p = bag_pack(k, v)
    | summarize edata_p = make_bag(p)
)
| evaluate bag_unpack(edata_p, columnsConflict="replace_source")
| where ObjectDN !endswith msa_dn

2. Target (Legacy) Account Not Disabled After dMSA Linking

During a legitimate migration, the legacy account is automatically disabled. Assuming the low-privileged user not having rights on the targeted account, we can flag dMSA linking without disabling the target account.
Leaving this query as an exercise for the reader

3. Suspicious msDS-DelegatedMSAState Values

Setting this attribute to 2 is essential for BadSuccessor but is not observed in legitimate migrations. We can flag this event. Keep in mind that future hotfixes/patches on Windows Server 2025 may result in this event being generated for legitimate dMSA migrations.
SecurityEvent
| where EventID == 5136
| where EventData has_all  ("msDS-DelegatedManagedServiceAccount", "msDS-DelegatedMSAState")
| extend EventData = parse_xml(EventData)
| extend EventData = EventData.EventData.Data
| mv-apply EventData on (
    project k = tostring(EventData['@Name']), v = EventData['#text']
    | project p = bag_pack(k, v)
    | summarize edata_p = make_bag(p)
)
| evaluate bag_unpack(edata_p, columnsConflict="replace_source")
| where AttributeValue == 2

4. Malformed dMSA Security Descriptor

Currently, BadSuccessor tools that are tested omit setting the msDS-GroupMSAMembership attribute, resulting in malformed descriptors. This is a strong indicator of tool-based exploitation. However, it can easily be fixed I think. Therefore, don't rely 100% on this detection.
SecurityEvent
| where EventID == 5136
| where EventData has_all  ("msDS-DelegatedManagedServiceAccount", "msDS-GroupMSAMembership")
| extend EventData = parse_xml(EventData)
| extend EventData = EventData.EventData.Data
| mv-apply EventData on (
    project k = tostring(EventData['@Name']), v = EventData['#text']
    | project p = bag_pack(k, v)
    | summarize edata_p = make_bag(p)
)
| evaluate bag_unpack(edata_p, columnsConflict="replace_source")
| where AttributeValue == "Malformed Security Descriptor"

Conclusion

The BadSuccessor is quite a dangerous attack and can't be detected without manual audit configurations. Once the correct auditing is configured, it becomes trivial to detect this threat. Keep in mind that obect-level auditing must be configured on the domain level (targeting all OUs) for full coverage. 
Share this post