Interfacing with AD

Generally, interfacing with AD environments is done by using net commands, WinAPI calls, LDAP interfaces and PowerShell.

Using PowerShell allows us to use the official RSAT-AD-PowerShell AD cmdlets to interact with various .NET wraps for different RPC interfaces and communicate with LDAP objects as well - PowerView is considered the best tool to do all the above. PowerView is a PowerShell tool that uses PSReflect and its Win32 function calls to give precise situational awareness in AD environments by executing everything in memory.

The most common parameters used for PowerView are Identity and Credential:

  • Identity specifies the target of the command to be executed and accepts a number of formats

    • samAccountName

    • distinguishedName

    • objectGUID

    • objectSID

    • dnshostname for machine accounts

  • Credential specifies the PSCredential credential object used to perform authentication; the following is the standard way to create one

$SecPassword = ConvertTo-SecureString 'SomehthingSecure123!' - AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\user', $SecPassword)
Get-DomainUser otter -Credential $Cred

GPOs

GPOs are collections of user settings applied to groups of users and computers. They are used to set group memberships, local admin passwords, local user rights, LAPS settings, registry entries and schedule tasks.

After a GPO is created and linked to a site, a domain object or an OU, we can enumerate these links

Get-DomainOU -LDAPFilter "(gpkink=*)"

Another thing we can enumerate for is OU GPO inheritance: whenever a machine enumerates GPOs linked to a OU it always starts at the lowest level of the unit (for example if we need to enumerate CN=WINDOWS1,OU=Child,OU=Parent, …, the search will start at OU=Child and only after go through to OU=Parent) but GPOs can actually block inheritance to higher levels of the OU by setting gpOptions to 1. To know what hosts a GPO is linked and applied to we can use

Get-DomainOU -GPLink '<GUID>' | % {Get-DomainComputer -SearchBase $_.distinguishedname -Properties dnshostname}

Domain Trusts

Domain Trusts allow domains to form inter-connected relationships by linking the authentication systems of two or more domains to make the authentication flow between them possible.

Communications between trusted domains happens via a system of referrals: when the SPN that is being requested resides outside the primary domain (the domain of the user that is requesting it), the DC issues a referral to the forest's KDC (the KDC of the trusted domain) - this interaction generates a inter-realm TGT signed with a inter-realm key which is NOT from the krbtgt account.

In order to enumerate trusts manually we can use the [System.DirectoryServices.ActiveDirectory] namespace and some of its methods such as GetCurrentDomain().GetAllTrustRelationships() or GetCurrentForest().GetAllTrustRelationships() but the same can be achieved with the DsEnumerateDomainTrusts() and DsGetForestTrustInformationW() WinAPI calls.

Trusted Domain Objects or TDOs for short are crucial to establish a trust with another domain; we can enumerate them using LDAP since they have a separate object class of trustedDomain.

([adsisearcher]"(objectClass=trustedDomain)").FindAll() | %{ $_.Properties }

This query will print the properties of all the TDOs:

  • TrustType

    • DOWNLEVEL / 0x00000001: a trusted Windows domain that is not running Active Directory, shows as WINDOWS_NON_ACTIVE_DIRECTORY in PowerView

    • UPLEVEL / 0x00000002: a trusted Windows domain the is running Active Directory, shows as WINDOWS_ACTIVE_DIRECTORY in PowerView

    • MIT / 0x00000003: a trusted domain that is running a non-Windows, RFC4120-compliant Kerberos distribution

  • TrustAttributes

    • NON_TRANSITIVE / 0x00000001: the trust cannot be used transitively

    • QUARANTINED_DOMAIN or FILTER_SIDS / 0x00000004: the SID filtering protection is enabled for the trust

    • FOREST_TRANSITIVE / 0x00000008: a trust between two forests

    • WITHIN_FOREST / 0x00000020: the trusted domain is within the same forest

    • TREAT_AS_EXTERNAL / 0x00000040: external trust

Since TDOs are replicated in the global catalog we can enumerate all trusts for every domain in the entire forest just by querying the catalog.

