用纯开源方案搭建生产级 SOC:Wazuh + DFIR-IRIS + 自研集成层实战记录

一份完整的现场报告:我们如何为一家中型企业用 Wazuh、DFIR-IRIS 和一个自研的 Python 中间层搭建出真正可用的安全运营中心 — 哪些设计有效、哪些坑踩过、以及那些真正影响成败的工程决策。

凡是给商用 SIEM 或 SOC 平台询过价的人都明白一件事:仅是 license 费用,往往就超过了使用这套平台的分析师的工资总和。这就是开源 SOC 方案吸引人的原因。但真正的难点不在工具本身,而在于让这些工具像一个统一的产品一样协同工作 — 大多数项目在这里就停下了。

本文是一份来自一线的工程笔记。没有 SaaS、没有云锁定,所有组件都跑在客户自己机房的 Docker 上,软件总成本为零。

特别是在中国境内运营、需要满足《网络安全法》、《数据安全法》、《个人信息保护法》(PIPL) 以及网络安全等级保护 2.0 (等保 2.0) 要求的企业,日志在本地留存、保留可审计的处置链路,已经不是技术选择题,而是合规底线。


整体架构一览

工具 职责
检测 Wazuh 4.x 日志收集、事件解码、基于规则的告警触发
案件管理 DFIR-IRIS 分析师做研判、跟踪处置流程的工作台
集成 SOC Integrator (FastAPI) 把所有组件串起来的中间层
威胁情报 VirusTotal + AbuseIPDB 在告警生成时附加 IOC 信誉数据
呼叫 PagerDuty 凌晨叫醒值班分析师
自动化 Shuffle SOAR IOC 相关的工作流自动化
日志源 FortiGate, Windows AD, VMware ESXi, Sysmon 实际被监控的系统

各组件之间的数据流如下:

flowchart TD
    A["FortiGate / Windows AD / VMware / Sysmon"] -->|"syslog / agent"| B["Wazuh Manager"]
    B -->|"解码事件 + 告警"| C["Wazuh Indexer"]
    C -->|"每5秒轮询"| D["SOC Integrator (FastAPI)"]
    D -->|"丰富数据"| E["VirusTotal + AbuseIPDB"]
    D -->|"创建告警"| F["DFIR-IRIS"]
    D -->|"呼叫值班"| G["PagerDuty"]
    D -->|"触发流程"| H["Shuffle SOAR"]
    F -->|"分析师研判"| I["KPI 仪表板"]

开源 SOC 工具链很少有人提的一个真相 — 单看每个工具,质量都不差,但它们并不是设计成"一个产品"协同工作的。集成层才是把这一切捏合成一套连贯系统的关键。


第一部分 — Wazuh: 从通用检测到贴合自身环境的检测

Wazuh 出厂自带约 3,500 条规则,覆盖了暴力破解、常见恶意软件、可疑进程等"标准"场景。但通用规则只能抓通用威胁。要检测真正发生在 你自己环境 中的事件 — 你的特权账户、你的 IP 段、你独有的异常模式 — 必须自己写规则。

自定义规则的组织方式

我们按检测设计文档(经客户确认)中的用例附录来分文件:

wazuh_cluster/rules/
  soc-a1-ioc-rules.xml          # DNS / 威胁情报命中
  soc-a2-fortigate-fw-rules.xml # FortiGate 防火墙事件
  soc-a3-fortigate-vpn-rules.xml# VPN 隧道事件
  soc-a4-windows-ad-rules.xml   # Windows 认证
  soc-b1-vmware-rules.xml       # vCenter / ESXi
  soc-b2-logmon-rules.xml       # 日志缺失监控
  soc-c1-c3-rules.xml           # 多阶段相关性检测
  soc-ioc-cdb-rules.xml         # 威胁情报 CDB 查找

每条规则都有两套版本:仿真版 (在 UAT 环境安全触发,ID 段 100xxx) 和 生产版 (按真实流量调优,ID 段 110xxx)。两套放在同一个文件里,一次 rule_test 就能验证两条路径。

