Практическое руководство

Плейбук реагирования на фишинг за 12 шагов: сквозной разбор в Krasper Suite

Конкретный пошаговый разбор того, как реальный плейбук реагирования на фишинг собран от начала до конца в Krasper Suite: от приёма оповещения до аудиторской записи. Двенадцать узлов, три условных ветвления и один dry-run перед тем, как что-либо коснётся продуктива.

Автор Krasper Engineering 17 июня 2026 г. 9 мин чтения
Phishing-Response Playbook — hero

TL;DR: The previous post in this series argued for the four design choices that keep SOAR playbooks alive: reactive data flow, typed node contracts, first-class branching, dry-run. This one shows what those choices look like in a working playbook. Twelve nodes, walked one at a time, with the data shape, the failure mode, and the design intent for each. By the end, the playbook handles the long tail of real phishing variation that scripted automations cave under.

Phishing remains the highest-volume initial-access vector in most organizations. It is also the incident category where the cost of poor automation is most visible: a single broken playbook can either miss real threats or, worse, take destructive action on a false positive (quarantining a legitimate CEO email, isolating a sales laptop mid-deal call). Doing it well is mostly about disciplined wiring; the logic itself is rarely the hard part.

This walkthrough follows one playbook end-to-end as it lives in Krasper Suite's designer. You can adapt the exact shape to any environment; the point of the walkthrough is the discipline it demonstrates.

Contents

  1. Ingress: alerts arriving from the mail-security signal
  2. Header parsing and message decomposition
  3. IOC extraction: URLs, domains, hashes, sender
  4. Threat-intelligence enrichment
  5. Sender reputation and domain-age signals
  6. Recipient resolution and asset mapping
  7. Phishing-confidence classification (the central switch)
  8. High-confidence branch: quarantine across mailboxes
  9. URL-click detection on affected endpoints
  10. Endpoint isolation (with explicit failure handling)
  11. Multi-channel notification with role-aware routing
  12. Ticket creation, audit trail, and post-mortem record

Plus: dry-run discipline and the one branch most teams forget to wire.

Шаг 1: приём

Первый узел получает оповещения от системы почтовой безопасности. В конструкторе это один входной порт с объявленной схемой: объект оповещения в том виде, в каком его публикует в общую шину адаптер почтового шлюза.

   ┌──────────────────────────────────┐
   │  Alert ingress                    │
   │                                   │
   │  in:  (event bus subscription)   │
   │  out: alert: MailAlert            │
   └──────────────────────────────────┘
Шаг 1: узел приёма оповещения

What matters here is that the alert is already typed. There is no "parse this JSON" step at the front of the playbook. That responsibility belongs to the mail-gateway adapter, which guarantees the shape before the alert ever reaches the bus. Authors of the playbook write against a contract, not against raw payloads.

If the upstream adapter is unavailable, no alert reaches this node; no execution starts; nothing fails silently. The platform's health view is the place to discover that, not the playbook itself.

Шаг 2: разбор заголовков и декомпозиция письма

Первый преобразующий узел разбирает почтовый объект на поля, которые понадобятся дальше: headers, subject, body_text, body_html, attachments[]. Это чистая функция: без внешних вызовов, без побочных эффектов и без режимов отказа, кроме некорректного входа (он уходит в порт ошибки и поднимает оповещение аналитику).

   ┌──────────────────────────────────┐
   │  Decompose message                │
   │                                   │
   │  in:  alert: MailAlert            │
   │  out: message: ParsedMail         │
   │  err: ParseError → analyst        │
   └──────────────────────────────────┘
Шаг 2: декомпозиция письма

Отдельным узлом это сделано ради тестируемости, а не для красоты. У парсера свой набор тестов. Когда вышестоящий шлюз переименует поле, сломается ровно один узел, с ровно одним исправлением, которое можно отревьюить изолированно.

Шаг 3: извлечение индикаторов компрометации

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

   ┌──────────────────────────────────┐
   │  Extract IOCs                     │
   │                                   │
   │  in:  message: ParsedMail         │
   │  out: iocs: IOCSet {              │
   │         urls[], domains[],        │
   │         hashes[], senders[]       │
   │       }                           │
   └──────────────────────────────────┘
Шаг 3: извлечение индикаторов

Two design notes that matter at scale.

First, URL extraction normalizes before deduplication. http://x.com, https://x.com/, and https://X.COM collapse to a single canonical URL. Without this, downstream lookups duplicate cost and the classifier double-counts the same indicator.

Second, attachment hashing uses SHA-256 only. Older hash algorithms add noise without value; the threat-intelligence APIs the next step calls accept SHA-256 universally. One hash family, one code path, one less source of integration drift.

