hexbytedev/hexwall
GitHub: hexbytedev/hexwall
hexwall 是一款服务器出站流量防护工具,通过监控并终止绕过 DNS 的直接 IP 可疑连接,弥补 Pi-hole 无法检测硬编码 IP 数据外泄的盲区。
Stars: 2 | Forks: 0
# hexwall
[][2]
[][4]
[][6]
[][8]
[][10]
[][12]
[][14]
`hexwall` 是一个出站数据外泄制动器,专为已受到 Pi-hole 保护的服务器设计。当供应链攻击、恶意软件植入或受损的软件包已经在机器内部运行时,它们可以完全绕过 DNS,直接将窃取的数据发送到硬编码的 IP 地址。Pi-hole 无法看到这一跳。`somo` 可以看到实时连接。`hexwall` 利用这种可见性来标记,并在 `enforce` 模式下,于数据离开服务器之前终止可疑的直接 IP 连接。
它会监控活动的网络连接,并终止任何连接到不受 Pi-hole 历史记录、内置本地允许列表或近期已建立的流量信任的 IP 的连接——从而填补了 Pi-hole 为直接 IP 连接留下的漏洞。
简而言之:`Pi-hole + somo + hexwall` 为您提供了一个针对受损后出站流量的实用遏制层,尤其是供应链攻击中偶尔使用的那种直接 IP 数据外泄行为。
## 概览
| 情况 | 未使用 Pi-hole DNS 的服务器 | 仅使用 Pi-hole 的服务器 | 使用 Pi-hole + somo + hexwall 的服务器 |
| --- | --- | --- | --- |
| 通过 DNS 解析的恶意域名 | ❌ 暴露风险 | ✅ 可在 DNS 层拦截 | ✅ 可在 DNS 层拦截 |
| 恶意软件直接连接到硬编码 IP | ❌ 暴露风险 | ❌ Pi-hole 无法察觉 | ✅ 直接 IP 连接可见且受检查 |
| 识别哪个进程拥有该连接 | ❌ 无内置可见性 | ❌ 无进程可见性 | ✅ `somo` 显示 PID/程序 |
| 受损后阻止出站数据外泄 | ❌ 无遏制层 | ⚠️ 仅在攻击者仍使用 DNS 时有效 | ✅ 可终止可疑的已建立连接 |
| 绕过 DNS 的供应链恶意软件 | ❌ 高风险 | ❌ 仍存在暴露风险 | ✅ 更强的遏制能力 |
| 整体出站防护态势 | ❌ 薄弱 | ⚠️ 部分 | 🛡️ 三者中最强 |
## 问题所在
Pi-hole 在 DNS 层运行。它可以拦截域名,但对完全绕过 DNS 的连接(即进程直接拨打硬编码 IP 地址)毫无可见性。恶意软件和受损的软件包通常利用这种技术进行回连,而不会触发任何基于 DNS 的拦截。
hexwall 基于相反的假设运作:**如果某个 IP 不受信任,则该连接默认为可疑。** 当 IP 符合内置的 CIDR 允许列表、在过去一小时内从 Pi-hole 查询历史记录中刷新过,或者在过去 60 秒内已被识别为已建立的连接时,即被授予信任。在启动时,它会立即从 Pi-hole 刷新受信任的 IP,然后每 30 秒刷新一次,同时每 10 秒扫描一次连接。任何不在此信任集合内的 IP 都会根据外部欺诈 API 进行检查,结果按每个 IP 在本地缓存 6 小时。确认的威胁将被记录(watch 模式)或终止(enforce 模式)。
## 依赖项
| 依赖项 | 角色 | 必需 |
| ----------------------------------------- | --------------------------------------------------- | --------------------------- |
| [`somo`](https://github.com/theopfr/somo) | 列出已建立的 TCP/UDP 连接;按 PID 终止 | 必须位于 `$PATH` 中 |
| Pi-hole FTL database | 受信任 DNS 历史记录的来源 | 自动检测或通过 `--db` |
| `deghostapi.hexbyte.dev` | 针对未知 IP 的欺诈/威胁分类 | 需要网络访问权限 |
**构建依赖:** `modernc.org/sqlite`(纯 Go 编写的 SQLite,无需 CGo 或系统 libsqlite3)。
## 二进制文件与部署
CI 发布了适用于以下平台的轻量级 Linux 二进制文件:
- `linux/amd64`
- `linux/arm64`
您可以从该项目的 GitHub Releases 页面下载它们:
- [GitHub Releases](https://github.com/hexbytedev/hexwall/releases)
对于生产环境的 Linux 服务器,请从 Releases 下载匹配的二进制文件,将其放置在您的部署期望的 `./app/hexwall` 路径下,并使用包含的演示 Docker Compose 文件 [`docker-compose.yml`](./docker-compose.yml) 在服务器上启动它。
对于 macOS 和 Windows,CI 不发布预构建的二进制文件。请克隆仓库并在部署前手动构建二进制文件。
### 安装 `somo`
如果 `somo` 尚未在您的 `$PATH` 中,这是在 Ubuntu 或 Debian 上安装并验证它的最快方法:
```
curl https://sh.rustup.rs -sSf | sh
sudo apt install build-essential
cargo install somo
sudo ln -s "$HOME/.cargo/bin/somo" /usr/local/bin/somo
sudo somo
```
这些步骤的作用:
- 使用 `rustup` 安装 Rust 工具
- 安装构建 `somo` 所需的编译器工具链
- 使用 Cargo 构建并安装 `somo`
- 通过 `/usr/local/bin` 在系统范围内公开 `somo`
- 运行一次 `sudo somo` 以确认它可以查看到实时连接和 PID
## Pi-hole 数据库位置
自动检测按以下顺序进行:
1. `/etc/pihole/pihole-FTL.db` — 裸机 / 软件包安装
2. 名称或镜像包含 `pihole` 的正在运行的 Docker 容器,检查其 `/etc/pihole` 挂载点
如果两者均未找到,则启动失败。请使用 `--db` 手动指定路径。
## CLI 标志
| 标志 | 默认值 | 描述 |
| ------------ | ------------------- | ---------------------------------------------------------------- |
| `--db` | _(自动检测)_ | `pihole-FTL.db` 的路径 |
|`--hexwall-db`| `./hexwall.db` | 本地 hexwall 数据库的路径(首次运行时创建) |
| `--mode` | `watch` | `watch` — 仅检测并记录;`enforce` — 检测、记录并终止 |
| `--debug` | `false` | 启用详细的每个连接扫描日志 |
`--mode` 接受 `watch` 或 `enforce`(不区分大小写)。任何其他值都会导致错误退出。
### 调试日志
当启用 `--debug` 时,每个扫描周期会记录其遇到的每个连接及其状态:
- **allowed** — IP 位于本地信任缓存或允许列表中
- **unrecognized-clean** — IP 不在缓存中,但欺诈 API 返回了干净的判定(或者针对私有/保留范围返回了 403)
- **vulnerable** — IP 不在缓存中,欺诈 API 将其标记为滥用者/攻击者/威胁
当禁用调试时(默认),仅记录未被允许的结果。此外,空扫描(somo 返回零连接)无论调试模式如何都会被记录。
## 连接何时被终止
只有当以下所有条件都满足时,连接才会被终止:
1. `--mode enforce` 处于活动状态。
2. `somo` 报告该连接当前已建立。
3. 远程 IP 不受信任。
4. 远程 IP 当前的欺诈判定(无论是实时获取的还是从本地 6 小时缓存中复用的)将其标记为 `is_abuser`、`is_attacker` 或 `is_threat`。
当以下任何一项成立时,该 IP 即被视为受信任:
- 它匹配内置的 CIDR 允许列表。
- 它在过去一小时内从 Pi-hole 历史记录中刷新过。
- 它在过去 60 秒内已被视为已建立的允许连接。
在以下情况下,连接不会被终止:
- `--mode watch` 处于活动状态。该连接仅被记录为 `would kill`。
- 该 IP 位于内置允许列表中。
- 该 IP 仍受近期 Pi-hole 刷新或近期已建立连接活动的信任。
- 该 IP 在过去 6 小时内已有缓存的干净欺诈判定。
- 欺诈 API 返回 HTTP `403`,这被视为干净/私有/保留地址。
- 欺诈 API 返回了报告,但 `is_abuser`、`is_attacker` 或 `is_threat` 均不为 true。
- 欺诈查询本身失败。
当确实发生终止时,hexwall 会首先在本地 `killed_connections` 审计表中记录该事件,然后要求 `somo` 终止拥有该连接的 PID。
### 欺诈 API 缓存行为
欺诈查询按每个 IP 在本地 SQLite 数据库中缓存 6 小时。
- 如果不受信任的 IP 具有未超过 6 小时的缓存欺诈判定,hexwall 将复用该缓存结果,并再次调用欺诈 API。
- 如果缓存的判定超过 6 小时,下一次扫描将再次调用欺诈 API 并刷新缓存时间戳。
- 干净和值得终止的欺诈判定都会被缓存。
- HTTP `403` 响应被视为干净/私有/保留地址,并作为不予终止的结果进行缓存。
- 欺诈查询失败不会被缓存。
此缓存作为 Unix 时间戳(`INTEGER` / int64 风格的秒数)存在于本地 hexwall 数据库中,并在重启后依然保留。
## Pi-hole 历史记录如何成为受信任的 IP
Pi-hole 信任基于近期的 DNS 历史记录建立,而不是直接信任当前每一个连接。刷新流程如下:
1. 从 `pihole-FTL.db` 读取 `queries` 表。
2. 选择过去一小时内出现的、非空的不同域名。
3. 将它们统一转换为小写并去除首尾空格。
4. 通过系统 DNS 解析器解析每个域名,限制并发数,并为每个域名设置 1 秒的查询超时。
5. 忽略无法解析的域名。这对于被拦截的域名、过期的记录及类似情况是正常的。
6. 对已解析的 IP 进行去重。如果多个域名解析到同一个 IP,则按排序顺序为该 IP 存储第一个域名。
7. 将每个解析出的 IP 插入或更新到本地 `allowed_ips` 表中,设置或刷新 `last_refreshed`。
重要细节:
- 启动时会在首次监控扫描之前立即执行此刷新,以便在开始强制执行之前,近期的 Pi-hole 历史记录中出现的正常流量就已经获得信任。
- 刷新每 30 秒重复一次。
- 来自 Pi-hole 刷新的信任自最近一次成功的插入或更新起持续 1 小时。
- 现有行在刷新时会保留其原始的 `first_approved` 和 `last_established` 值;仅更新存储的域名和 `last_refreshed` 时间戳。
- 监控器还会在发现已受信任的连接依然处于已建立状态时更新 `last_established`,从而为长期允许的连接延长信任期。
这意味着,只有在以下所有情况发生后,IP 才会从 Pi-hole 历史记录中获得信任:
1. Pi-hole 在过去一小时内记录了针对它的域名查询。
2. 该域名在刷新周期内仍然可以解析。
3. 某个解析出的 IP 已被写入本地 `allowed_ips` 缓存中。
如果在 Pi-hole 历史记录中出现过某个域名,但在刷新期间不再解析,则不会为其添加任何 IP,因此单凭该域名无法产生任何信任。
标签:EVTX分析, 日志审计, 请求拦截