Working with SID History in Active Directory

The SIDHistory attribute on a user or computer object stores legacy Security Identifiers that used to belong to that account before it moved into your current domain. It exists almost entirely because Microsoft needed a way to migrate accounts without breaking every Access Control List on every server in the network. When you move a user from domain A to domain B, their primary SID changes. SIDHistory preserves the old one so existing file shares, group memberships, and permissions still work during the transition. Once the migration completes and you clear the attribute, it's usually forgotten about entirely until something breaks. The attribute itself is multi-valued. You can store multiple legacy SIDs on a single object. In practice though, you almost never see more than one or two, and only during active migration windows. The attribute is exposed through ADSIEdit, PowerShell, and the raw LDAP property SIDHistory. It does not appear in the standard Active Directory Users and Computers GUI unless you explicitly enable advanced features and dig into the Attribute Editor tab. Here is how it looks when you query it directly:

Get-ADUser -Identity jsmith -Properties SIDHistory | Select-Object Name, SIDHistory If the attribute returns nothing, that object has no legacy SIDs stored. If it returns values, you are looking at actual SID strings in the format S-1-5-21-... The domain portion is preserved, which matters because SIDs are only valid within their original domain's context. One thing beginners miss is that SIDHistory bypasses the normal SID filtering that Domain Controllers apply during cross-domain authentication. This is intentional but it is also a known attack surface. If an attacker gains control of a domain with the SID filter ignorelist enabled, they can inject crafted SIDs into victim accounts and escalate across trusts. Microsoft locked this down considerably in later builds, but on legacy forests running older schema versions, this remains a real concern. I have seen it exploited in red team engagements where the target was still on a 2008 R2 schema level with no intermediate updates applied.

How SID History Works During a Migration

The actual mechanism is straightforward. When you run the Active Directory Migration Tool or use the built-in move account process in ADUC, the migration engine writes the source domain's original SID into the destination object's SIDHistory attribute. It does not modify the object's primary SID. The primary SID becomes the new one from the destination domain. The legacy SID sits alongside it in SIDHistory, and when the account authenticates to resources in the old domain, the DC sees the old SID in that attribute and grants access accordingly. This only works during the migration window. The whole point is to prevent permission errors while you are still migrating file servers, email systems, and application databases. Once everything is moved, you are supposed to clear SIDHistory. The attribute is cleared automatically if you use certain migration workflows that include a cleanup step, but it is not cleared automatically just because a domain was migrated years ago. I have inherited forests where SIDHistory entries dated back to migrations that happened in 2006 and nobody remembered them. Clearing the attribute is not a complex operation. You can do it with PowerShell:

Get the Full Details

Sneaky Active Directory Persistence #14: SID History – Active Directory & Azure AD/Entra ID Security
Sneaky Active Directory Persistence #14: SID History – Active Directory & Azure AD/Entra ID Security

Get-ADUser -Filter {SIDHistory -ne $null} -Properties SIDHistory | ForEach-Object {Set-ADUser $_.DistinguishedName -Clear SIDHistory} That will remove all legacy SIDs from every user object in the domain in one sweep. You want to test that on a small OU first. Running it against the entire domain is generally safe but if you have any applications or scripts that depend on legacy SIDs for identification, clearing everything at once will break those. I learned that the hard way with a ticketing system that cached user lookups by SID instead of UPN. After the bulk clear, roughly 40 support tickets came in within an hour about missing account data. The fix was a schema update to the lookup query, but it took most of a Tuesday to sort out.

When SID History Causes Problems

The most common issue I deal with is stale SIDHistory entries causing confusion during troubleshooting. A user reports that they can access a share they should not have, or cannot access one they should. The permissions were modified in the new domain, but the old SID sitting in SIDHistory still matches an ACL entry left behind on an old file server that was never decommissioned properly. These ghost servers show up more often than you would expect. Someone migrates the data off it, forgets it is still powering on, and it continues to authenticate users against the old domain SID without anyone noticing. Another problem is trust-related. SIDHistory only functions across trusted domains. If you have an external forest trust that was broken and then re-established, or a two-way trust that was partially migrated, some objects may retain SIDHistory entries that reference a domain that no longer exists in the trust topology. Those entries are harmless in most cases because the SIDs simply never get evaluated, but they add noise to any forensic analysis. If you are investigating a lateral movement attempt or a privilege escalation, having to wade through dead legacy SIDs slows things down. I wrote a cleanup script that identifies SIDHistory entries referencing domains that no longer appear in any trust relationship, then clears just those. That reduced the average audit time for a migrated object from about ten minutes down to two. The attribute can also cause issues with group policy processing in rare cases. There are documented scenarios where a user object with a malformed SIDHistory entry triggers extended group policy refresh times. The DC spends additional time resolving the legacy SID during token construction. I saw this on a domain with roughly 12,000 user objects where about 300 of them had SIDHistory entries from a migration that was never cleaned up properly. Policy processing times spiked from under two seconds to nearly eight seconds on those accounts. Clearing the bad entries brought it back down to normal. The total script execution time for the cleanup was under four minutes.

