На синтетике 0 ложных эскалаций, на реальном потоке 39 из 100: как я делал локального L1-аналитика SOC. gemma.. gemma. llm.. gemma. llm. ollama.. gemma. llm. ollama. Open source.. gemma. llm. ollama. Open source. soc.. gemma. llm. ollama. Open source. soc. Wazuh.. gemma. llm. ollama. Open source. soc. Wazuh. Информационная безопасность.. gemma. llm. ollama. Open source. soc. Wazuh. Информационная безопасность. Машинное обучение.. gemma. llm. ollama. Open source. soc. Wazuh. Информационная безопасность. Машинное обучение. триаж алертов.

На моём тестовом наборе модель показывала 55 из 55 верных вердиктов и ни одной ложной эскалации. Потом я дал ей 100 реальных алертов с лабораторного стенда Wazuh. Она зря подняла на вторую линию 39 из них и к некоторым дописала инцидент, которого не было.

Этот разрыв определил всю дальнейшую работу. Ниже что я построил, какие цифры получил и чего эти цифры не доказывают. Код и веса открыты: GitHub, Hugging Face.

Задача

Первая линия SOC тонет в однотипных алертах, а облачные LLM часто нельзя использовать: логи не должны покидать периметр. Я хотел получить помощника, который работает на обычном ноутбуке с 8 ГБ видеопамяти, ничего не отправляет наружу и готовит для второй линии проверяемую карточку.

Название kezekshi по-казахски значит «дежурный». В основе дообученная Gemma 4 E2B под лицензией Apache 2.0.

На вход один алерт как пакет доказательств E1…En. На выход JSON: вердикт из четырёх классов, краткое описание, утверждения со ссылками на номера доказательств, техники MITRE ATT&CK, чего не хватает и что проверить дальше. Ссылки нужны, чтобы вывод модели можно было проверить кодом, а не верить ему на слово.

Что сработало на обучении

Трудные негативы полезнее новых типов атак. PowerShell -enc из агента SCCM, vssadmin create от бэкапа, скан Nessus по заявке, impossible travel через корпоративный VPN: поверхность выглядит как атака, но в пакете есть явный признак легитимности. Я добавил 14 таких кейсов, и набор негативов вырос с 8 до 22. Ложных эскалаций на нём осталось 0, а recall не просел.

Правило в промпте иногда дешевле дообучения. Рубрика тяжести поначалу уронила recall на незнакомых типах с 20/20 до 16/20: все четыре промаха это OAuth-согласие, прочитанное как штатное действие. Одно правило, что согласие приложению, выдача прав или новый метод MFA без подтверждения в пакете это минимум suspicious, вернуло 20/20 без видеокарты.

Режим размышления дообученной модели вредит. Рабочий режим без него.

Удар о реальный поток

Теневой прогон по реальным алертам стенда показал то, с чего я начал. На высокочастотных правилах, которых нет в синтетике (FIM реестра 751, 752, 750, rootcheck 510), модель завышала тяжесть и сочиняла нарратив.

Я сделал две вещи.

Первая: дообучил модель на 215 реальных benign-кейсах стенда. Замер на 100 отложенных алертах:

Версия

Совпадение вердикта

Лишние эскалации

v4, только синтетика

39/100

39/100

v5, плюс реальный шум

96/100

0/100

Вторая: перестал звать модель там, где исход от неё не зависит. Инъекции в логах и операционный шум из белого списка решаются детерминированно ещё до модели, причём контекст всё равно эскалирует: изменение в автозапуске или защитной службе из белого списка выпадает. На 3000 алертах стенда 98,1 % решилось без модели, 1,9 % ушло в неё. Это примерно в 50 раз меньше вызовов при тех же решениях об авто-закрытии.

Памятка правил страхует и саму модель. Пример из тестовых алертов в репозитории: на попытке входа по SSH под несуществующей учёткой v6 ответила false_positive, памятка сказала suspicious, и алерт ушёл человеку.

Схема ответа оказалась дешевле новой версии

