diagonalciso/wazuh-attackmap
GitHub: diagonalciso/wazuh-attackmap
一个基于纯 Python 的 Norse 风格实时攻击地图,通过读取 Wazuh Indexer 中的真实告警数据,在浏览器全屏画布上以动画弧线可视化攻击者的地理位置与攻击类型。
Stars: 1 | Forks: 1
# wazuh-attackmap
[](https://github.com/diagonalciso/wazuh-attackmap/actions/workflows/lint.yml)
[ Wazuh ](https://wazuh.com) 的 Norse 风格**实时攻击地图**。这是一个独立的、
无依赖的 Python 服务,它会轮询您本地的 `wazuh-indexer` 以获取携带可路由源 IP 的告警,
对每个 IP 进行地理定位,并将动画弧线
(攻击者来源 → 您的传感器 "home")流式传输到全屏画布页面。

*上图:演示模式下的地图。弧线按攻击类型着色,并汇聚于
配置的传感器 home(示例:荷兰阿姆斯特丹)。*
与绘制来自公共互联网的已知恶意基础设施的通用威胁源地图不同,该地图的弧线来源于
**您自己的 Wazuh 告警** —— 因此
每一条弧线都是针对您受监控机群记录的真实事件,而非背景噪音。
## 为什么选择它
- **真实归因,而非数据源。** 每条弧线 = 一条带有公共
`data.srcip` 的 Wazuh 告警。它展示了究竟是谁在攻击*您的*传感器。
- **零依赖。** 纯 Python 3 标准库。无需 pip,无需 Node,无需数据库。一个
文件即可运行服务器;页面是一个单一的自包含 HTML/canvas。
- **只读。** 使用只读账户连接到 `wazuh-indexer`。它绝不会
向 Wazuh 写入数据,也不需要 manager API 访问权限。
- **不受升级影响。** 它*不是*一个 OpenSearch Dashboards 插件,因此 Wazuh
升级不会破坏它。它只与 indexer 的搜索 API 通信。
## 工作原理
```
wazuh-indexer (wazuh-alerts-*)
│ read-only "monitor" account, basic-auth over TLS
▼
mapserver.py ──► attackmap.py poller ──► geo.py (mmdb / synthetic)
│ │
│ ▼
│ arc events (deduped by timestamp watermark)
▼ │
HTTP :8100 ◄───────────┘
/attackmap (canvas page) /api/attackmap/stream (SSE)
```
每隔 `MAP_POLL_INTERVAL` 秒,轮询器会搜索比它上次看到的更新的告警
(`range > watermark`),并且这些告警必须包含 `data.srcip`。每个匹配项都会被
转换为一个事件:源 IP 被进行地理定位,`rule.groups` 被映射
到对应的攻击类型桶(bruteforce / webattack / malware / ransomware / ddos /
recon / intrusion / other),然后该事件会通过 Server-Sent Events 推送到所有已连接的浏览器。最后由浏览器绘制出弧线。
## 文件
| 文件 | 用途 |
|------|---------|
| `mapserver.py` | 独立的 HTTP 服务器 + `LocalIndexerClient` (basic-auth → `https://127.0.0.1:9200`)。**生产环境入口。** |
| `attackmap.py` | Emitter、告警轮询器、geo bucketing、SSE、`/attackmap` 画布页面以及 `MAP_DEMO` 合成数据生成器。 |
| `geo.py` | 内置的 MaxMind mmdb 读取器 + 合成数据回退。 |
| `world.geojson` | 内置的低分辨率世界轮廓图,供画布使用。 |
| `demo.py` | 使用**合成**数据运行地图,无需 Wazuh(`python3 demo.py`)。 |
| `tools/render_shot.py` | 渲染静态主图 PNG(无需浏览器)—— 用于生成 `docs/screenshot.png`。 |
| `wazuh-attackmap.service` | systemd unit。 |
| `wazuh-attackmap.env.example` | 环境变量模板。 |
| `docs/INSTALL.md` | 逐步安装手册。 |
| `docs/ADMIN.md` | 操作员 / 管理员手册(配置、操作、故障排除)。 |
| `docs/USER.md` | 查看者手册(阅读地图、控制操作)。 |
`GeoLite2-City.mmdb` **不**包含在内(被 gitignore,约 60 MB)。`geo.py` 会自动检测
脚本旁边的副本,然后是 `$GEOIP_MMDB`,如果都没有,则回退到使用合成
散布数据。请参阅 [docs/INSTALL.md](docs/INSTALL.md)。
## 快速预览(无需 Wazuh)
```
python3 demo.py # then open http://127.0.0.1:8788/attackmap
```
重新生成截图(或 1280×640 的社交预览卡片):
```
python3 tools/render_shot.py docs/screenshot.png
SHOT_W=1280 SHOT_H=640 python3 tools/render_shot.py docs/social-preview.png
```
## 在 Wazuh 服务器上安装
有关完整的操作指南,请参阅 **[docs/INSTALL.md](docs/INSTALL.md)**。简短版本如下:
```
sudo mkdir -p /opt/wazuh-attackmap && sudo chown "$USER":"$USER" /opt/wazuh-attackmap
cp mapserver.py attackmap.py geo.py world.geojson /opt/wazuh-attackmap/
# 将 GeoLite2-City.mmdb 放入 /opt/wazuh-attackmap/ 中(可选但推荐)
sudo cp wazuh-attackmap.env.example /etc/wazuh-attackmap.env
sudo chmod 600 /etc/wazuh-attackmap.env # set INDEXER_PASS to a read-only account
sudo cp wazuh-attackmap.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now wazuh-attackmap
curl -s http://127.0.0.1:8100/healthz
```
## 路由
| 路由 | 描述 |
|-------|-------------|
| `/` | 302 → `/attackmap` |
| `/attackmap` | 全屏实时地图页面 |
| `/api/attackmap/world` | 内置的世界 geojson |
| `/api/attackmap/recent?limit=N` | 最近的事件 + home 坐标 (JSON) |
| `/api/attackmap/stats` | 运行计数器 (JSON) |
| `/api/attackmap/stream` | Server-Sent Events 数据流 |
| `/healthz` | 200 存活探针 |
## 配置
所有配置均通过环境变量完成 —— 有关
完整表格,请参阅 [docs/ADMIN.md](docs/ADMIN.md)。最常用的:
| 变量 | 默认值 | 说明 |
|-----|---------|-------|
| `INDEXER_URL` | `https://127.0.0.1:9200` | 本地 wazuh-indexer |
| `INDEXER_USER` / `INDEXER_PASS` | `monitor` / — | **只读**账户 |
| `INDEXER_CA` | — | 用于 TLS 验证的 `root-ca.pem`;未设置 = 关闭验证 |
| `MAP_PORT` | `8100` | 监听端口 |
| `MAP_TLS_CERT` / `MAP_TLS_KEY` | — | 同时设置两者 → 提供 **HTTPS** 服务(复用 Wazuh 证书;请参阅 [docs/INSTALL.md](docs/INSTALL.md#serve-the-map-over-https-recommended)) |
| `MAP_HOME_LAT` / `MAP_HOME_LON` | `52.37` / `4.90` | 弧线降落的传感器 "home" |
| `MAP_INCLUDE_PRIVATE` | `0` | `1` = 同时绘制**内部/LAN**源 IP(见下文) |
| `MAP_DEMO` | — | `1` = 使用合成数据生成器代替 Wazuh |
## 内部 / LAN 攻击者
**默认情况下**,地图仅绘制**公共**源 IP —— 因此,如果攻击者已经
在您的 LAN *内部*(横向移动、内部 bruteforce、内部 recon)并使用
`192.168.x / 10.x / 172.16–31.x` 地址,将**不会产生弧线**。RFC1918 地址
无法进行地理定位,因此会被丢弃。
这是一个可视化缺口,**而不是**检测缺口 —— Wazuh 仍会记录这些
告警;只是地图默认情况下不会将它们绘制出来。
设置 `MAP_INCLUDE_PRIVATE=1` 可使它们显示出来。内部源将以独特的
**紫红色** 绘制,聚集在您的传感器 home 周围的一个环形中(每个
内部主机都会获得一个固定的位置,由其 IP 哈希生成)。因此,如果一台内部主机
正在猛烈攻击您的传感器,它将显示为中心附近的紫红色弧线 —— 在视觉上
与从国外扫入的多色公共弧线区分开来。在 API 中,事件也会
携带 `"scope":"internal"` 以便进行下游过滤。
```
MAP_INCLUDE_PRIVATE=1 python3 mapserver.py # or set it in /etc/wazuh-attackmap.env
```
## 已知限制(这是一个 POC)
- 默认情况下**仅**获取可路由的公共 `data.srcip` 源(有关 `MAP_INCLUDE_PRIVATE`,请参阅上文的*内部 / LAN 攻击者*)。仅限家庭/LAN 的 Wazuh 将显示一个
空地图,除非设置了该标志 —— 这是正确的行为,不是 bug。要查看公共
弧线,您需要面向互联网的传感器(或 honeypot)产生带有公共
源 IP 的告警。
- 地理定位是近似的(城市级 mmdb,或者更糟的,合成散布数据)。
- 地图页面本身没有身份验证。**默认设置针对实验室进行了调优**(无身份验证,
`MAP_BIND=0.0.0.0`,HTTP),适用于受信任的内部网络上的 SOC 大屏。在
生产环境中,请将其置于具有身份验证的反向代理之后,绑定到 localhost,
开启 HTTPS —— 请参阅 [SECURITY.md](SECURITY.md#test-lab-vs-production-posture)。
在没有代理 + 身份验证的情况下,切勿将其暴露在互联网上。
- 仅内存运行:事件环形缓冲区会在重启时重置。无历史记录,无数据库。
- 通过 analysisd 队列注入的测试告警带有旧的时间戳,并且会落在轮询水位线之后,因此它们永远不会被发出。请使用真实流量或 `MAP_DEMO`。
## 安全性
只读、无依赖、安全加固标头、可选的 HTTPS、只读 indexer
账户、绑定控制。请参阅 **[SECURITY.md](SECURITY.md)** 了解威胁模型和
接受的 POC 权衡(无页面身份验证、`'unsafe-inline'` CSP 等)。
## 许可证 / 状态
概念验证,按“原样”提供。无任何保证,不保证提供支持。
采用 [MIT License](LICENSE) 授权。
标签:Wazuh, 安全运营, 扫描框架, 攻击态势感知, 自动化扫描