D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Конфигурационный файл встроенного в Windows средства мониторинга Sysmon может разрастаться до тысячи строк, которые описывают правила отбора регистрируемых в логах событий. Стандартный набор функций Sysmon для проверки конфига неудобен и затрудняет работу аналитика. В этой статье я покажу, как составлять пайплайн CI/CD для валидации конфига Sysmon и сделать невозможной потерю событий.
Главная проблема при работе с Sysmon в том, что в случае ошибки ты получаешь текст с атрибутами XML-файла, как на картинке ниже.
Один из методов составления конфига Sysmon я уже описал в статье на «Хабрахабре».
Ошибки могут быть связаны с опечатками, некорректными параметрами, новыми типами событий — все они способны привести к нарушению процесса мониторинга. Для автоматической проверки мы могли бы воспользоваться схемой XSD, которая не поставляется вместе с Sysmon.
На GitHub был похожий проект, автор описывал XSD-схему для разных версий Sysmon и разработал модули PowerShell. В настоящее время проект не поддерживается.
Для решения проблемы мы можем воспользоваться подходом Detection-as-Code (DaC). Для его реализации я воспользовался возможностями GitLab CI/CD.
Практики DevOps набирают обороты во всех сферах ИТ. Detection engineering не исключение — коллаборация получила название Detection-as-Code.
В большинстве случаев для создания нового правила или редактирования существующего аналитику необходимо открыть встроенный редактор используемого продукта. Изменения, вносимые в пользовательском интерфейсе, трудно и неудобно отслеживать. Подход DaC позволяет быть уверенным, что мы ничего не сломаем. В процессе CI/CD создаваемый контент тестируется до того, как он будет доставлен в целевую систему.
Так выглядит начало файла .gitlab-ci.yml:
Код: Скопировать в буфер обмена
Ключевое слово stages определяет этапы в пайплайне, а workflow — правило, описывающее, когда он будет запускаться. Мой пайплайн запускается только при изменении модулей конфига.
Код: Скопировать в буфер обмена
Job для GitLab выглядит вот так:
Код: Скопировать в буфер обмена
В результате образ будет добавлен в реестр Docker соответствующего проекта GitLab. Его мы будем использовать на остальных этапах.
Я решил разделить свой конфиг по модулям, которые может регистрировать драйвер Sysmon (ProcessCreate, NetworkConnection и так далее). Этот способ используется в репозитории sysmon-modular.
Чтобы можно было работать с разными версиями Sysmon, я предусмотрел вариант, когда итоговый конфиг собирается на основе схем, поставляемых вместе с Sysmon.
Собирает конфиг скрипт merge_module.py, написанный на Python. Также используется шаблон Jinja2 (config.xml.j2).
Код: Скопировать в буфер обмена
Скрипту на вход подается схема из Sysmon, а дальше в соответствии со схемой собирается итоговый конфиг.
Код: Скопировать в буфер обмена
Job в pipeline GitLab выглядит следующим образом:
Код: Скопировать в буфер обмена
Обрати внимание на ключевое слово artifacts, где мы определяем, что все файлы из директории output/ будут передаваться между джобами и мы сможем использовать их повторно.
Результат: собранный конфиг Sysmon для соответствующих схем.
После проверки структуры XML нужно проверить параметры и значения в самом конфиге.
Код: Скопировать в буфер обмена
Для уменьшения объема кода я применяю атрибут extends, который позволяет повторно использовать код, в моем случае before_script.
Код: Скопировать в буфер обмена
На этапе merge_configs мы указали артефакты и конфиги Sysmon, которые валидируем на соответствие схемам при помощи скрипта sysmonvalidate.py.
Код: Скопировать в буфер обмена
Давай посмотрим, что проверяется в текущей версии sysmonvalidate.py.
Корректность версии конфига Sysmon и версии схемы:
Код: Скопировать в буфер обмена
Корректность наименования опций:
Код: Скопировать в буфер обмена
Значение атрибута groupRelation:
Код: Скопировать в буфер обмена
Далее проверяются:
Что не проверяется — это типы данных.
Результат: конфиг проверен на соответствие схемам.
Я оптимизировал роль Ansible, которую нашел на GitHub под свою задачу, ее мы рассмотрим далее.
Job выглядит следующим образом:
Код: Скопировать в буфер обмена
В job test-windows-16 запускается плейбук Ansible, который выполняет следующие таски.
Создает директорию, в которую копируются файлы для будущих этапов:
Код: Скопировать в буфер обмена
Проверяет, установлен ли Sysmon:
Код: Скопировать в буфер обмена
Удаляет Sysmon, если он установлен:
Код: Скопировать в буфер обмена
Копирует файл Sysmon:
Код: Скопировать в буфер обмена
Копирует конфигурационный файл и Eula.txt:
Код: Скопировать в буфер обмена
Устанавливает Sysmon:
Код: Скопировать в буфер обмена
Удаляет Sysmon и загруженные файлы:
Код: Скопировать в буфер обмена
Результат: проведено тестирование на хосте.
Вот несколько примеров способов доставки:
Источник xakep.ru
Автор @d3f0x0 aka @war_gun8
Главная проблема при работе с Sysmon в том, что в случае ошибки ты получаешь текст с атрибутами XML-файла, как на картинке ниже.