<!-- 仿真版: 通过模拟器 JSON 事件触发 -->
<rule id="100341" level="10">
  <if_sid>110350</if_sid>
  <description>A4-01 [SIM] Windows 特权账户认证失败</description>
  <group>soc_sim,a4,windows,auth,</group>
</rule>

<!-- 生产版: 匹配真实 Windows 事件日志字段 -->
<rule id="110341" level="10">
  <if_group>windows</if_group>
  <field name="win.eventdata.targetUserName" type="pcre2">(?i)(admin|adm_|svc_|sa_|-adm)</field>
  <field name="win.system.eventID">^4625$</field>
  <description>A4-01 [PROD] 特权账户认证失败</description>
  <group>soc_prod,a4,windows,auth,</group>
  <mitre><id>T1110</id></mitre>
</rule>

容易被忽略的细节: location 字段

当一台设备直接向 Wazuh 发 syslog (没有装 agent),Wazuh 会把 agent.name 设成 wazuh.manager,把源 IP 存在 location 字段里。这点文档里几乎没写,我们也是踩过几小时坑才弄明白。

VMware ESXi 只能 syslog,所以必须在 location 上按 IP 过滤:

<rule id="110401" level="12">
  <if_group>vmware</if_group>
  <field name="location" type="pcre2">^172\.16\.0\.(107|108|109|110)$</field>
  <match>Login failure</match>
  <description>B1-01 [PROD] vCenter 登录失败</description>
  <mitre><id>T1110</id></mitre>
</rule>

不加这个过滤的话,任何 发送 "Login failure" 字符串的设备都会触发这条规则。把范围限定到这 4 台 ESXi 主机后,误报基本消失。

与其和内置规则对抗,不如继承它

ESXi 的 SSH 事件由 Wazuh 的 sshd 解码器处理,而非 vmware 解码器。所以 if_group=vmware 抓不到。正确做法是从已经触发的内置规则继承:

<rule id="110404" level="10">
  <if_sid>5715</if_sid>  <!-- 内置 SSH 成功规则 -->
  <field name="location" type="pcre2">^172\.16\.0\.(107|108|109|110)$</field>
  <description>B1-04 [PROD] ESXi SSH 登录成功 — 确认是否授权</description>
  <mitre><id>T1021.004</id></mitre>
</rule>

规则 5715 已经把事件解析好了,我们只需要把范围收窄到目标主机。

通过 CDB 列表实现威胁情报查找

Wazuh 的 CDB(常量数据库)以 O(log n) 复杂度在平铺文件里查值。我们维护三份列表 — 恶意 IP、恶意域名、恶意哈希 — Integrator 每 4 小时刷新一次,从 VirusTotal 和 AbuseIPDB 拉取最近 30 天确认命中的 IOC。

<rule id="110600" level="13">
  <if_sid>22101</if_sid>
  <list field="data.srcip" lookup="match_key">etc/lists/malicious-ioc/malicious-ip</list>
  <description>FortiGate 源 IP 命中威胁情报列表</description>
  <mitre><id>T1071</id></mitre>
</rule>

让我们浪费几小时的 Docker 坑

Wazuh 官方镜像把规则放在挂载到 /var/ossec/etc 的命名卷里。如果你试图用 docker cpsed -i 改规则文件,要么静默失败,要么报 "Device or resource busy" — 命名卷的 inode 优先级更高。

唯一能在不重启容器的前提下原地更新规则的可靠方法,是从容器内部用 Python 的 open().write() 写文件:

docker exec wazuh.manager python3 -c "
with open('/var/ossec/etc/rules/soc-a4-windows-ad-rules.xml', 'w') as f:
    f.write('''<group name=\"soc_mvp,...\">...</group>''')
"
docker exec wazuh.manager /var/ossec/bin/wazuh-control reload

任何会触碰 Wazuh 生产规则的 CI/CD 流水线都应该把这一点写进规范里。


第二部分 — Integrator: 让一堆工具变成一个产品的那层

