从零构建SOC:Wazuh + IRIS-web 真实项目实战报告
历时三周、逐条commit的实录:使用Wazuh 4.x、IRIS-web和自研FastAPI集成器从零搭建生产级安全运营中心——检测规则、告警流水线、IOC情报富化,以及那些永远不会出现在架构图里的基础设施坑点。
技术栈: Wazuh 4.x · IRIS-web · soc-integrator (FastAPI) · OpenSearch · Docker Compose · VirusTotal API · AbuseIPDB
flowchart TD
subgraph "Log Sources"
A["Windows Agent"]
B["FortiGate Syslog"]
C["Simulator Scripts"]
end
subgraph "Wazuh"
D["Wazuh Manager"]
E["Decoders + Rules"]
F["OpenSearch Indexer"]
end
subgraph "soc-integrator (FastAPI)"
G["Alert Poller"]
H["Severity Filter"]
I["IOC Enricher"]
J["WazuhSyslogAdapter"]
K["Webhook Receiver"]
L["Email Notifier"]
end
subgraph "External Threat Intel"
M["VirusTotal"]
N["AbuseIPDB"]
O["Feodo / URLhaus / ThreatFox"]
end
subgraph "IRIS-web"
P["IRIS Alerts"]
Q["Cases + Triage"]
R["Outbound Webhook"]
end
A --> D
B --> D
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> M
I --> N
O --> I
I --> P
P --> Q
Q --> R
R --> K
K --> L
J --> D
I --> J
第一周:从一张白纸开始(3月13日–16日)
每个SOC项目都以同样的方式开始:光标在屏幕上闪烁,面前是一份需要检测的威胁场景清单。
我们的起点是一份内部需求规格书——三个用例附录:
- 附录A — Windows 与 Active Directory 滥用
- 附录B — 网络与防火墙事件(FortiGate)
- 附录C — 身份安全:impossible travel、credential abuse、管理员横向移动
最初的commit毫不起眼:添加multipart依赖、提交三个附录的样本日志文件——137行来自接近生产环境的系统收集的防火墙、认证和Windows事件数据。随后,一个pass.txt出现在仓库里:早期SSH会话的凭据笔记和测试输出。
3月16日,第一个真正的里程碑达成:test-firewall-syslog.py脚本——能够向Wazuh的514端口发送FortiGate格式的UDP syslog数据包,覆盖10种不同场景。
Docker NAT问题
--via-docker标志几乎是立即加入的——第二天就发现了这个问题。没有它,每个数据包到达Wazuh时,源IP都是Docker网关IP而非实际主机IP,导致基于源IP的规则匹配完全失效。该标志强制数据包通过宿主机网络栈路由,让Wazuh看到正确的来源IP。
如果你正在Docker内构建Wazuh模拟环境,却发现源IP规则从不触发——原因就在这里。
flowchart TD
A["test-firewall-syslog.py"]
B{"--via-docker flag?"}
C["Packet exits via Docker NAT"]
D["Wazuh sees Docker gateway IP"]
E["Source-IP rules never match"]
F["Packet exits via host network"]
G["Wazuh sees real host IP"]
H["Source-IP rules match correctly"]
A --> B
B -- "No" --> C
C --> D
D --> E
B -- "Yes" --> F
F --> G
G --> H
第二周:规则、解码器与OR陷阱(3月17日–22日)
检测工程,说白了就是不断重写你以为已经写对的东西。
3月17日涌现出一批带有progress、progress-update、rule update标签的commit。附录A、B、C共59条模拟规则正在成形——但有些不对劲:A2和A3规则在不该触发的时候触发了。
根本原因:多<match>的OR陷阱
Wazuh的XML规则语法允许在单条规则内写多个<match>标签。大多数工程师默认这是AND逻辑。但在Wazuh 4.x的某些解码器链中,它实际表现为OR。
一条本应只在action=deny 且 logid=13同时出现时才触发的规则,有时仅满足其中一个条件就会触发。
<!-- 错误:在某些解码器链中表现为OR -->
<rule id="100201" level="10">
<match>action=deny</match>
<match>logid=13</match>
<description>Firewall block — specific log ID</description>
</rule>
<!-- 正确:用单条regex强制实现AND逻辑 -->
<rule id="100201" level="10">
<regex>action=deny.*logid=13|logid=13.*action=deny</regex>
<description>Firewall block — specific log ID</description>
</rule>
3月22日的commit用一句话描述了修复:
"fix A2/A3 rule OR-trap: replace multi-
<match>with single<regex>lookaheads."
这一行commit背后,是整整一个下午反复用wazuh-logtest喂入样本事件、眼睁睁看着规则对本该忽略的输入做出响应的排查过程。如果你的Wazuh规则触发范围比预期宽泛,检查一下同一条规则内是否存在多个<match>标签。
flowchart TD
subgraph "WRONG: multi-match behaves as OR"
A1["Incoming log event"]
B1["match: action=deny"]
C1["match: logid=13"]
D1["Rule fires if EITHER matches"]
A1 --> B1
A1 --> C1
B1 --> D1
C1 --> D1
end
subgraph "CORRECT: single regex enforces AND"
A2["Incoming log event"]
B2["regex: action=deny AND logid=13"]
C2["Rule fires only when BOTH match"]
A2 --> B2
B2 --> C2
end
第三周:打通Wazuh与IRIS的链路(3月23日–25日)
让Wazuh规则触发并在日志文件里看到结果,是一回事。把告警送入分析师可以进行研判的工单管理平台,是完全不同的另一个问题。
soc-integrator流水线
答案是soc-integrator——一个坐落在Wazuh与IRIS-web之间的FastAPI服务:
flowchart TD
A["Raw Security Event"]
B["Wazuh Manager"]
C["Decoder Chain"]
D["Rule Matching"]
E["OpenSearch Indexer"]
F["soc-integrator Poller"]
G{"Severity >= threshold?"}
H["Discard (noise)"]
I["Create IRIS Alert"]
J["IRIS-web Case Queue"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G -- "No" --> H
G -- "Yes" --> I
I --> J
集成器的核心功能:
- 每隔N秒轮询Wazuh Indexer(OpenSearch)
- 仅转发达到或超过可配置严重级别阈值的告警(默认:medium及以上——低于此的均视为噪声)
- 提供GET/PUT接口,允许分析师在不重启服务的情况下实时调整阈值
- 生成携带完整事件元数据的结构化IRIS告警
3月23日,一个7步端到端测试脚本验证了从原始事件到IRIS告警的完整链路。
一天内修复三处时区问题
所有容器都在跑UTC。身处曼谷(ICT,UTC+7)的分析师看到的时间戳偏差了七小时。
- 标准容器:在Docker Compose的env块中添加
TZ=Asia/Bangkok Go/scratch基础镜像:构建时时区数据库被裁剪掉了——需要以volume形式显式挂载- 附加修复:在IRIS导航栏添加ICT/UTC双时钟小组件
分析师立刻就注意到了。小改动,实际影响不小。
IOC情报富化与弃用Shuffle SOAR
3月24日之前,威胁情报还是纯手工操作——分析师手动查询可疑IP。新的IOC流水线彻底取代了这一方式:
按需查询:
- VirusTotal——IP/域名/哈希信誉查询
- AbuseIPDB——IP滥用历史记录
后台订阅源摄入:
- Feodo Tracker — C2基础设施
- URLhaus — 恶意URL
- ThreatFox — IOC聚合
- MalwareBazaar — 恶意软件哈希
Wazuh CDB列表文件(malicious-ip、malicious-domains、malware-hashes)通过Wazuh API重新生成并热重载。新增规则110600–110602负责内联CDB匹配。
flowchart TD
subgraph "Ad-hoc Lookups"
A["Suspicious IP / Domain / Hash"]
B["VirusTotal API"]
C["AbuseIPDB API"]
A --> B
A --> C
end
subgraph "Background Feed Ingestion"
D["Feodo Tracker (C2 IPs)"]
E["URLhaus (Malicious URLs)"]
F["ThreatFox (IOC Aggregator)"]
G["MalwareBazaar (Hashes)"]
end
subgraph "Wazuh CDB Hot-Reload"
H["malicious-ip list"]
I["malicious-domains list"]
J["malware-hashes list"]
K["Wazuh API reload trigger"]
H --> K
I --> K
J --> K
end
subgraph "Detection Rules"
L["Rule 110600: IP match"]
M["Rule 110601: Domain match"]
N["Rule 110602: Hash match"]
end
B --> H
C --> H
D --> H
E --> I
F --> H
F --> I
G --> J
K --> L
K --> M
K --> N
Shuffle SOAR被彻底移除。 直接调用API更快、更简单,也不需要维护一个独立的工作流平台。
私有IP泄漏
早期测试中,集成器将192.168.x.x和10.x.x.x地址提交给了VirusTotal,消耗API配额并因内部扫描流量触发大量429限速错误。
解决方案:在调用任何外部富化接口前,先过滤掉RFC1918和回环地址。调用外部威胁情报API之前,务必检查并排除私有IP。
磁盘告急、仪表板一无所显、同步噪声
logall_json的灾难
开发调试期间开启的logall_json配置,在生产环境中每天写入14GB的archives.json。
解决方案:关闭logall_json;应用OpenSearch ISM策略,30天后自动删除旧索引;在容器内OS层面添加日志轮转。
什么都不显示的仪表板
附录C仪表板的过滤条件是rule.id:1005*——开发阶段的模拟规则ID范围。而生产环境中真实的检测规则落在110xxx区间,仪表板因此对真实事件一无所返。
解决方案:从规则ID过滤切换为rule.groups:appendix_c。基于分组的过滤方式不会受规则ID变动影响。
同步过滤器中的事件类型不匹配
Wazuh→IRIS同步曾依赖event_type文本字段做过滤,而这个字段在Wazuh agent产生的真实Windows事件中根本不存在——它只是模拟阶段的产物。
解决方案:将过滤器重构为显式规则ID的frozenset。明确、确定、易于代码审查。
第四周:Webhook、邮件通知与闭环(3月28日)
3月28日是整个项目密度最高的一天。
IRIS Webhook接收器
当IRIS创建或更新告警时,值班分析师如何得知?答案是在soc-integrator中实现一个webhook接收器。IRIS通过其模块系统支持出站webhook。
flowchart TD
A["IRIS Alert created or updated"]
B["IRIS outbound webhook module"]
C["POST /iris/webhook (soc-integrator)"]
D["Parse alert payload"]
E["Enrich: entity name + event type"]
F["Resolve IRIS_EXTERNAL_URL"]
G["Format email body"]
H["smtplib send"]
I["On-call analyst inbox"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
邮件通知在一天内经历了三次迭代:
| 版本 | 变更内容 |
|---|---|
| v1 | 仅简单通知:"有事件到达" |
| v2 | 主题明确化:"A1-02 Brute Force"而非"IRIS Event" |
| v3 | 完整上下文:告警ID、标题、关联工单、直达链接 |
.env内联注释解析Bug
IRIS_EXTERNAL_URL=http://10.0.0.5 # production host被整体解析为http://10.0.0.5 # production host。环境变量行末的内联注释悄无声息地污染了变量值。
解析.env文件时务必剥离内联注释,或使用能正确处理此类情况的解析器,例如Python的python-dotenv。
模拟与生产的解码器分叉
来自Windows Wazuh agent的真实事件使用windows_eventchannel解码器链;通过syslog注入的模拟事件使用JSON解码器。两者完全互斥——挂载在windows_eventchannel链上的规则永远不会响应模拟事件。
flowchart TD
A["Incoming Windows Event"]
B{"Source type?"}
C["Real Wazuh Agent"]
D["Syslog-injected Simulator"]
E["windows_eventchannel decoder"]
F["JSON decoder"]
G["Production anchor rule"]
H["Simulation anchor rule 100270"]
I["16 A4 Production rules"]
J["16 A4 Simulation rules"]
A --> B
B -- "Agent" --> C
B -- "Simulator" --> D
C --> E
D --> F
E --> G
F --> H
G --> I
H --> J
解决方案:为JSON解码器路径创建锚规则(100270),将A4的16条模拟规则全部指向该锚规则,而非生产锚规则。
JSON格式化是检测流水线的一部分
Windows事件的告警描述在IRIS中以单行压缩JSON呈现。需要在两处同时修复:
- 集成器侧:检测到JSON字符串后,在写入IRIS前以2空格缩进格式化
- 前端侧:
<span>元素会折叠空白字符——改用<pre style="white-space:pre-wrap">,格式化后的JSON才能正确渲染
以单行压缩JSON呈现的告警描述会被忽略;同样的数据加上缩进,才会被阅读和处理。呈现方式是检测流水线的组成部分。
第五周:闭合反馈环路与消灭误报(3月31日–4月1日)
闭合Wazuh反馈环路
C系列检测(C1 impossible travel、C2 credential abuse、C3 lateral movement)的检测逻辑运行正确,但处于"沉默"状态——集成器确认匹配并创建IRIS告警,而Wazuh本身对此毫不知情。
这带来两个实际问题:
- Wazuh中的
level 15规则应在攻击被确认后触发,而不只是对原始事件响应 - Wazuh不知道确认检测发生了,SOC仪表板就无法如实展示
解决方案:WazuhSyslogAdapter——集成器内部一个小巧的UDP发送器。确认C1匹配后,集成器将结构化syslog事件回送至Wazuh:
soc_event=correlation event_type=c1_impossible_travel user="..." src_ip=...
Wazuh通过soc-prod-integrator解码器解析,触发锚规则100260,最终以level 15(已确认严重)触发规则110502。环路闭合,仪表板如实呈现。
flowchart TD
A["Login event from two locations"]
B["Wazuh raw rule fires (low level)"]
C["OpenSearch Indexer"]
D["soc-integrator poller"]
E["C1 correlation logic"]
F{"Impossible travel confirmed?"}
G["Discard"]
H["Create IRIS Alert"]
I["WazuhSyslogAdapter"]
J["UDP syslog back to Wazuh port 514"]
K["soc-prod-integrator decoder"]
L["Anchor rule 100260"]
M["Rule 110502 fires (level 15 critical)"]
N["SOC dashboards updated"]
O["Email notification sent"]
A --> B
B --> C
C --> D
D --> E
E --> F
F -- "No" --> G
F -- "Yes" --> H
H --> I
I --> J
J --> K
K --> L
L --> M
M --> N
H --> O
非工作时间检测的时区错误
C2的非工作时间检测窗口被配置为20:00–06:00 UTC。听起来合理——直到换算成本地时间:
20:00 UTC=03:00 ICT(曼谷)- 曼谷工作时间从
08:00 ICT=01:00 UTC开始
规则在正常上班时间段内持续触发。
修正后的窗口:11:00–01:00 UTC = 18:00–08:00 ICT。时间类检测规则务必先按分析师所在时区设定,再转换为UTC。
C3-03:每日24次误报 → 归零
规则C3-03检测通过RDP type-3登录实施的管理员横向移动,但每天在FPBIADFS01上触发24次。
根本原因:AD FS持续执行无源IP的服务间type-3认证——完全符合规则的检测模式,但来自已知安全的服务账号。
修复:添加单一守卫条件——ipAddress必须存在且非回环地址。AD FS服务认证没有源IP,因此被干净过滤掉;真实横向移动携带远程源IP,仍会正常触发规则。
flowchart TD
A["Type-3 logon event detected"]
B{"ipAddress present and non-loopback?"}
C["FPBIADFS01 service auth (no IP)"]
D["Rule suppressed — false positive avoided"]
E["Remote admin session (real IP)"]
F["C3-03 fires — lateral movement alert"]
A --> B
B -- "No" --> C
C --> D
B -- "Yes" --> E
E --> F
一个条件。误报率:归零。
没有写进任何Commit的基础设施备忘
macOS bind-mount的inode问题
Docker on macOS通过inode追踪bind-mount的文件。当编辑器在保存时创建新inode(sed -i或部分IDE的常见行为),容器仍从旧inode读取。症状:修改Wazuh规则、重载Wazuh、测试——旧行为依然存在。
修复:在macOS上修改bind-mount的配置文件后,必须执行docker compose up --force-recreate。第一天就写进README。
wazuh-logtest stdin挂起
通过docker exec -i将20行测试文件pipe进wazuh-logtest,会在最后一个事件的phase 2之后卡住。
修复:将测试内容写入容器内的临时文件后重定向,或给命令调用加上超时。
通过docker exec传输大文件
部分主机上通过docker exec传输大文件时,pipe在约64KB处被截断。
修复:在宿主机对文件进行base64编码,将编码后的字符串pipe进容器,在容器内用Python解码。
当前状态
截至2026年4月1日
| 模块 | 状态 |
|---|---|
| 附录A — Windows/AD模拟 | 59条规则,全部端到端触发验证通过 |
| 附录B — 网络/防火墙 | 生产规则运行中,FortiGate syslog正常接入 |
| 附录C — 身份安全(C1/C2/C3) | 检测 + Wazuh反馈环路已闭合 |
| Wazuh → IRIS同步 | 运行中,按严重级别和规则ID双重过滤 |
| IOC情报富化 | VT + AbuseIPDB + 4个威胁订阅源,CDB热重载 |
| 邮件通知 | 每次IRIS webhook事件触发,携带完整告警上下文 |
| 误报控制 | C3-03 ADFS守卫、C2时区修正、登录类型过滤 |
| 日志保留 | Wazuh 3天,OpenSearch ISM策略30天 |
核心经验
1. 检测工程是迭代,不是积累
添加规则的commit和修复其误报的commit同等重要。一条对所有事件都触发的规则,比没有规则更糟——它会训练分析师忽略告警,从根本上动摇SOC存在的意义。
2. 反馈环路至关重要
进了IRIS但没有回写到Wazuh的检测,只是半个检测。SIEM必须知道关联引擎确认了什么,否则仪表板在说谎,分析师对平台的信任会逐渐瓦解。
3. 小的基础设施Bug会累积成大问题
时区错误、.env内联注释解析Bug、向VirusTotal提交私有IP——这些单独看都不会搞垮演示。但在生产环境中,它们制造持续的背景噪声,侵蚀团队对平台的信心。趁早修,别等它们变成"已知问题"后被默默忽视。
4. 始终检查Wazuh规则中的OR陷阱
单条Wazuh规则中的多个<match>标签,在特定解码器链上下文中可能表现为OR逻辑。如果规则触发范围超出预期,将其整合为单条带有明确AND逻辑的<regex>。
5. JSON格式化不是锦上添花
以单行压缩JSON呈现的告警描述,会被快速扫过然后忽略。同样的数据加上合适的缩进,才会被认真阅读并触发行动。呈现方式是检测流水线不可或缺的一环。
延伸阅读
- Wazuh 规则语法文档
- IRIS-web (DFIR-IRIS) 文档
- OpenSearch ISM 策略
- Feodo Tracker · URLhaus · ThreatFox · MalwareBazaar
作者:Simplico Co., Ltd. — 总部位于曼谷的软件工程与产品工作室,专注于AI/RAG系统、网络安全/SOC解决方案及企业系统集成,服务泰国、日本、中国及全球英语市场。
技术栈:Wazuh 4.x · IRIS-web · soc-integrator (FastAPI) · OpenSearch · Docker Compose · macOS开发环境
Get in Touch with us
Related Posts
- Payment API幂等性设计:用Stripe、支付宝、微信支付和2C2P防止重复扣款
- Idempotency in Payment APIs: Prevent Double Charges with Stripe, Omise, and 2C2P
- Agentic AI in SOC Workflows: Beyond Playbooks, Into Autonomous Defense (2026 Guide)
- Building a SOC from Scratch: A Real-World Wazuh + IRIS-web Field Report
- 中国品牌出海东南亚:支付、物流与ERP全链路集成技术方案
- 再生资源工厂管理系统:中国回收企业如何在不知不觉中蒙受损失
- 如何将电商平台与ERP系统打通:实战指南(2026年版)
- AI 编程助手到底在用哪些工具?(Claude Code、Codex CLI、Aider 深度解析)
- 使用 Wazuh + 开源工具构建轻量级 SOC:实战指南(2026年版)
- 能源管理软件的ROI:企业电费真的能降低15–40%吗?
- The ROI of Smart Energy: How Software Is Cutting Costs for Forward-Thinking Businesses
- How to Build a Lightweight SOC Using Wazuh + Open Source
- How to Connect Your Ecommerce Store to Your ERP: A Practical Guide (2026)
- What Tools Do AI Coding Assistants Actually Use? (Claude Code, Codex CLI, Aider)
- How to Improve Fuel Economy: The Physics of High Load, Low RPM Driving
- 泰国榴莲仓储管理系统 — 批次追溯、冷链监控、GMP合规、ERP对接一体化
- Durian & Fruit Depot Management Software — WMS, ERP Integration & Export Automation
- 现代榴莲集散中心:告别手写账本,用系统掌控你的生意
- The Modern Durian Depot: Stop Counting Stock on Paper. Start Running a Real Business.
- AI System Reverse Engineering:用 AI 理解企业遗留软件系统(架构、代码与数据)













