Infostealer-инцидент заканчивается не сменой пароля, а проверкой сессий
DFIR timeline: browser artifact -> session revoke -> mailbox/OAuth review -> business action retest.
Коротко: Пароль - это только один ключ. После infostealer нужно считать скомпрометированными сессии, токены, recovery-каналы и доверенные устройства.

Проблема

Infostealer-волны неприятны тем, что компрометация выглядит как нормальный вход: правильный cookie, знакомый браузер, иногда даже тот же регион. SIEM может не увидеть brute force, потому что его не было.

Если response-playbook ограничен сменой пароля, остаются OAuth grants, refresh tokens, mailbox forwarding, доверенные устройства, recovery-коды и бизнес-действия, выполненные до блокировки.

Решение

Первый час реагирования должен быть заранее расписан: revoke sessions, rotate secrets, проверить MFA enrollment, mailbox rules, OAuth apps, device trust, payment/invoice/profile changes.

Отдельно фиксируются volatile evidence: время входов, user agent, ASN, device id, session id hash, список выданных токенов и действия в критичных workflow. Сырые токены в отчёт не попадают.

Что проверить руками

  • Есть ли централизованный способ отозвать все активные сессии пользователя.
  • Попадают ли OAuth consent и mailbox rules в SOC-логи.
  • Сохраняются ли sign-in logs достаточно долго для расследования.
  • Проверяются ли бизнес-действия: платежи, инвойсы, изменение реквизитов, invite, recovery email.

Как это должно попадать в отчёт

  • DFIR-отчёт должен отделять containment от recovery. Containment - блокировка текущего доступа; recovery - доказательство, что доверие восстановлено и повторный путь закрыт.
  • Ретест формулируется через контроль: старые сессии не работают, OAuth grants очищены, mailbox rules пусты, новые входы требуют MFA/passkey.

Безопасный пример: нормализация sign-in CSV без сохранения токенов

import csv
import hashlib

def stable_hash(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()[:16]

with open("signin-events.csv", newline="", encoding="utf-8") as fh:
    for row in csv.DictReader(fh):
        print({
            "user": row.get("user"),
            "time": row.get("time"),
            "ip_hash": stable_hash(row.get("ip", "")),
            "asn": row.get("asn"),
            "device": row.get("device_id", "<none>"),
            "mfa": row.get("mfa_result"),
            "risk": row.get("risk_level"),
        })
Этическая рамка: материал предназначен для defensive security, легального аудита собственных систем, подготовки remediation и обучения в контролируемой среде.