Шаг 4: обогащение данными об угрозах

Следующий узел первым обращается к внешней инфраструктуре. Набор индикаторов уходит в адаптер threat intelligence (обычно сервис вроде VirusTotal или аналог), который возвращает вердикт по каждому индикатору: известно-вредоносный, известно-чистый, неизвестный.

   ┌──────────────────────────────────┐
   │  Threat-intel lookup              │
   │                                   │
   │  in:  iocs: IOCSet                │
   │  out: verdicts: VerdictMap        │
   │  err: TIError → degraded path     │
   │  timeout: 8s                      │
   └──────────────────────────────────┘
Шаг 4: запрос к threat intelligence

Здесь автоматически действуют три гарантии платформы: вызов несёт ключ идемпотентности (повтор не съест квоту API дважды), таймаут ограничен (медленный внешний сервис не подвесит всё выполнение), а порт ошибки обязателен (сбой у провайдера данных не понизит плейбук молча до «все индикаторы неизвестны»).

Порт ошибки ведёт в деградированный путь, а не в жёсткий останов. Если API анализа угроз недоступен, плейбук всё равно отрабатывает. Он классифицирует по тем сигналам, что есть (репутация отправителя, возраст домена, заголовки), и помечает вердикт как intel_unavailable в аудиторской записи. Аналитик, открывший тикет, понимает, что уверенность ниже, потому что обогащение было неполным.

Ещё нюанс, который стоит заложить: результаты этого узла кешируются на ограниченное окно (обычно несколько часов) по ключу самого индикатора, а не оповещения. Фишинговая кампания, за одно утро уронившая сотню почти одинаковых писем, не должна порождать сотню платных запросов по одному URL. Слой кеша относится к адаптеру, а не к плейбуку, поэтому авторам о нём думать не нужно, но знание о его существовании — разница между автоматизацией, которая экономически масштабируется, и той, которая ко второй неделе тихо выжигает квоту API.

Шаг 5: репутация отправителя и возраст домена

Параллельно с запросом к threat intelligence идут два более дешёвых обогащения: репутация домена отправителя (из отдельного адаптера) и возраст его регистрации (запрос в стиле WHOIS). Рантайм распараллеливает их, потому что они не зависят ни друг от друга, ни от результата анализа угроз.

   ┌────────────────────────┐    ┌────────────────────────┐
   │  Sender reputation     │    │  Domain age            │
   │                        │    │                        │
   │  in:  iocs.senders     │    │  in:  iocs.domains     │
   │  out: rep_score        │    │  out: age_days         │
   └────────────────────────┘    └────────────────────────┘
Шаг 5: параллельные дешёвые обогащения

Возраст домена важен, потому что свежезарегистрированные домены сильно коррелируют с фишинговыми кампаниями. Домен, зарегистрированный три дня назад и просящий вашего финансового директора отправить перевод, заслуживает больше внимания, чем домен пятнадцатилетней давности. Один этот сигнал ничего не решает; в сочетании с остальными он заметно повышает уверенность.

Шаг 6: разрешение получателей и сопоставление с активами

Почтовое оповещение перечисляет получателей по адресам. Для решений о локализации нужно больше: какой пользователь, какая роль, какая конечная точка, где он в структуре организации. Этот узел разрешает каждого получателя через общую каноническую модель активов.

   ┌──────────────────────────────────┐
   │  Resolve recipients               │
   │                                   │
   │  in:  message.recipients[]        │
   │  out: targets: ResolvedTarget[] { │
   │         user_id, role,            │
   │         endpoints[],              │
   │         privileged: bool          │
   │       }                           │
   └──────────────────────────────────┘
Шаг 6: разрешение получателей

The privileged flag is a derived field, true if the user is in any group that warrants elevated containment (finance approvers, executive assistants, IT administrators). The playbook treats privileged users differently in Step 11, so this resolution has to happen before the central switch.

If a recipient cannot be resolved (external recipient, freshly provisioned user not yet in the asset model), the target is included in the array with resolved: false rather than dropped. Downstream nodes can filter on resolution status; nothing silently disappears.

Шаг 7: классификация уверенности в фишинге

