← 返回 Day 160 正文

Day 160 · 图解

Day 160 — 安全日志分析:标准化与检测图解

1. 端到端流水线

flowchart LR A[多源日志文件<br/>sshd / nginx / JSON] --> B[read_lines<br/>编码错误→read_error] B --> C[detect_source<br/>JSON→nginx→sshd→unknown] C -->|unknown| U[coverage.unparsed] C -->|识别成功| D[parser: 解析出 LogEvent] D --> E{有时间戳?} E -->|否| N[coverage.no_timestamp<br/>保留事件但不参与窗口] E -->|是| F[统一时区 UTC+8] F --> G[去重: 同 ts/host/action/user/ip] G --> H[按时间排序<br/>统计 out_of_order] N --> H H --> I[七个检测器] I --> J[Alert 列表<br/>subject 掩码 + sha 指纹] U --> K[AlertReport] J --> K K --> L[JSON / Markdown] K --> M{门禁} M -->|存在 read_error / unparsed / no_timestamp / 空输入| O[exit 4] M -->|有告警 ≥ 阈值| P[exit 3] M -->|干净| Q[exit 0]
flowchart LR
    A[多源日志文件<br/>sshd / nginx / JSON] --> B[read_lines<br/>编码错误→read_error]
    B --> C[detect_source<br/>JSON→nginx→sshd→unknown]
    C -->|unknown| U[coverage.unparsed]
    C -->|识别成功| D[parser: 解析出 LogEvent]
    D --> E{有时间戳?}
    E -->|否| N[coverage.no_timestamp<br/>保留事件但不参与窗口]
    E -->|是| F[统一时区 UTC+8]
    F --> G[去重: 同 ts/host/action/user/ip]
    G --> H[按时间排序<br/>统计 out_of_order]
    N --> H
    H --> I[七个检测器]
    I --> J[Alert 列表<br/>subject 掩码 + sha 指纹]
    U --> K[AlertReport]
    J --> K
    K --> L[JSON / Markdown]
    K --> M{门禁}
    M -->|存在 read_error / unparsed / no_timestamp / 空输入| O[exit 4]
    M -->|有告警 ≥ 阈值| P[exit 3]
    M -->|干净| Q[exit 0]

灰线(unparsed / no_timestamp / read_error)与告警同样重要: 它们是"这次分析的结论有多可信"的答案。

2. 时间窗口:滑动窗口统计

失败事件(同一 host + src_ip):
  t=0s   ●
  t=10s  ●
  t=20s  ●
  t=25s  ●
  t=40s  ●
  t=45s  ●
  t=50s  ●
  t=60s  ●
  t=65s  ●
  t=70s  ●
  ...
窗口 W = 300s,阈值 = 8

滑窗前进:每当右端纳入一个新事件,就弹出所有 (now - left) > W 的旧事件。
              ┌──────────── W = 300s ────────────┐
   ...  ●  ●  ●  ●  ●  ●  ●  ●  ●  ●
                                  ↑
                          窗口内计数 = 10 ≥ 8 → 告警

为什么用滑动窗口而不是"按固定分钟分桶"?
  分桶会给攻击者一个免费的规避手法:把 9 次失败均摊到相邻两个桶(各 4~5 次),
  两个桶都低于阈值。滑动窗口看的是"任意 W 秒内",无法靠对齐边界躲开。

3. 主体键:为什么必须带 host

日志合并前的两台机器:
  web01: admin ← 203.0.113.7   (失败 6 次)
  web02: admin ← 203.0.113.7   (失败 6 次)

错误做法:按 src_ip 聚合      → 203.0.113.7 共 12 次 → 1 条告警(跨主机混淆)
正确做法:按 (host, src_ip) 聚合 → 2 条独立告警,各 6 次 → 若阈值 8 则都不告警
                                    ↑
                    这才是事实:两台机器各自被尝试了 6 次,
                    而"同一台机器被打了 12 次"是一个不存在的场景。

同理,(host, user) 不能用 user 代替:admin@web01 与 admin@web02 可能是完全不同的人,也可能连账号体系都不同。

4. 分布式爆破:per-IP 规则的盲区

单源规则(阈值 8)看到的:            账号聚合(阈值 10,来源 ≥3)看到的:
  198.51.100.1 → 3 次  ✗               svc@web01 ← {.1, .2, .3, .4, .5}
  198.51.100.2 → 3 次  ✗               600s 内 15 次失败,5 个来源
  198.51.100.3 → 3 次  ✗                        ↓
  198.51.100.4 → 3 次  ✗              告警:分布式口令猜测
  198.51.100.5 → 3 次  ✗              (severity=medium,confidence=medium:
                                        共享出口 IP / NAT 也可能长这样)

教训:规则抓不到的东西,不代表没发生。 报告里必须写清"本次跑了哪些检测器、用了什么阈值",否则读报告的人 会以为"覆盖了全部攻击手法"。

5. 可判定性决策树

flowchart TD A[有一条告警线索] --> B{该事件有时间戳?} B -->|否| C[无法计算窗口<br/>→ 只能记录,不能判定] B -->|是| D{日志覆盖完整?} D -->|否 unparsed/no_timestamp| E[结论打折扣<br/>报告必须声明] D -->|是| F{阈值回显了吗?} F -->|否| G[读者无法区分“没攻击”与“阈值太宽”] F -->|是| H[结论可复核] H --> I{同 host 才能合并?} I -->|否| J[跨主机混淆 → 假告警] I -->|是| K[进入人工复核<br/>结合值班表/变更记录/情报]
flowchart TD
    A[有一条告警线索] --> B{该事件有时间戳?}
    B -->|否| C[无法计算窗口<br/>→ 只能记录,不能判定]
    B -->|是| D{日志覆盖完整?}
    D -->|否 unparsed/no_timestamp| E[结论打折扣<br/>报告必须声明]
    D -->|是| F{阈值回显了吗?}
    F -->|否| G[读者无法区分“没攻击”与“阈值太宽”]
    F -->|是| H[结论可复核]
    H --> I{同 host 才能合并?}
    I -->|否| J[跨主机混淆 → 假告警]
    I -->|是| K[进入人工复核<br/>结合值班表/变更记录/情报]

6. 七个检测器一览

检测器 主体键 严重度 置信度 主要误报来源
brute_force_single_source (host, src_ip) high/critical high 自动化脚本用错凭据、健康检查
brute_force_distributed (host, user) medium medium NAT/共享出口、代理池正常流量
failed_then_success (host, user, src_ip) high medium 本人连续输错
login_from_unexpected_source (host, user) medium medium 出差、VPN 切换、动态 IP
login_from_single_source (host, user) low low 无基线时的启发式,几乎必然误报
off_hours_success (host, user) low low 值班、发布窗口、跨时区
web_auth_abuse (host, src_ip) medium medium 前端重试、监控探测 401
path_traversal_probe (host, src_ip) medium high 安全扫描器、爬虫抓取历史链接

置信度不是"我们有多确定",而是"只看日志能有多确定"。 没有值班表、变更记录、来源情报时,任何检测器的置信度都上不了 high—— 这些外部信息才是把 confidence 抬起来的东西。