Accounts With a Dangerous SID History Attribute is an Active Directory finding: a user, group or computer holds a privileged or same-domain SID in its sIDHistory attribute and inherits that SID’s rights at logon. Fix it by removing the dangerous value with PowerShell (Set-ADUser, Set-ADGroup or Set-ADObject -Remove) and keeping SID filtering enabled on external and forest trusts.
The PowerShell part takes seconds. The real work is proving that nothing still depends on the old SID, and finding out how a privileged SID got there in the first place.
What the scanner is actually detecting
SID history exists for domain migrations: a migrated account keeps its old SID in sIDHistory so resources that reference the old SID still work. These scanners flag values that no normal migration should leave behind.
| Scanner | Finding title | What it checks |
|---|---|---|
| Tenable Identity Exposure | Accounts With a Dangerous SID History Attribute (indicator C-ACCOUNTS-DANG-SID-HISTORY, High) | Active users, computers and groups whose sIDHistory holds the SID of a built-in privileged group (such as Domain Admins, Enterprise Admins or the operator groups) or any SID from the same domain. |
| Microsoft Defender for Identity (security posture assessment in Secure Score) | Unsecure SID History attributes | Accounts with SID History values that Defender for Identity profiles as risky. |
| PingCastle | T-SIDHistoryDangerous (Check if dangerous SID are stored in the SIDHistory attribute) | SIDs that do not come from a former domain (such as SYSTEM), or that do but have a RID below 1000. |
The RID is the last number in a SID. Built-in privileged principals have RIDs below 1000: 500 is the built-in Administrator, 512 Domain Admins, 518 Schema Admins, 519 Enterprise Admins, and S-1-5-32-544 is the builtin Administrators group. PingCastle also has related rules for ordinary leftovers: S-SIDHistory, T-SIDHistorySameDomain and T-SIDHistoryUnknownDomain.
Real-world risk
At logon, Windows adds every SID in an account’s sIDHistory, plus the sIDHistory of every group it belongs to, to the access token. Access checks treat those SIDs like normal group memberships. An account whose history holds the Domain Admins SID therefore acts as a Domain Admin without appearing in the Domain Admins group, so membership reviews and group-change alerts miss it. That is why SID-History Injection (MITRE ATT&CK T1134.005) is a known persistence backdoor.
Two limits are worth stating. Writing a malicious value needs domain administrator or equivalent rights (Tenable), for example through the DCShadow attack (PingCastle), so the finding usually points to a past compromise or a sloppy migration, not an open door. And SID filtering on external and forest trusts strips foreign SIDs at the forest boundary but not between domains of one forest. Microsoft’s example: an account holding the Enterprise Admins SID from another domain in the same forest is effectively an admin in every domain.
How to confirm it on the host
Run these from a management host with the ActiveDirectory PowerShell module. The quick listing PingCastle recommends shows every object that has any SID history:
Get-ADObject -LDAPFilter "(sIDHistory=*)" -Properties sIDHistory
This script walks every domain in the forest, labels each value and saves the result, which also serves as your record of what existed before the change:
Import-Module ActiveDirectory
$report = foreach ($domain in (Get-ADForest).Domains) {
$domainSid = (Get-ADDomain -Server $domain).DomainSID.Value
Get-ADObject -Server $domain -LDAPFilter "(sIDHistory=*)" -Properties sIDHistory, sAMAccountName |
ForEach-Object {
$obj = $_
foreach ($sid in $obj.sIDHistory) {
$rid = [int64]($sid.Value.Split('-')[-1])
$reason = if (-not $sid.AccountDomainSid) { 'Well-known or builtin SID' }
elseif ($rid -lt 1000) { 'Privileged RID (below 1000)' }
elseif ($sid.AccountDomainSid.Value -eq $domainSid) { 'SID from the same domain' }
else { 'Migration SID, review' }
[pscustomobject]@{ Domain = $domain; Name = $obj.sAMAccountName; Class = $obj.ObjectClass
Sid = $sid.Value; Reason = $reason; DN = $obj.DistinguishedName }
}
}
}
$report | Export-Csv C:Tempsidhistory-report.csv -NoTypeInformation
$report | Sort-Object Reason | Format-Table Domain, Name, Class, Sid, Reason -AutoSize
The RID rule mirrors PingCastle’s logic. Privileged groups created later, such as DnsAdmins, have higher RIDs, so read the “review” rows too. To check a single account, Microsoft documents this for the Defender for Identity assessment (use Get-ADGroup or Get-ADComputer for other object types):
Get-ADUser -Identity jdoe -Properties SidHistory | Select-Object -ExpandProperty SIDHistory
Then check your trusts. The first name is the trusting domain (where your resources live); /domain is the trusted one. Without Yes or No, netdom only displays the current state:
Get-ADTrust -Filter *
netdom trust contoso.com /domain:partner.local /quarantine
netdom trust contoso.com /domain:fabrikam.com /enablesidhistory
How to fix it
Before you remove anything
- Keep the CSV from the script as the record of which SID each account carried.
- Take a system state backup of at least two domain controllers per domain, the backout plan in Microsoft’s archived guide to removing SID history.
- Dangerous values should not be kept. If someone relies on one, grant that access through normal group membership instead.
- Ordinary migration SIDs are a slower job: translate resource ACLs to the new SIDs first (ADMT security translation exists for this), then remove history in phases.
Unlike adding a value, removing one does not require domain administrator rights, only write access to the attribute (Tenable). Active Directory Users and Computers fails at this, so use PowerShell.
Remove one specific value
This is the safest form because it leaves any other history on the object untouched:
Set-ADUser -Identity jdoe -Remove @{sIDHistory='S-1-5-21-1111111111-2222222222-3333333333-512'}
Set-ADGroup -Identity "Old-Finance" -Remove @{sIDHistory='S-1-5-21-1111111111-2222222222-3333333333-519'}
Set-ADComputer -Identity FS01 -Remove @{sIDHistory='S-1-5-21-1111111111-2222222222-3333333333-512'}
Remove all dangerous values from the report
Run it with -WhatIf, review the output, then run it again without it:
Import-Csv C:Tempsidhistory-report.csv |
Where-Object Reason -ne 'Migration SID, review' |
ForEach-Object { Set-ADObject -Identity $_.DN -Server $_.Domain -Remove @{sIDHistory = $_.Sid} -WhatIf }
Clear all SID history from one account
PingCastle’s documented commands remove every value from an object:
Get-ADUser jdoe -Properties sidhistory | foreach {Set-ADUser $_ -Remove @{sidhistory=$_.sidhistory.value}}
Get-ADGroup "Old-Finance" -Properties sidhistory | foreach {Set-ADGroup $_ -Remove @{sidhistory=$_.sidhistory.value}}
Do not turn this into a domain-wide one-liner. Microsoft’s own guidance warns against wiping SID history for every user at once.
Keep SID filtering on across forest boundaries
netdom trust contoso.com /domain:partner.local /quarantine:Yes
netdom trust contoso.com /domain:fabrikam.com /enablesidhistory:No
/quarantine is for external trusts; Microsoft says not to apply it to forest trusts or trusts within a forest. /enablesidhistory is valid only for outbound forest trusts, and No restores the default filtering. Only Domain Admins or Enterprise Admins can change these settings. Domain controllers running Windows Server 2003 or later quarantine new external trusts by default, so older trusts are the ones to check.
Investigate how the value got there
Treat a privileged or same-domain value as a possible compromise until explained. Replication metadata shows when sIDHistory last changed and where:
repadmin /showobjmeta DC01 "CN=J Doe,OU=Staff,DC=contoso,DC=com"
Event 4765 (SID History was added to an account), from the Audit User Account Management subcategory on domain controllers, records additions; treat a missing event as inconclusive. Tenable advises a forensic review of the whole forest if you find malicious activity. Check the other common persistence changes too, such as DCSync rights granted to non-admin accounts.
How to verify the fix and rescan
Run the detection script again: only migration SIDs you chose to keep should remain. Changes replicate like any other attribute and need no reboot. To confirm every DC has the change:
$dn = "CN=J Doe,OU=Staff,DC=contoso,DC=com"
foreach ($dc in Get-ADDomainController -Filter *) {
$h = (Get-ADObject -Identity $dn -Server $dc.HostName -Properties sIDHistory).sIDHistory
"{0}: {1}" -f $dc.HostName, ($h -join ', ')
}
Existing sessions keep the old token, so the change takes full effect at the next sign-in; whoami /groups in a fresh session should no longer list the SID. Defender for Identity refreshes statuses every 24 hours, so its recommendation may stay open for a day. Tenable Identity Exposure should drop the account from the indicator, and a new PingCastle health check should no longer raise T-SIDHistoryDangerous.
What can break and how to roll back
- Resource access that still depends on the old SID: file shares and NTFS permissions, Exchange permissions, application ACLs and local groups on migrated servers.
- Group history: removing a value from a group affects every member of that group.
- Trust changes: users who rely on SID history across a trust lose that access, and universal groups not created in the trusted domain are filtered.
Rollback is the hard part: PingCastle notes a removed value cannot be added back without a real migration. Fix forward instead: use the CSV to see which old SID an account relied on, then grant its current SID or a group on that resource. An authoritative restore from the system state backup is the last resort. Trust settings revert with /quarantine:No or /enablesidhistory:Yes, which Microsoft says reduces security.
Common false positive reasons
True false positives are rare, because the SID really does land in the token. These cases usually explain a result that looks wrong:
- The source domain is gone. The SID still enters the token, and migrated servers may still list the old domain’s admin groups.
- Disabled accounts. Tenable reports only active accounts; other tools may list disabled ones. Re-enabling the account brings the rights back.
- An account migrated away and back. PingCastle notes this is the only normal way to get a same-domain SID. Remove it anyway.
- Stale data. The scan ran before the fix or read a DC that had not replicated yet.
If a migration is still running and ordinary migration SIDs must stay, record them through your risk acceptance process with an end date.
FAQ
Is it safe to remove all SID history after a migration?
Only once resource ACLs use the new SIDs. Remove it in phases, because a removed value cannot simply be written back.
Why can’t I remove sIDHistory in Active Directory Users and Computers?
Graphical tools fail on this attribute. Use Set-ADUser, Set-ADGroup, Set-ADComputer or Set-ADObject with -Remove.
Does SID filtering solve this finding?
Only at forest boundaries. It does not filter SIDs between domains of the same forest, so the account values must be cleaned.
Does a dangerous value always mean we were breached?
No, but writing one takes domain admin level rights, so investigate before assuming it came from a migration.
For the rest of the domain controller baseline, see the Windows Server hardening checklist.
Tracking this finding across many hosts
Findings like this can return after the next migration, so it helps to track them over time. SITEY, a self-hosted vulnerability management platform, imports findings from 16 scanners and merges duplicates within each scanner, not across scanners, so an account reported by two different tools stays as two records. Its AI triage can suggest that a finding is a false positive and shows its evidence, but a person makes the decision.