PowerShell: SID Walker, Texas Ranger (Part 3): Exporting Domain SIDs and Trusts

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.

Rosetta Stone

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.

SID 101

Here are the key facts to understanding SIDs and where they come from:

  • SID stands for Security IDentifier.
  • Every domain has a 32 byte SID.  The domain SID originates from the computer account SID of the first domain controller promoted in that domain.
  • Every security principal in the domain has its own SID (users, computers, groups, etc.).  The SID of each security principal is composed of the 32 byte domain SID and an 8 byte RID.
  • RIDs are Relative IDentifiers handed out by the domain RID Master FSMO.  Domain controllers have a pool of RIDs that they pull from any time they create a security principal.  These RIDs are requested from the RID Master in groups of 500 at a time.
  • Access Control Lists (ACLs) store SIDs as the way they identify a user, computer, or group requesting access to a resource.
  • During domain migrations when SID history is used the new domain account gets a SID in the new domain, then the old domain account SID is appended as an alias that gives this new account identical access to the old domain.
  • SID history is intended as a temporary bridge for access during the migration project, but too often the project wraps up without cleaning it up.

For more information see the following great articles:

Domain SIDs

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?

Do you trust me?

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.

image

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.

The Challenge

Exactly how many domain SIDs do we need to find? Consider each one of these scenarios:

  1. We need to identify every domain in the forest.
  2. Each one of these forest domains could have external trusts. We need to enumerate those external, non-transitive trusts for each one of the forest domains.
  3. The forest root domain could have a transitive forest trust. In this case we need to not only enumerate the remote member of the trust, but also any child domains that may exist in the remote trusted forest. This one is the most challenging and requires adequate permissions in the remote forest domains.
  4. Those remote transitively trusted domains in other forests may have external trusts as well, but that is too many hops for us.  A trust of a trust of a trust is a bridge too far.  Permissions and the lack of transitivity prevent us from getting there.  These will be out of scope.

PowerShell Options

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:

  1. The ActiveDirectory module cmdlets
  2. WMI
  3. NLTEST
  4. ADSI
  5. Chuck Norris .NET code from PowerShell

AD Cmdlets

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).

  1. Get a list of domains in the forest:
    (Get-ADForest).Domains
  2. Get the SID of the domain:
    (Get-ADDomain).DomainSID
  3. List the name and SID of all trusted domains that are not forest members (filtering on trustAttributes):
    Get-ADObject -SearchBase "CN=System,DC=Contoso,DC=com" -SearchScope OneLevel -LDAPFilter "(&(objectClass=trustedDomain)(!trustAttributes=32))" -Property name, securityIdentifier

The down side here is that doing AD queries across domain partitions usually complains about permissions.

WMI

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:

  1. gwmi -namespace root\MicrosoftActiveDirectory -class Microsoft_LocalDomainInfo
  2. gwmi -namespace root\MicrosoftActiveDirectory -class Microsoft_DomainTrustStatus

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 and ADSI

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.

.NET

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.

The Script Solution

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:

  • Get the SID of my current domain
  • Get all domains in the present forest, and then gather all SIDs from the trusts of each of those domains (This effectively gets the SID of every domain in the forest, and then any external trusts.)
  • Get SIDs of child domains across forest trusts
  • Export the list to CSV

(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.

Conclusion

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.

SIDHistory.zip