Это центральная точка принятия решения. Узел-классификатор берёт все сигналы обогащения и выдаёт вердикт confidence: high | medium | low вместе со структурированным обоснованием.

   ┌──────────────────────────────────────────────────────┐
   │  Classify confidence                                  │
   │                                                       │
   │  in:  verdicts, rep_score, age_days, targets, message │
   │  out: classification: {                               │
   │         confidence: "high" | "medium" | "low",       │
   │         signals: [...],                              │
   │         intel_complete: bool                         │
   │       }                                              │
   └──────────────────────────────────────────────────────┘
                              │
                              ▼
                ┌────────────────────────────┐
                │  switch confidence          │
                │   case high   ──▶ Step 8    │
                │   case medium ──▶ analyst   │
                │   case low    ──▶ trend tag │
                │   default     ──▶ analyst   │
                └────────────────────────────┘
Шаг 7: классификатор и центральный switch

Классификатор намеренно объясним. Это взвешенный набор правил, и сигналы, повлиявшие на каждый вердикт, фиксируются в выводе, поэтому ничего не прячется за чёрным ящиком ML-оценки. Открывая тикет, аналитик видит, почему плейбук классифицировал оповещение именно так. Эта проверяемость важна не меньше точности: вердикт высокой уверенности, который аналитик не может проследить до входных данных, подрывает доверие ко всей автоматизации.

Конкретно: веса сигналов версионируются вместе с самим плейбуком. Решение «считать свежезарегистрированные домены более рискованными» — это ревьюируемый дифф, а не дрейф конфигурации в продуктивной базе. Когда аудит спрашивает, почему это оповещение отправили в карантин 14 марта, ответом служит конкретный коммит с весами классификатора на момент выполнения, зафиксированный в аудиторской записи.

Ветка default подключена, хотя любое значение уверенности должно быть одним из трёх. Это самая частая «забытая» ветка, и именно она защищает от будущих изменений классификатора, вводящих четвёртый случай, под который switch никто не обновил. Если через полгода классификатор начнёт выдавать confidence: "requires-human-review", плейбук по умолчанию направит это аналитику, а не отработает так, что оповещение молча потеряется.

Шаг 8: карантин по всем ящикам (ветка высокой уверенности)

В ветке высокой уверенности первое действие — поместить письмо в карантин во всех ящиках, куда оно дошло. Узел карантина вызывает адаптер почтовой платформы и идемпотентен по паре (campaign_id, message_id), поэтому повторное выполнение не удваивает эффект.

   ┌──────────────────────────────────┐
   │  Quarantine mail                  │
   │                                   │
   │  in:  message.id, targets         │
   │  out: quarantine_result           │
   │  err: → escalate to analyst       │
   │  dry-run: synthetic success       │
   └──────────────────────────────────┘
Шаг 8: карантин по всем ящикам

This is the first destructive node in the playbook. The dry-run discipline matters most here: every modification to this branch is tested against the dry-run corpus before merge, because a regression that quarantines legitimate mail at scale is a production incident of its own.

Шаг 9: обнаружение кликов по ссылкам

Параллельно с карантином плейбук проверяет, не перешёл ли кто-то из получателей по вредоносным ссылкам. Это запрос к телеметрии конечных точек (логи прокси, телеметрия браузера, агент) по затронутым пользователям в окне между приходом письма и выполнением плейбука.

   ┌──────────────────────────────────┐
   │  URL-click detection              │
   │                                   │
   │  in:  iocs.urls, targets,         │
   │       message.received_at         │
   │  out: clicks: ClickEvent[]        │
   └──────────────────────────────────┘
Шаг 9: обнаружение кликов

Если массив пуст, плейбук идёт к уведомлениям. Если в нём есть записи, следующий шаг эскалирует к локализации затронутых хостов.

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

Шаг 10: изоляция конечной точки (с явной обработкой отказа)

Для каждой конечной точки, где зафиксирован клик, плейбук вызывает адаптер и изолирует её от сети. Этот шаг чаще других падает в реальных условиях: устройство может быть офлайн, агент недоступен, адаптер ограничен по частоте запросов.

   ┌──────────────────────────────────┐
   │  Isolate endpoint (for each)     │
   │                                   │
   │  in:  endpoint_id                │
   │  out: isolation_result           │
   │  err: IsolationFailed →          │
   │        force-priority alert      │
   │  timeout: 30s                    │
   │  dry-run: synthetic success      │
   └──────────────────────────────────┘
Шаг 10: изоляция конечной точки

Порт ошибки здесь не ведёт в «залогировать и продолжить». Он ведёт в оповещение с принудительным высшим приоритетом дежурному аналитику с контекстом по конечной точке, потому что неудавшаяся локализация при известной компрометации срочнее исходного оповещения. Плейбук не делает вид, что провалившаяся изоляция удалась; он эскалирует явно, а аудиторская запись отражает и попытку, и её исход.

Шаг 11: многоканальные уведомления с учётом ролей