Один из методов составления конфига Sysmon я уже описал в статье на «Хабрахабре».
Ошибки могут быть связаны с опечатками, некорректными параметрами, новыми типами событий — все они способны привести к нарушению процесса мониторинга. Для автоматической проверки мы могли бы воспользоваться схемой XSD, которая не поставляется вместе с Sysmon.
На GitHub был похожий проект, автор описывал XSD-схему для разных версий Sysmon и разработал модули PowerShell. В настоящее время проект не поддерживается.
Для решения проблемы мы можем воспользоваться подходом Detection-as-Code (DaC). Для его реализации я воспользовался возможностями GitLab CI/CD.
Практики DevOps набирают обороты во всех сферах ИТ. Detection engineering не исключение — коллаборация получила название Detection-as-Code.
КРАТКОЕ ВВЕДЕНИЕ В DETECTION-AS-CODE
Detection-as-Code — это подход включения практик DevOps в деятельность аналитиков SOC и CERT, а также инженеров по внедрению СЗИ и средств мониторинга. DaC позволяет структурировать управление правилами обнаружения, конфигурационными файлами, провести тестирование до их использования в конечном продукте.В большинстве случаев для создания нового правила или редактирования существующего аналитику необходимо открыть встроенный редактор используемого продукта. Изменения, вносимые в пользовательском интерфейсе, трудно и неудобно отслеживать. Подход DaC позволяет быть уверенным, что мы ничего не сломаем. В процессе CI/CD создаваемый контент тестируется до того, как он будет доставлен в целевую систему.
ПАЙПЛАЙН
Этапы
Мой пайплайн включает пять этапов:- ".pre" — сборка Docker-контейнера.
- build — сборка конфига из модулей для нужной Sysmon версии схемы.
- test-schema — тестирование полученных конфигов на соответствие схеме Sysmon.
- test-on-windows — тестирование конфигов на хостах (при необходимости можно использовать Windows 10 и Windows 7, если нужен конфиг для легаси).
- deploy — доставка конфига до пользователей.