Auditing and Reporting on SID History

If you need to produce a report of every object with SIDHistory populated, this query will give you the full picture across a domain: Get-ADUser -LDAPFilter "(SIDHistory=*)" -Properties SIDHistory, DistinguishedName, UserPrincipalName | Select-Object UserPrincipalName, DistinguishedName, @{N='LegacySIDs';E={$_.SIDHistory}} | Export-Csv C:\SIDHistory_Audit.csv -NoTypeInformation Run the same against computers if you need those as well. Machine accounts can carry SIDHistory too, though it is less common. Group objects can carry it as well, but again, you will almost never see it on anything other than user and computer objects in normal operations.

Active Directory SID History Injection Attacks
Active Directory SID History Injection Attacks

For a quick count without exporting anything: (Get-ADDomain).UsedObjectCount would not help here. Instead use: (Get-ADUser -LDAPFilter "(SIDHistory=*)").Count

That gives you the number of user objects with at least one legacy SID. If that number is above zero in a domain that has been stable for more than a year, you should investigate further. A healthy, post-migration domain should have no SIDHistory entries except in very specific cases like account renaming workflows that deliberately preserve cross-domain access. I keep a quarterly schedule for this. Every three months I run the query, export the results, and compare against the previous quarter. Any growth triggers a review. Any objects showing SIDHistory from domains that no longer exist get flagged for clearing. This approach catches forgotten migrations before they become security incidents. It takes maybe fifteen minutes per quarter if you automate the export and diff process.

Known Limitations and Edge Cases

SIDHistory is not replication-critical. Changes to the attribute do replicate across DCs the same way other user attributes do, but because it is rarely modified after initial migration, replication conflicts are virtually nonexistent. However, if you ever need to manually set or append to SIDHistory rather than clear it, you must use a tool that supports writing to multi-valued attributes correctly. Some older third-party migration tools attempted to write SIDHistory in single-value mode, which would silently drop all but the last SID. I found this on a client's environment where a consultant had used a deprecated migration utility. Four users had SIDHistory entries but only half of their actual legacy SIDs were present. The other half was gone. It took a SIDHistory from the source domain controllers via NTDS.dit export to recover the missing values. Another limitation is that SIDHistory does not support SIDs from untrusted domains. If you try to add a legacy SID from a domain that has no trust relationship with the current domain, the write will fail. The DC validates the SID before accepting it. This is a protective measure but it also means you cannot use SIDHistory as a general purpose cross-domain identifier hack. It only works within the intended migration and trust framework. The most important thing to remember is that SIDHistory is not a security feature. It is a compatibility feature. Every entry on an object is a potential bypass for permission boundaries that were designed around the current primary SID. If your environment has strict compliance requirements, even legacy SIDHistory entries may violate your policy. In those cases, clearing all SIDHistory and rebuilding permissions explicitly under the new SIDs is the only path that satisfies auditors. It is disruptive but it eliminates the ambiguity that compliance teams flag during reviews.

L'Active Directory : SID, RID et SID History
L'Active Directory : SID, RID et SID History

Practical Guidance

Run the LDAP query every quarter. Clear stale entries from decommissioned domains. Investigate any SIDHistory that appears on objects outside of an active migration. Do not assume a clean SIDHistory count of zero means your domain is perfectly healthy, because the absence of entries could also mean someone ran a bulk clear without documenting it. Check your migration logs to confirm whether a historical migration existed and whether the cleanup was intentional. If you cannot confirm the cleanup was planned, treat the zero result with the same level of scrutiny as a non-zero result. The attribute itself is simple. The problems arise from forgetting about it, which happens constantly in environments where migrations were completed years ago and nobody thought to document the post-migration steps. A fifteen-minute quarterly check with a saved PowerShell script is all it takes to stay ahead of it.