В третьей части мы научили Wazuh видеть агрессивный брутфорс SSH: правило 100100 срабатывает на четвёртую неудачу за минуту с одного IP и поднимает алерт уровня 12. Проблема в том, что дальше в цепочке стоит человек: в три часа ночи между «детект сработал» и «кто-то закрыл IP на файрволе» проходит от двадцати минут до следующего утра, а брутфорсу хватит и пяти. Четвёртая часть — про то, как человека из этой цепочки убрать: Active Response, встроенный в Wazuh механизм, который сам банит IP, изолирует хост и запускает произвольные скрипты по сработавшим правилам — без внешнего SOAR.
Содержание
Открыть содержание
Что такое Active Response и где он живёт
Механизм достался Wazuh в наследство от OSSEC и с тех пор почти не менялся — тот же декларативный XML, что и правила, и такая же долгоживущая инвестиция. Идея простая: у каждого алерта есть level, rule id и группа, а у вас — список «если сработало вот это, выполнить вот то». Отдельного сервиса нет: решение принимает analysisd, исполнение доверено wazuh-execd.
- analysisd прогоняет событие по правилам (части 1–3) и поднимает алерт;
- active-response — блок в ossec.conf, описывающий, на что реагировать (level, rules_id или rules_group), чем (имя команды) и где (какой хост исполняет),
- execd на целевом хосте — manager или конкретный агент — запускает скрипт из
/var/ossec/active-response/bin/, передав ему JSON на stdin, - timeout — по истечении времени execd запускает тот же скрипт ещё раз, уже с командой на откат: блокировка снимается сама, руками чистить нечего.
Ключевой нюанс схемы: скрипт должен лежать на том хосте, где он исполняется. Реакция local исполняется на агенте, приславшем событие, — значит, и скрипт, и nftables должны быть на каждом агенте. Это цена за возможность реагировать там, где идёт атака, а не там, где стоит сервер.
Встроенные команды: firewall-drop и когда его достаточно
В поставке уже есть готовые скрипты, и чаще всего хватает именно их:
- firewall-drop — блокирует srcip через системный файрвол: firewalld или iptables/nftables на Linux, WFP на Windows,
- host-deny — дописывает IP в /etc/hosts.deny (TCP wrappers; работает не везде),
- disable-account — блокирует учётную запись, под которой идут попытки входа.
firewall-drop — рабочая лошадка: для брутфорса SSH, сканеров и web-атак его достаточно в большинстве случаев. Включается блоком в ossec.conf на manager, скрипт уже входит и в серверный пакет, и в пакет агента.
Когда встроенного мало: нужен nftables-сет со своими таймаутами, вызов внешнего API — создать тикет, изолировать хост в EDR, погасить учётку в IdP — или реакция, зависящая от контекста алерта. Тогда пишете свой скрипт, и это ровно тот же контракт stdin/JSON, что и у встроенных.
Анатомия настройки: command и active-response
Настройка живёт в ossec.conf на manager и состоит из двух блоков — «что запускать» и «когда запускать»:
<command>
<name>nft-ban</name>
<executable>nft-ban.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<active-response>
<command>nft-ban</command>
<location>local</location>
<rules_id>100100</rules_id>
<timeout>600</timeout>
</active-response>
- command (первый блок) — описание исполняемого: имя, скрипт из active-response/bin и флаг timeout_allowed; без него команда одноразовая и отката по таймауту не будет,
- command (внутри active-response) — какое из описаний запускать,
- location — где исполнять:
local— на агенте, приславшем алерт;server— на manager;defined-agentс<agent_id>— на конкретном хосте;all— на всех агентах сразу. Доки не зря просят осторожности сall: один ложный детект — и недоступна вся сеть, - rules_id / level / rules_group — триггеры; правило — самый точный из них, поэтому вешаем на 100100 из третьей части,
- timeout — секунды до автоматического отката.
Практика: свой nftables-бан с safety-исключением
Собираем пример из заготовки: повторные неудачные аутентификации → IP уходит в nftables-сет, блокировка снимается сама, admin-подсеть не банится никогда. Реестра забаненных и cron-чистилок не будет — таймаут держит само ядро nftables.
База nftables на агенте, /etc/nftables.conf:
table inet wazuh_ar {
set banned {
type ipv4_addr
flags timeout
}
chain input {
type filter hook input priority -10; policy accept;
ip saddr @banned drop
}
}
- set banned — именованный сет с флагом timeout: элементы живут указанное время и исчезают сами, без внешнего планировщика,
- chain input — всё входящее от адресов из сета молча дропается.
Скрипт кладём в /var/ossec/active-response/bin/nft-ban.sh:
#!/usr/bin/env bash
# Active Response: бан srcip в nftables на 10 минут, admin-подсеть не трогаем.
LOG="/var/ossec/logs/active-responses.log"
INPUT=$(cat)
cmd=$(echo "$INPUT" | jq -r '.command')
srcip=$(echo "$INPUT" | jq -r '.parameters.alert.data.srcip' | grep -E '^[0-9.]+$')
log() { echo "$(date '+%F %T') nft-ban [$cmd] $*" >> "$LOG"; }
[ -n "$srcip" ] || { log "no valid srcip — skip"; exit 0; }
# Safety: админ-подсети не баним никогда, даже если правило сработало
case "$srcip" in
192.0.2.*|10.10.*) log "safety: $srcip is admin range — skip"; exit 0;;
esac
case "$cmd" in
add)
nft add element inet wazuh_ar banned { "$srcip" timeout 10m } \
&& log "blocked $srcip for 10m" || log "nft add failed for $srcip" ;;
delete)
nft delete element inet wazuh_ar banned { "$srcip" } 2>/dev/null \
&& log "unblocked $srcip" ;;
esac
Что здесь важно:
- stdin — JSON, а не аргументы. Wazuh 4.x передаёт сообщение вида
{"version":1,"origin":{…},"command":"add","parameters":{"alert":{…},"extra_args":[],"program":"…"}}; srcip живёт в parameters.alert.data.srcip, а по таймауту приходит тот же скрипт с"command":"delete", - grep -E ’^[0-9.]+$’ — обязательная валидация: значение из чужого лога уходит в привилегированную команду, и пустой или кривой srcip должен отсеиваться до nft,
- safety-выход до любого действия — сначала сверка с admin-подсетью, потом бан. Даже если правило сработало ложно, админа не забанит,
- timeout 10m прямо на элементе сета — если execd умрёт и delete не придёт, ядро само выкинет адрес через десять минут.
Установка и права — на каждом агенте, где скрипт будет исполняться:
install -m 750 -o root -g wazuh nft-ban.sh /var/ossec/active-response/bin/
systemctl restart wazuh-agent
Дальше — ossec.conf на manager (блоки из прошлого раздела) и systemctl restart wazuh-manager.
Как не забанить самого себя
Автобан хорош, пока банит чужих. Три рубежа, каждый спасает в своей ситуации:
- white_list в ossec.conf —
<global><white_list>192.0.2.10</white_list></global>: адрес, который встроенные скрипты не тронут никогда. Занесите мониторинг, CI и bastion до включения AR, а не после первого инцидента, - safety-префикс в скрипте — второй рубеж поверх white_list: защищает от собственных багов в правилах, а не только от чужих адресов,
- location: local вместо all — бан только на атакованном хосте, а не по всей сети разом.
Про таймауты — две единицы, в которых легко запутаться: timeout в ossec.conf считается в секундах, а repeated_offenders — в минутах, и живёт он в ossec.conf агента, не manager:
<active-response>
<repeated_offenders>30,60,1440</repeated_offenders>
</active-response>
Первое срабатывание банит на базовый timeout (600 секунд), второе — на 30 минут, третье — на час, четвёртое — на сутки. На Windows-агентах механизм не работает — учитывайте это в гетерогенном парке. Стартовые значения: 10–15 минут базового бана достаточно, чтобы убить перебор, и не так страшно, если под раздачу попал легитимный хост.
И главное правило внедрения: тестируйте с адреса, потерять доступ с которого не жалко. Первая неделя — короткий timeout и tail логов; лестницу repeated_offenders наращиваете, когда поверили, что ложных срабатываний нет.
| Вопрос | Дежурный вручную | firewall-drop | Свой AR-скрипт |
|---|---|---|---|
| Время реакции | минуты–часы | секунды | секунды |
| Что умеет | всё, что умеет человек | блок srcip системным файрволом | любой код: nftables, API, изоляция |
| Откат | человеком | timeout | timeout или свой механизм |
| Ложное срабатывание | решает усталость | бан легитимного IP на N минут | ровно то, что вы написали |
| Стоимость входа | уже оплачена | один блок в ossec.conf | скрипт + тест на стенде |
Как проверить, что всё работает
Wazuh-logtest тут не помощник: он гоняет правила в изолированной сессии и active response не запускает — проверять нужно живым событием.
С запасного хоста, не из admin-подсети, четыре неудачные SSH-попытки на агент:
for i in 1 2 3 4; do sshpass -p wrong ssh nosuchuser@AGENT_IP true; done
На manager — что алерт поднялся и дошёл до реакции:
tail -f /var/ossec/logs/alerts/alerts.json | grep 100100
На агенте — жизнь самого скрипта и результат:
tail -f /var/ossec/logs/active-responses.log
nft list set inet wazuh_ar banned
В сете должен появиться адрес с тикающим вниз timeout, а SSH с тестового хоста — перестать отвечать. Через десять минут элемент исчезает сам: так вы проверяете и бан, и откат. Если active-responses.log пуст — смотрите /var/ossec/logs/ossec.log на ошибки execd и проверьте, что rules_id совпадает, а у скрипта права 750 root:wazuh.
Итог
Обнаружение без реагирования — это просто дорогой лог: красиво, но атака к моменту прочтения уже закончилась. Active Response не заменит полноценный SOAR — здесь нет оркестрации, плейбуков и approval-цепочек, — но первый шаг к автоматическому ответу он закрывает почти бесплатно: один блок в ossec.conf на встроенный firewall-drop, сорок строк bash для nftables с safety-исключением и таймаутом — и ваш SIEM из второй и третьей частей впервые не только видит брутфорс, но и сам закрывает атакующему IP. В пятой части цикла уходим в Kubernetes: собираем аудит-логи кластера и учимся видеть, кто и что в нём делает.