This post is part three in a series on documenting and remediating SID history in your AD forest. Go back and read part 1 and part 2 if you haven't had a chance yet. In today's episode we will document the domain SIDs so we know from where this latent access originates.
Many AD admins are like archeologists, digging up clues from the past… trying to figure out what the ancients did when they built the forest. We don't always have all the pieces. Many of us have inherited SID history in our forest from previous mergers and acquisitions, domains converging and splitting to support the political company currents of days gone by. In today's blog entry on SID history remediation we will unearth a Rosetta Stone, the key to decyphering the identity of civilizations past.
Here are the key facts to understanding SIDs and where they come from:
For more information see the following great articles:
By querying the SID history of all accounts in the domain and then parsing out the domain portion of the identifier we can quantify how many domains have contributed to the past mergers. But a SID is just a number, and it doesn't tell us the name of the domain where the accounts originated. We need a legend that maps SID history domains to their names. The domain SID is stored on the root of the domain partition. We can get this easily enough for the present domains in the forest, but what about the old domains?
If your environment is like many of those I encounter in my line of work, you may have stale trusts scattered across the forest, remnants of past migrations never cleaned up afterwards. These trusts are the clues we need to understand the past.
Lucky for us each trust is stored in the domain partition of the database as a trustedDomain object. One of the attributes on this object is the domain SID of the trusted domain. Now you can see that all we need is a well-crafted AD query to retrieve these domain SIDs and build our list.
On the other side of the coin it is possible that the trusts are long gone, and we will not be able to identify every domain in our past. This is the nature of archeology. You win some; you lose some.
Exactly how many domain SIDs do we need to find? Consider each one of these scenarios:
There are a number of ways to get domain SIDs and trusted domain SIDs from PowerShell. Each of these have their own strengths and weaknesses. I've listed them in order of my personal preference:
This method is appropriate for environments where you have at least one 2008 R2 domain controller in each forest domain (or you have the AD web service running on a legacy DC).
The down side here is that doing AD queries across domain partitions usually complains about permissions.
This method is handy when you have older domains where the AD cmdlets cannot reach. It is also easier when trying to connect to trusted domains. Each of these WMI queries will list the SID of the domain involved:
If you don't run these commands from a DC, then you'll need to add the -ComputerName switch and pass the name of a DC. These WMI classes only exist on DCs.
The down side here is that there is no quick way to enumerate all of the domains in the forest. We would have to spider-web through all of the trusts to discover every domain and its trusts.
Note: I haven't seen very much written on the WMI classes for Active Directory. They have been there for years, and you can get some pretty decent data with them. There is even a method to trigger the KCC to run on a DC. To get an exhaustive list of the classes and their live data run this command on a DC:
gwmi -namespace root\MicrosoftActiveDirectory –list | ForEach-Object {gwmi -namespace root\MicrosoftActiveDirectory –class $_.Name} | fl *
NLTEST does some stunning trust enumeration complete with domain SIDs. This is exactly what we want… almost. What we don't want is to parse a bunch of command line output. (NLTEST is a little-known utility we've had in the resource kit for a long time. It has a load of handy switches. Check it out.)
NLTEST /server:foo /domain_trusts /all_trusts /v
ADSI, painful as it is, can query trustedDomain objects as well, but it will have the same limitations we ran into earlier with the AD cmdlets.
Really? .NET? Yes, you can call .NET methods for AD from PowerShell. However, IT admins like myself don't know .NET nor have the time to learn it. We'll stick with the AD cmdlets and WMI.
Well… not so fast. In this case we have to use .NET for a couple reasons. I came to this painful realization after many hours of blood, sweat, and tears at the PowerShell ISE in my lab. Once I saw the coolness from NLTEST I knew there had to be a way to call the same APIs from PowerShell. From .NET we snag a forest object and then call the GetAllTrustRelationships method. Then we peel down into the TrustRelationshipInformation which holds the TrustedDomainInformation where we find the domain SID of the trust partners.
We're going to give this a one-two punch combo of WMI and .NET. Here are the steps to put these pieces together and build our Rosetta Stone:
(Note that some of the lines below are truncated by the blog display. See the attched file for full code.)
$DomainSIDList = @{} # Get my own local domain SID $MyDomainSID = gwmi -namespace root\MicrosoftActiveDirectory -class Microsoft_LocalDomainInfo | Select-Object DNSname, SID $DomainSIDList.Add($MyDomainSID.DNSname, $MyDomainSID.SID) # Get list of all domains in local forest $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() # For each domain in local forest use WMI to get trust list # Use WMI, because .NET Domain class GetAllTrustRelationships method does not include SID # (although the forest class version does) $forest.Domains | ForEach-Object { gwmi -namespace root\MicrosoftActiveDirectory -class Microsoft_DomainTrustStatus ` -computername $_.Name | ForEach-Object { $DomainSIDList.Add($_.TrustedDomain, $_.SID) } } # Get forest trusts from .NET $trusts = $forest.GetAllTrustRelationships() ForEach ($trust in $trusts) { $trust.TrustedDomainInformation | ForEach-Object { $DomainSIDList.Add($_.DnsName, $_.DomainSid) # Get all forest trusts from remote trusted forests $context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$_.DnsName) $remoteforest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($context) $remotetrusts = $remoteforest.GetAllTrustRelationships() ForEach ($remotetrust in $remotetrusts) { $remotetrust.TrustedDomainInformation | ForEach-Object { $DomainSIDList.Add($_.DnsName, $_.DomainSid) } } } }
I've added this code to the SIDHistory.ps1 function library as Export-DomainSIDs, and it will give you a CSV containing all of the domain names and domain SIDs. Use "Get-Help Export-DomainSIDs -Full" to see the syntax and notes. As a bonus I've thrown in a function called Update-SIDMapping. This function will take the SID report file generated by Export-SIDMapping and insert a new column showing the source domain name for each SID history entry based on the output of Export-DomainSIDs.
See the attached ZIP file for the function library and example output.
Now we have everything we need to identify how the Greeks and Egyptians slipped into the forest. After all it was the Greeks that invented the Trojan Trust; that's why Microsoft invented SID filtering.
If you would like to have me or another Microsoft PFE visit your company and assist with the ideas presented in this blog post, then contact your Microsoft Premier Technical Account Manager (TAM) for booking information.
For more information about becoming a Microsoft Premier customer email PremSale@microsoft.com. Tell them GoateePFE sent you.