Шестая версия выросла в обучении до 3546 примеров и стала точнее на знакомых типах, но брак ответа вырос: на типах вне обучения 9 из 20 карточек не проходили проверку. Я посмотрел сырые ответы и увидел причину: в ссылке на доказательство модель писала имя поля ev1 вместо номера E1.

Вместо переобучения я стал собирать схему Ollama под каждый алерт. Разрешены только E-id этого алерта и кандидаты MITRE из локального индекса. Заодно нашлась ошибка подбора: короткие триггеры искались как подстроки, и tor срабатывал на monitor.

Набор

Показатель

Было

Стало

Типы вне обучения (20)

брак ответа

9/20

0/20

Типы вне обучения (20)

MITRE, полнота

13/36

25/36

Типы вне обучения (20)

MITRE, точность

13/46

25/34

Типы вне обучения (20)

атаки подняты в L2

20/20

20/20

Знакомые типы (76)

брак ответа

12/76

1/76

Знакомые типы (76)

атаки подняты в L2

42/42

42/42

Знакомые типы (76)

ложные эскалации

0/34

1/34

Реальный шум (100)

совпадение вердикта

97/100

93/100

Реальный шум (100)

ложные эскалации

0/100

1/100

Плата видна в таблице: появились единичные лишние эскалации, а проверка ссылок стала формальной. Теперь номер всегда существует, но подходит ли он к утверждению, схема не гарантирует.

Подсказка меняет ответ

Самый неприятный результат этой серии. Когда в индексе появился кандидат T1555.004, более ранняя v5.1 начала писать про «массовый сбор учётных данных» о штатном чтении Credential Manager процессом Outlook. Список кандидатов работает как намёк на атаку.

Я добавил в промпт оговорку, что кандидаты подобраны по словам и атаку не означают. Recall на новых типах упал с 20/20 до 18/20: OAuth-согласие и IDOR закрылись как штатные. Оговорку я убрал. Для SOC лишняя эскалация дешевле пропущенной атаки, но это компромисс, а не решение.

Чего эти цифры не доказывают

  • Recall на реальных атаках не измерен. В реальном наборе только шум. 20/20 получено на пяти семействах атак, этого мало.

  • Часть кейсов почти совпадает с обучением. 30 из 76 знакомых и 63 из 100 реальных совпадают с точностью до цифр. Вне этих совпадений вердикт на прежней схеме был 44/46 и 34/37.

  • Реальный шум не проверяет хвост. Почти весь такой поток закрывает белый список, а строка про 100 алертов описывает модель без него.

  • Эталон реального шума это суждение одного аналитика, а не решения дежурной смены.

Поэтому контур работает в режиме assist: сам закрывает только детерминированный шум, всё, что дошло до модели, смотрит человек. Как разгрузка от шума и черновик карточки он полезен. Как автономный решатель эскалаций не проверен.

Что открыто

В репозитории код контура поверх Wazuh (только чтение), схема ответа, индекс ATT&CK, скрипты обучения и оценки, руководство оператора. Веса v6 лежат на Hugging Face: GGUF на 4,9 ГБ, который ставится в Ollama одной командой.

Датасета нет, и это осознанно: обучающий набор опирается на материалы учебного курса и на алерты закрытого стенда. Модель можно запустить на своих алертах, но метрики из статьи из открытых файлов не воспроизвести. Как собрать свой набор, описано в docs/dataset.md.

Перед публикацией весов я проверил, не выдаёт ли модель имена машин стенда. Во входах обучения они есть, в ответах нет, а ошибка считается только по ответу. За 306 попыток имена не появились, как и синтетическое имя, которое стояло в 144 ответах из обучения. Риск это снижает, но запоминание полностью не исключает.

Дальше

Следующий шаг это управляемые атаки на стенде и разбор их дежурным: только так появится настоящий recall контура. Скрипт для этого уже есть, eval/recall_eval.py.

Если вы работаете в SOC или MSSP, мне интересно другое: какая доля потока у вас реально решается памяткой и где такой контур сломается первым. Напишите в комментариях.

Автор: ipgleg

Источник