这是把 Wazuh、IRIS 和其他工具从"四个独立系统"变成"一套整体方案"的核心。它是一个 FastAPI 服务,负责 5 件事。

1. 告警同步

每 5 秒,Integrator 查询 Wazuh Indexer,拉取匹配生产规则 (rule.id:[110301 TO 110602]) 的新事件,做归一化,然后在 IRIS 里创建对应告警。用复合键去重,避免同一事件淹没队列。

2. IOC 富化

对于威胁情报类规则,Integrator 在 创建 IRIS 告警之前 就调 VirusTotal 和 AbuseIPDB。分析师打开告警的瞬间就能看到 — 信誉分、威胁分类、最后命中日期。不用切标签页,不用复制粘贴。

3. 多阶段相关性检测

有些攻击只有把多个事件放在一起看才能发现。Wazuh 规则是无状态的,所以我们在 PostgreSQL 里跑了一个相关性引擎。最经典的例子是"不可能旅行" — 同一个用户从两个物理上不可能在已过时间内到达的城市做了 VPN 登录:

async def _detect_impossible_travel(self, event: dict) -> dict | None:
    # ... 查这个用户的上一次登录 ...
    distance_km = haversine(loc1, loc2)
    min_hours = distance_km / 900  # 商用航班最大速度
    actual_hours = (ts2 - ts1).total_seconds() / 3600
    if actual_hours < min_hours:
        return {"confirmed": True, "distance_km": distance_km, ...}

如果北京 10:00,法兰克福 11:30,那是物理上不可能的。Integrator 确认 C1 检测,在 IRIS 里生成一条高严重度告警,并把两次登录事件作为附件挂上。

4. 分组去重

一次密码喷洒攻击就能在一小时内产生数千条 Windows 4625 事件。如果每条都变成 IRIS 告警,队列直接废掉。所以我们按 (rule_id, user, host) 分组,每条规则配自己的冷却时间:

_GROUP_DEDUP_RULES: dict[str, int] = {
    "110341": 2,   # 特权账户认证失败 — 2 小时
    "110342": 2,   # 服务账户认证失败 — 2 小时
    "110344": 4,   # 公网 IP 认证失败 — 4 小时
    "110347": 4,   # runas / 权限假冒 — 4 小时
    "110359": 1,   # 密码喷洒 — 1 小时
}

第一条匹配 (rule_id, user, host) 的事件创建 1 条 IRIS 告警。冷却期内的后续事件只更新 last_seen 并写入 incident_events 表用于后续取证,不重复创建告警。关键点是:抑制逻辑是显式且可审计的 — 后续可以查到哪些事件被抑制了、为什么被抑制。这一点在等保 2.0 测评和 PIPL 合规审计时尤其重要。

5. 基于 SLA 的 KPI 跟踪

每条告警都有按严重度计时的 SLA:

严重度 SLA 目标
紧急 (Critical) 1 小时
高 (High) 4 小时
中 (Medium) 8 小时
低 (Low) 24 小时

IRIS 仪表板每条告警显示一个实时进度条 — 绿色代表在 SLA 内、黄色代表超过 75%、红色代表已逾期。管理层拿到的是诚实的数字,分析师看到的是真正紧急的事件。

每条告警还附带一份从 YAML 在启动时加载的处置指引:

guidelines:
  110346:
    use_case: "A4-06 — 公网 IP 认证成功"
    steps:
      - "立即识别用户与源 IP"
      - "确认是否为预期内行为(远程办公、出差)"
      - "检查会话内是否有特权操作"
      - "若未授权: 强制登出、重置凭证、边界封禁 IP"

分析师打开告警就能看到处置剧本,不需要去 wiki 找运维手册。

为什么不用 Wazuh 自带的 Active Response?

Wazuh 有个 "Active Response" 框架,可以在规则匹配时执行脚本。我们没用。四个原因:

  1. 状态 — 相关性检测需要历史数据。Active Response 是无状态的
  2. 异步 I/O — 在 async Python 里并行调用 VirusTotal 和 AbuseIPDB 很自然,在 shell 里就很痛苦
  3. 可测试性 — Integrator 有完整的 REST API。任意事件都可以通过 POST /monitor/wazuh/ingest 重放,每一步决策都能复盘
  4. 解耦 — IRIS、PagerDuty、Shuffle 都可替换。Wazuh 完全不知道它们的存在