Так выглядит начало файла .gitlab-ci.yml:
Код: Скопировать в буфер обмена
Код:
stages:
- ".pre"
- build
- test-schema
- test-on-windows
- deploy
workflow:
rules:
- changes:
- templates/*.xml
Нулевой этап. Сборка образа Docker
Для сборки образа Docker я использую подход Docker-in-Docker. Docker-файл также находится в репозитории. На этом этапе мы устанавливаем Ansible, pywinrm и коллекцию Ansible Community.Windows в собираемый образ.Код: Скопировать в буфер обмена
Код:
FROM python:3.12-bookworm
LABEL maintainer="d3f0x0"
RUN apt-get update && apt-get upgrade -y && pip install --upgrade pip && apt-get install ansible -y && pip3 install pywinrm && ansible-galaxy collection install community.windows && apt-get clean
VOLUME [ "/data" ]
WORKDIR /data
CMD ["ansible-playbook", "--version"]
Код: Скопировать в буфер обмена
Код:
build-docker:
stage: ".pre"
image: docker:dind
tags:
- docker
services:
- name: docker:dind
script:
- echo -n "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
- docker pull "$CI_REGISTRY_IMAGE:latest" || true
- >
docker build
--pull
--cache-from $CI_REGISTRY_IMAGE:latest
--label "org.opencontainers.image.title=$CI_PROJECT_TITLE"
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
--label "org.opencontainers.image.created=$CI_JOB_STARTED_AT"
--label "org.opencontainers.image.revision=$CI_COMMIT_SHA"
--label "org.opencontainers.image.version=$CI_COMMIT_REF_NAME"
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
.
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Первый этап. Билд конфига
Первый шаг в обеспечении надежности конфига Sysmon — это проверка структуры XML-файла. XML должен быть валидным, чтобы Sysmon корректно интерпретировал конфиг.Я решил разделить свой конфиг по модулям, которые может регистрировать драйвер Sysmon (ProcessCreate, NetworkConnection и так далее). Этот способ используется в репозитории sysmon-modular.

Чтобы можно было работать с разными версиями Sysmon, я предусмотрел вариант, когда итоговый конфиг собирается на основе схем, поставляемых вместе с Sysmon.
Собирает конфиг скрипт merge_module.py, написанный на Python. Также используется шаблон Jinja2 (config.xml.j2).
Код: Скопировать в буфер обмена
Код:
<Sysmon schemaversion="{{ schemaversion }}">
<HashAlgorithms>*</HashAlgorithms>
<DnsLookup>True</DnsLookup>
<CheckRevocation>True</CheckRevocation>
<ArchiveDirectory>sysmon</ArchiveDirectory>
<EventFiltering>
{% for module in modules_files %}
{% set event = module.split('.') %}
{% if event[0] in event_schema %}
{% include module %}
{% endif %}
{% endfor %}
</EventFiltering>
</Sysmon>
Код: Скопировать в буфер обмена
Код:
SCHEMA_DIR = os.path.join(os.getcwd(),"schemas")
MODULES_DIR = os.path.join(os.getcwd(),"templates")
SYSMON_CONFIG_NAME = "config"
if not os.path.isdir("output"):
os.mkdir("output")
try:
for file in os.listdir(SCHEMA_DIR):
schema = SysmonSchema(os.path.join(SCHEMA_DIR, file))
schemaEvents = (schema.events.keys())
environment = Environment(loader=FileSystemLoader(MODULES_DIR), trim_blocks=True, lstrip_blocks=True)
template = environment.get_template("config.xml.j2")
configFilename = os.path.join("output",f"{schema.binaryversion}_{SYSMON_CONFIG_NAME}.xml")
content = template.render(schemaversion=schema.schemaversion, modules_files=os.listdir(MODULES_DIR), event_schema=list(schema.events.keys()))
with open(configFilename, "w") as obj:
obj.write(content)
except FileExistsError:
print(f"ERROR - File not found")
except Exception as e:
print(f"ERROR - New exeception - {e}")%
Код: Скопировать в буфер обмена
Код:
merge_configs:
image: CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
stage: build
before_script:
- pip install -r requirements.txt
script:
- python3 merge_module.py
artifacts:
paths:
- output/*
expire_in: 30 min
Результат: собранный конфиг Sysmon для соответствующих схем.
После проверки структуры XML нужно проверить параметры и значения в самом конфиге.
Второй этап. Тестирование собранных конфигов на соответствие схемам
Я пользуюсь несколькими схемами и для каждой создаю отдельный job на этапе test-schema.Код: Скопировать в буфер обмена
Код:
test-config-schema-16:
needs: [merge_configs]
stage: test-schema
image: CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
tags:
- docker
extends: .before_script_schema
script:
- python sysmonvalidate/sysmonvalidate.py output/16_config.xml schemas/16_schema.xml
Код: Скопировать в буфер обмена
Код:
.before_script_schema:
before_script:
- git submodule init
- git submodule update
Код: Скопировать в буфер обмена
python sysmonvalidate/sysmonvalidate.py output/16_config.xml schemas/16_schema.xml
Давай посмотрим, что проверяется в текущей версии sysmonvalidate.py.
Корректность версии конфига Sysmon и версии схемы:
Код: Скопировать в буфер обмена
Код:
if config_schemaversion > float(schema.schemaversion):
raise ConfigError(f"The configuration version is higher than the schema version: "
f"{config_schemaversion} > {schema.schemaversion}")
Код: Скопировать в буфер обмена
Код:
config_options = [elem.tag for elem in root if elem.tag != 'EventFiltering']
for options in config_options:
if options not in schema.get_schema_options():
raise ConfigError(f"Correctness of the names of the configuration file options.\nSysmon -> "
f"ERROR: {options}\n")
Код: Скопировать в буфер обмена
Код:
for rulegroup in rule_group_element:
next_object = get_next_object(rulegroup)
if rulegroup.attrib['groupRelation'] not in ['and', 'or']:
raise ConfigError(f"Values of the groupRelation attribute of the RuleGroup element.\n"
f"RuleGroup -> groupRelation: {next_object}\n")
- название правил фильтрации, например Process Create;
- значение атрибута onmatch, в котором указывается фильтр для отбора событий ProcessCreate onmatch="exclude"
- данные податрибута элемента event, например FileCreateTime;
- используемые функции фильтрации, например: is, is not, contains, contains any, image.
Код:
for objectrulegroup in rulegroup.findall(f".//{next_object.tag}"):
# Check Names of filtering events
if objectrulegroup.tag not in schema.events:
raise ConfigError(f"Names of filtering events.\nRuleGroup -> ERROR: {next_object.tag}")
# Check Values of the onmatch attribute of the filtering events
if objectrulegroup.attrib['onmatch'] not in ['exclude', 'include']:
raise ConfigError(f"Values of the onmatch attribute of the filtering events\nRuleGroup -> {next_object.tag} -> onmatch")
flagRuleName = False
for rule in objectrulegroup.iter():
if not flagRuleName:
flagRuleName = True
continue
if rule.tag == "Rule":
if rulegroup.attrib['groupRelation'] not in ['and', 'or']:
raise ConfigError(f"Values of the groupRelation attribute of the Rule element.\n"
f"Rule -> groupRelation: {next_object}\n")
continue
# Sub-element data of the event element
if rule.tag not in schema.events[next_object.tag] and rule.tag != "Rule":
raise ConfigError(f"Sub-element data of the event element\nRuleGroup -> {next_object.tag} -> ERROR: {rule.tag} = {rule.text}")
# Used filters of the data element
if not "condition" in rule.attrib:
raise ConfigError(f"Elements without condition.\n {next_object.tag} -> {rule.tag} -> {rule.text}")
if rule.attrib['condition'] not in schema.filters :
raise ConfigError(f"Used filters of the data element.\nRuleGroup -> {next_object.tag} -> {rule.tag} -> {rule.attrib['condition']} "
f"= {rule.text}")
Результат: конфиг проверен на соответствие схемам.
Третий этап. Тестирование на хостах
Как говорится, тестов мало не бывает, поэтому на этапе test-on-windows тестирование проводится прямо на хостах. Я использую несколько виртуальных машин, несколько версий Sysmon (их можно найти в Internet Archive) и роль Ansible.Я оптимизировал роль Ansible, которую нашел на GitHub под свою задачу, ее мы рассмотрим далее.
Job выглядит следующим образом:
Код: Скопировать в буфер обмена
Код:
test-windows-16:
stage: test-on-windows
needs: [merge_configs, test-config-schema-16]
image: CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
tags:
- docker
script:
- cp output/16_config.xml .ansible/ansible-role-sysmon/files/config.xml
- ansible-playbook -vvv -i .ansible/hosts.yml .ansible/main.yml -e ansible_password=$ANSIBLE_PASSWORD
allow_failure: true
Создает директорию, в которую копируются файлы для будущих этапов:
Код: Скопировать в буфер обмена
Код:
- name: (Windows) Create installation directory
ansible.windows.win_file:
path: "{{ sysmon_install_path }}"
state: directory
Код: Скопировать в буфер обмена
Код:
- name: (Windows) Check if sysmon is installed
ansible.windows.win_service:
name: "{{ sysmon_servicename }}"
register: sysmon_installed
ignore_errors: true
Код: Скопировать в буфер обмена
Код:
- name: (Windows 7) Uninstall sysmon
ansible.windows.win_command: "{{ sysmon_servicename }} -u"
args:
chdir: "{{ sysmon_install_path }}\"
when:
- sysmon_installed.exists
- ansible_distribution_major_version | int == 6
- name: (Windows >7) Uninstall sysmon
ansible.windows.win_command: "{{ sysmon_servicename }} -u"
args:
chdir: "{{ sysmon_install_path }}\"
when:
- sysmon_installed.exists
- ansible_distribution_major_version | int > 6
Код: Скопировать в буфер обмена
Код:
- name: (Windows 7) Upload sysmon sysmon binary
ansible.windows.win_copy:
src: files/14/{{ sysmon_binary }}
dest: "{{ sysmon_install_path }}\\Sysmon64.exe"
when:
- ansible_distribution_major_version | int == 6
- name: (Windows >7) Upload sysmon sysmon binary
ansible.windows.win_copy:
src: files/15/{{ sysmon_binary }}
dest: "{{ sysmon_install_path }}\\Sysmon64.exe"
when:
- ansible_distribution_major_version | int > 6
Код: Скопировать в буфер обмена
Код:
- name: (Windows) Upload Eula.txt
ansible.windows.win_copy:
src: files/Eula.txt
dest: "{{ sysmon_install_path }}\\Eula.txt"
- name: (Windows) Upload sysmon configuration
ansible.windows.win_copy:
src: files/{{ sysmon_config }}
dest: "{{ sysmon_install_path }}\\sysmonconfig.xml"
- name: (Windows 7) Install sysmon
ansible.windows.win_command: "{{ sysmon_binary }} -i sysmonconfig.xml -accepteula"
args:
chdir: "{{ sysmon_install_path }}"
when:
- ansible_distribution_major_version | int == 6
Код: Скопировать в буфер обмена
Код:
- name: (Windows >7) Install sysmon
ansible.windows.win_command: "{{ sysmon_binary }} -i sysmonconfig.xml -accepteula"
args:
chdir: "{{ sysmon_install_path }}"
when:
- ansible_distribution_major_version | int > 6
- name: (Windows 7) Clean uninstall sysmon
ansible.windows.win_command: "{{ sysmon_servicename }} -u"
args:
chdir: "{{ sysmon_install_path }}\"
when:
- ansible_distribution_major_version | int == 6
Код: Скопировать в буфер обмена
Код:
- name: (Windows >7) Clean uninstall sysmon
ansible.windows.win_command: "{{ sysmon_servicename }} -u"
args:
chdir: "{{ sysmon_install_path }}\"
when:
- ansible_distribution_major_version | int > 6
- name: (Windows 7) Clean directory tools
ansible.windows.win_file:
path: C:\tools
state: absent
when:
- ansible_distribution_major_version | int == 6
- name: (Windows >7) Clean directory tools
ansible.windows.win_file:
path: C:\tools
state: absent
when:
- ansible_distribution_major_version | int > 6
Четвертый этап. Доставка собранного конфига до получателя
Заключительный этап — это доставка рабочего конфига. Выбор способа доставки зависит от метода, которым ты распространяешь Sysmon (GPO, SCCM, Ansible, PS1 и так далее).Вот несколько примеров способов доставки:
- загрузка актуального конфига из репозитория;
- копирование конфига в SYSVOL;
- распространение через Ansible.
ВЫВОДЫ
Описанный пайплайн — это пример того, как можно организовать процесс разработки правил детектирования. Его можно улучшить, добавив:- создание файла MSI, содержащего конфиг, и исполняемого файла с помощью WiX Toolset;
- использование пакетов;
- использование релизов;
- использование Terraform или Vagrant для управления виртуальными машинами, на которых проводятся тесты.
Источник xakep.ru
Автор @d3f0x0 aka @war_gun8