Get-DomainTrust -SearchBase "GC://domain.com"

Replication Metadata

Whenever a change is made to a domain object, those changes are replicated from the DC the changes took place on to other DCs in the same domain - as part of this process, metadata about the replication is preserved in two main attribute that can be enumerated by any authenticated user.

When an object is replicated, so do its attributes unless they have the FLAG_ATTR_NOT_RELICATED flag in the systemFlags, if this flag is set the attribute will not be replicated. This means that we can look for the attributes that are NOT replicated but also for the attributes that ARE.

$searcher = [adsisearcher][adsi]"LDAP://CN=schema,CN=configuration,DC=domain,DC=com"
$searcher.Filter = '(&(&(objectClass=attributeSchema)(!systemFlags:1.2.840.113556.1.4.803:=1)))'
$searcher.PropertiesToLoad.Add('ldapdisplayname')
$searcher.FindAll() | % { $_.Properties.ldapdisplayname }

or

REPADMIN /showobjmeta server "CN=otter,CN=Users,DC=domain,DC=com"

The constructed msDS-ReplAttributeMetaData property is associated with every user / group / computer and contains things like

  • the name of the attribute that got changed

  • when the attribute changed

  • the number of time the attribute change

  • the DC that initiated the change

As long as a user has the right to read an object, it's possible for them to read its metadata as well.

To read the metadata we use

$searcher = [adsisearcher]"(samaaccountname=otter)"
$searcher.PropertiesToLoad.Add("msDS-ReplAttributeMetaData")
$searcher.FindAll() | % { $_.Properties.'msDS-ReplAttributeMetaData' }

This returns data in XML format so we can parse it

$searcher = [adsisearcher]"(samaaccountname=otter)"
$searcher.PropertiesToLoad.Add("msDS-ReplAttributeMetaData")
$xml = $searcher.FindAll() | % { $_.Properties.'msDS-ReplAttributeMetaData' } | % { [xml]$_ }
$xml[1].DS_REPL_ATTR_META_DATA

The results will show a number of attributes

  • pszAttributeName: the name of the attribute that changed

  • dwVersion: the number of times the attribute has changed

  • ftimeLastOriginatingChange: the time the attribute got changed

  • pszLastOriginatingDsaDN: the DC the change originated from

The msDS-ReplValueMetaData attribute is different from the msDS-ReplAttributeMetaData one because of the way forward and back links are replicated. Linked value replication allows individual values of a multivalued attribute to be replicated separately so AD calculates the value of a given attribute (the back link) from the value of another attribute (the forward link) - in this dynamic only forward links are writable.

This link replication mechanism means that the previous values of these attributes are stored in replication metadata (if we were to add and remove a user to / from a group we'll still be able to view the deleted user's name).

$searcher = [adsisearcher]"(samaaccountname=Domain Admins)"
$searcher.PropertiesToLoad.Add("msDS-ReplValueMetaData")
$xml = $searcher.FindAll() | % { $_.Properties.'msDS-ReplValueMetaData' } | % { [xml]$_ }
$xml[0].DS_REPL_VALUE_META_DATA

Once again this returns several attributes

  • dwObjectDn: the name of the member that was added

  • ftimeDeleted: the time the member was removed

  • ftimeCreated: the time the member was added

  • dwVersion: the number of times the attribute has changed (this attribute is an odd number if the user is a member of the group and it's even if the user was added and then removed)

PowerShell also offers cmdlets that automate the process

  • Get-DomainObjectAttributeHistory: retrieves the msds-replattributemetadata data and parses the XML to proper object output

  • Get-DomainObjectLinkedAttributeHistory: retrieves the msds-replvaluemetadata data for linked attributes and parses the XML to proper object output

  • Get-DomainGroupMemberDeleted: retrieves any users who were removed from groups by wrapping Get-DomainObjectLinkedAttributeHistory’s functionality

We can see how to determine which DC initiated the change by looking at Enumerate change metadata.

Last updated