27 秒仪表板事件

刚上线时,IRIS KPI 仪表板加载要 27 秒。直接调端点很快(128 毫秒),但走代理链就慢。

根本原因是每次数据库调用都用 psycopg.connect() — 这是一个同步阻塞调用(每次约 34 毫秒,主要花在 TCP 握手和认证上)。Auto-sync 每 5 秒处理约 437 个事件,每个事件 2 次数据库调用:

437 个事件 × 2 次 DB 调用 × 34 毫秒 = 约 30 秒事件循环被堵

这段时间里到达的任何 HTTP 请求都得排在 sync 后面。修复方法是把"每次调用建一个新连接"改成"持久连接池":

# 修复前: 每次都建新 TCP 连接
@contextmanager
def get_conn():
    with psycopg.connect(db_dsn(), ...) as conn:
        yield conn

# 修复后: 从连接池借连接 — 每次约 1 毫秒
_pool: ConnectionPool | None = None

def init_pool() -> None:
    global _pool
    _pool = ConnectionPool(db_dsn(), min_size=2, max_size=10, ...)

@contextmanager
def get_conn():
    with _pool.connection() as conn:
        yield conn

仪表板加载时间: 27 秒 → 0.2 秒。教训: 任何按请求做数据库读写的 FastAPI 服务,从第一天就用异步连接池。

闭合反馈环

Integrator 确认多阶段检测(比如"不可能旅行")后,会把一条结构化的 syslog 消息回送给 Wazuh。Wazuh 有一条规则匹配这条消息,在自己的仪表板上触发一条 level-15 告警。这样,在 Wazuh 上工作的分析师和在 IRIS 上工作的分析师都能看到同一个事件。哪个工具都不是"主"的。


第三部分 — DFIR-IRIS: 不 fork 也能定制

DFIR-IRIS 出厂就很扎实 — 告警、案件、IOC、时间线、报告都齐。我们做了两处扩展,上游代码只动了一行。

用 Flask Blueprint 做 KPI 仪表板

IRIS 是 Flask 应用,加新页面很直接 — 写一个 Blueprint,注册一次就行。

@kpi_dashboard_blueprint.route('/kpi-dashboard')
@ac_requires(no_cid_required=True)
def kpi_dashboard(caseid, url_redir):
    return render_template('kpi_dashboard.html', csrf_token=generate_csrf())

@kpi_dashboard_blueprint.route('/kpi-dashboard/api/alerts')
@ac_api_requires(Permissions.alerts_read)
def proxy_list_alerts():
    content, status, _ = _soc_get('/iris/alerts', request.args)
    return Response(content, status=status, content_type='application/json')

前端用 Alpine.js — 体量小、不需要构建步骤、轮询代理端点,渲染带 SLA 计时器和一键分配的实时告警列表。

结构化告警备注

不用自由文本,每条 IRIS 告警都带一份固定结构的 JSON 备注:

{
  "rule": {
    "id": "110344",
    "description": "公网 IP Windows 认证失败",
    "mitre": ["T1110"]
  },
  "asset": {
    "hostname": "FPFTPSRV02",
    "ip": "172.16.10.50",
    "os": "Windows"
  },
  "network": {
    "src_ip": "91.202.x.x",
    "dst_ip": "172.16.10.50",
    "protocol": "TCP"
  },
  "guideline": {
    "use_case": "公网 IP 认证失败",
    "steps": ["识别源 IP 和地理位置", "..."]
  }
}

UI 上人看得懂,过滤和报告时机器也能解析。分析师可以按 rule.idasset.hostname 检索;管理者可以按 MITRE 技术分组生成趋势报告 — 这对等保 2.0 的安全运维报表也是直接可用的输出。

怎么定制 IRIS 又不 fork 它