Затронутых пользователей нужно уведомить, но канал и тон зависят от человека. Узел уведомлений распараллеливается по целям с учётом ролей:

  • Обычные пользователи: уведомление по утверждённому внутреннему каналу (почта или чат) с объяснением, что произошло и что делать.
  • Привилегированные пользователи (финансы, руководство, ИТ-администраторы): то же уведомление плюс немедленное оповещение высшего приоритета в дежурный канал, потому что последствия компрометации учётных данных выше.
  • Внешние получатели: автоматических уведомлений нет, случай эскалируется человеку.
   ┌────────────────────────────────────────────────┐
   │  Notify (for each target)                       │
   │                                                 │
   │  in:  target, message, classification           │
   │  out: notify_result                             │
   │  err: NotifyFailed → analyst                    │
   │  dry-run: synthetic success                     │
   │                                                 │
   │  routing:                                       │
   │    target.privileged == true → on-call + user   │
   │    target.privileged == false → user only       │
   │    target.resolved == false → analyst review    │
   └────────────────────────────────────────────────┘
Шаг 11: уведомления с учётом ролей

Текст уведомления не генерируется в рантайме языковой моделью, по крайней мере не в разрушительной ветке. Шаблоны заранее утверждены, версионированы и рендерятся с подстановкой полей конкретного оповещения. Это делает уведомления проверяемыми и единообразными и не даёт регрессии модели рассылать тревожные или путаные сообщения нетехническим пользователям в масштабе.

Шаг 12: создание тикета, аудиторский след и запись для разбора

Последний узел создаёт тикет инцидента и пишет запись для последующего разбора. В тикет входят полная классификация (с сигналами, повлиявшими на вердикт), трасса действий (что помещено в карантин, что изолировано, кто уведомлён) и ссылки на исходное оповещение и на трассу выполнения плейбука.

   ┌──────────────────────────────────┐
   │  Create incident + audit record   │
   │                                   │
   │  in:  classification, actions,    │
   │       targets, execution_trace    │
   │  out: incident_id                 │
   │  err: → emergency mail to SOC     │
   │  dry-run: synthetic success       │
   └──────────────────────────────────┘
Шаг 12: инцидент и аудиторская запись

The execution trace is the part most teams underinvest in. Reconstructing what the playbook did, six weeks later when the audit is happening, requires every node's input, output, branch decision, and external call to be persisted with a stable execution ID. Without that discipline the playbook is effectively a black box; with it, auditors can follow the automation node by node, which is what makes it defensible.

Дисциплина dry-run до того, как всё это поедет

Ни один из двенадцати шагов не выкатывается без прохождения dry-run на подобранном корпусе оповещений. В корпусе: показательное оповещение высокой уверенности с привилегированными получателями, оповещение средней уверенности со смешанными получателями, оповещение низкой уверенности, оповещение с таймаутом threat intelligence и оповещение с неразрешимым внешним получателем. Любое изменение плейбука прогоняется по всем пяти в CI; любое расхождение с ожидаемой трассой выполнения роняет сборку.

Это и есть разница между плейбуком, которому команда доверяет, и тем, который она обходит. Ветки выше не теоретические: это те случаи, которые корпус проверяет при каждом изменении.

Ветка, которую забывает подключить большинство команд

Перечитайте шаг 7. Ветка default центрального switch ведёт к аналитику. Перечитайте шаг 10. Порт ошибки изоляции ведёт в оповещение высшего приоритета. Перечитайте шаг 4. Порт ошибки threat intelligence ведёт в деградированный путь, который всё равно доводит плейбук до конца.

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

Заключение

Вот и вся форма: двенадцать узлов, три условных ветвления и четыре явных пути ошибок, и всё это за одним dry-run, прежде чем что-то дойдёт до продуктива. Так выглядит сквозное реагирование на фишинг, когда к дисциплине сборки относятся всерьёз.

Следующий материал серии поднимается на уровень выше — от одного плейбука к метрикам, которые показывают, дают ли ваши плейбуки в совокупности эффект. Разберём покрытие, время локализации, долю ложных срабатываний и долю автоматизации — цифры, которые важны CISO, — и то, как их инструментировать.

Дополнительное чтение

  • NIST SP 800-61r3: Computer Security Incident Handling Guide
  • MITRE ATT&CK: Initial Access: Phishing (T1566)
  • ENISA: ежегодный отчёт Phishing Threat Landscape
  • CISA: рекомендации Stop Ransomware по защите от первичного доступа
Готовы защитить
корпоративную инфраструктуру?

Запишитесь на технический брифинг. Без продаж, только архитекторы и ваша команда.