直接 fork 项目、改源码、上线 — 这是最容易的诱惑。代价是 6 个月之后,上游发新版,你的 fork 已经漂移到合不回去了。

我们的做法:

  • 所有自定义 Blueprint 放在 bind-mount 进容器的目录里
  • 前端单独编译,作为静态包注入
  • 上游 __init__.py 里只动一行,用来注册我们的 Blueprint

那一行就是 IRIS 升级时唯一需要重新打的补丁。其他改动都是增量的、不侵入的。


第四部分 — 运营层面的经验

磁盘消耗会让你意外

logall=yes 会把所有解码事件都归档,不只是触发告警的那部分。做取证深挖时很有用,但对磁盘空间不友好。一次例行的 VMware 排查就把磁盘使用率推到 86%。现在我们默认关掉,只在做针对性调查时临时开启:

<global>
  <logall>no</logall>
  <logall_json>no</logall_json>
</global>

从 PIPL 合规角度看,日志保留时长应当是一项明确的策略,而不是磁盘容量的副产品。

覆盖度 vs 告警疲劳是可调参数,不是非此即彼

抑制吵闹规则只是表面解。更好的方案是上面提到的分组去重模式 — 每个事件仍然被记录,但每个冷却窗口里只有第一条会创建告警。覆盖度不丢、审计链不断、分析师队列保持可用。

监控你自己的 SOC

Integrator 每 2 分钟自检一次它自己的依赖 — Wazuh Manager、Wazuh Indexer、IRIS、Shuffle、PagerDuty。任何一个不在线就在 IRIS 里以 system-health 类别创建告警。分析师在同一个队列里就能看到基础设施故障和安全告警,不需要再装一套监控工具。

邮件依然是最可靠的兜底通知

紧急 IRIS 告警会向 SOC 团队和备用邮箱发邮件,内容包含告警标题、严重度、资产、IRIS 直链。仪表板会挂,收件箱不会。


数字

指标 数值
自定义 Wazuh 规则 86 条 / 8 个文件
已在生产触发的规则 64 条中的 17 条
处理过的 IRIS 告警 91,000+
Auto-sync 周期 每 5 秒
IOC 列表刷新 每 4 小时
KPI 仪表板加载 (修复前) 27 秒
KPI 仪表板加载 (修复后) 0.2 秒
数据库调用开销 (修复前 / 后) 34 毫秒 / 1 毫秒以下

重做一次会改的事

第一天就上异步数据库: 在 FastAPI 应用里用同步版 psycopg.connect() 是埋好的性能炸弹。在写第一个 repository 之前,就用 psycopg_pool.AsyncConnectionPool

Wazuh 规则测试纳入 CI: rule_test API 接受原始日志行,返回触发的规则。把它包进 pytest fixture 就能在上线前发现规则冲突。

仿真和生产用不同的 syslog 端口: 我们把两边都塞到 514 端口,导致必须维护 soc_sim/soc_prod 两套规则。换不同端口会简洁得多。


总结

完全跑在开源软件上的生产 SOC,不仅是可行的,而且是扎实的工程实践。但开源工具本身不构成产品。集成层 — 那个没人拿去做营销的部分 — 才是真正的 SOC 所在。

如果你正在评估商用 SIEM,而 license 报价让你坐立不安,这套技术栈是一个货真价实的替代方案。难点不在工具,难点在"胶水"。

对于在中国境内运营、需要满足《网络安全法》、《数据安全法》、《个人信息保护法》(PIPL) 以及网络安全等级保护 2.0 (等保 2.0) 要求的企业,这套架构还有几个额外价值 — 日志全程在自有数据中心、抑制逻辑可被合规审计、不需要把敏感数据外送到境外云。


Simplico 专注基于开源构建生产级安全系统。如果您正在规划 SOC、升级现有检测体系,或试图摆脱 SIEM 的 license 重压,欢迎与我们联系


Get in Touch with us

Chat with Us on LINE

iiitum1984

Speak to Us or Whatsapp

(+66) 83001 0222

Related Posts

Our Products