docs: 新增蜜罐部署运维文档,cli.md 补 honeypot 命令与排障条目
docs/honeypot.md(新增): - 生效范围与匹配规则:挂在 NotFound、业务前缀强制剔除、三类匹配方式; - 配置项表,并单独警告「行尾注释会被并入值」(IgnoreInlineComment 的坑); - 两条封禁路径:GET 探测走常规阈值、提交凭据才一次即封,附原因 (GET 就封等于拿不到字典,这是最容易误解的一点); - 凭据文件格式/权限/保留期,以及为什么不入库(备份、主从、BI); - trust_proxy 部署必读:回环 IP 会让取证失真且一次封禁封掉整站; - 升级清理旧版明文列(purge-creds 不可逆); - 排障表 + 设计取舍(GET 不封、canary 位置、邮件带明文等)。 docs/cli.md: - §0 补两条实测踩坑:「替换二进制必须重启进程」「strings 不认中文, 用 grep -ac」; - 命令总览加 honeypot; - 新增 §7 honeypot 命令(hits/stats/paths/size/prune/purge-creds), 原 7/8/9 顺延为 8/9/10; - 排错表加 5 条:回环 IP、整站 403、进程未重启、strings 误判、命令不存在。
This commit is contained in:
+60
-5
@@ -20,13 +20,25 @@ go build -o NekoBox ./cmd/
|
||||
两处都注册在同一份 `app.Commands` 列表中。**新增命令时必须同时改 `tamabox.go` 和 `cmd/tamabox.go`**,
|
||||
否则会出现「本机二进制有、线上没有」(或反之),典型报错是 `No help topic for '<cmd>'`。
|
||||
|
||||
⚠️ **替换二进制不等于生效**:`go build -o tamabox .` 只改磁盘文件,正在运行的进程仍是旧映像。
|
||||
换完之后必须重启(systemd / supervisor / 手动拉起),否则改了代码也看不到变化。
|
||||
另外 `./tamabox web` 在前台立即返回,通常是端口被旧进程占着:
|
||||
|
||||
```bash
|
||||
ps -eo pid,lstart,cmd | grep '[t]amabox' # 看进程启动时间,与二进制 mtime 比对
|
||||
```
|
||||
|
||||
确认手头二进制包含全部命令:
|
||||
|
||||
```bash
|
||||
./tamabox --help | sed -n '/COMMANDS/,/GLOBAL/p'
|
||||
# web / censor / uid / ban / restore / reports / restrict / unrestrict / hide-public
|
||||
# web / censor / uid / ban / restore / reports / restrict / unrestrict / hide-public / honeypot
|
||||
```
|
||||
|
||||
> 想确认某个字符串有没有编进二进制,**不要用 `strings`** —— 它默认只输出可打印 ASCII,
|
||||
> UTF-8 中文整段被丢弃,`strings x | grep 中文` 恒为 0。
|
||||
> 用 `grep -ac "中文字符串" x`。
|
||||
|
||||
### 配置文件
|
||||
|
||||
CLI 与 `web` 读同一份配置,路径由环境变量指定:
|
||||
@@ -64,9 +76,11 @@ pg_dump -Fc <dbname> > before-$(date +%F-%H%M).dump
|
||||
| `restore` | 恢复已停用账号 | 是 | 用户申诉后恢复 |
|
||||
| `censor` | 重新审核历史内容 | 是 | 审核规则变更后复检 |
|
||||
| `uid` | 为用户生成/补齐 uid | 是 | 数据修复 |
|
||||
| `honeypot` | 扫描器诱捕取证(假登录页) | 部分是(`prune` / `purge-creds`) | 看谁在扫、拿到了什么字典 |
|
||||
| `web` | 启动 Web 服务 | — | 生产入口 |
|
||||
|
||||
除 `reports` 外,所有写操作都要求交互式输入大写 `YES` 确认;脚本化时加 `--yes`。
|
||||
除 `reports`、`honeypot hits/stats/paths/size` 外,所有写操作都要求交互式输入大写 `YES` 确认;
|
||||
脚本化时加 `--yes`。注意 `honeypot prune` / `purge-creds` 的确认词是 **`yes`**(小写)。
|
||||
|
||||
---
|
||||
|
||||
@@ -277,7 +291,43 @@ pg_dump -Fc <dbname> > before-$(date +%F-%H%M).dump
|
||||
|
||||
---
|
||||
|
||||
## 7. 典型处置流程
|
||||
## 7. `honeypot` —— 扫描器取证
|
||||
|
||||
> 设计取舍、凭据存储格式、部署要点见 [honeypot.md](honeypot.md)。这里只列命令。
|
||||
|
||||
| 子命令 | 作用 | 改数据 |
|
||||
|---|---|---|
|
||||
| `hits` | 列出捕获到的凭据(时间倒序,密码默认脱敏) | 否 |
|
||||
| `stats` | 来源 IP / 路径排行(库)+ 凭据排行(文件) | 否 |
|
||||
| `paths` | 列出当前生效的诱饵路径 | 否 |
|
||||
| `size` | 表行数、磁盘占用、凭据文件体积 | 否 |
|
||||
| `prune` | 按保留期清元数据 + 凭据文件 | 是 |
|
||||
| `purge-creds` | 删除旧版遗留的明文凭据列(**不可逆**) | 是 |
|
||||
|
||||
```bash
|
||||
./tamabox honeypot hits --since 7d --limit 50
|
||||
./tamabox honeypot hits --ip 203.0.113.9 --show-password
|
||||
./tamabox honeypot stats --days 7
|
||||
./tamabox honeypot paths
|
||||
./tamabox honeypot size
|
||||
./tamabox honeypot prune --days 30
|
||||
./tamabox honeypot purge-creds
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- **凭据不入库**。用户名/密码只写在 `data/honeypot-creds/creds-YYYY-MM-DD.jsonl`
|
||||
(目录 0700、文件 0600),数据库里只有 IP / 路径 / UA / 是否封禁这些元数据。
|
||||
所以 `hits` / `paths` **不连数据库**,DB 挂了也能查。
|
||||
- 密码默认脱敏(保留首尾各 1 位)。终端输出常被贴进工单或截图,`--show-password` 只在确有取证需要时开。
|
||||
- `purge-creds` 用于清掉早期版本存在 `honeypot_hits` 上的明文列(AutoMigrate 不删既有列)。
|
||||
执行前先 `pg_dump`;它只处理当前库,**历史备份与从库里的明文需要自行清理**。
|
||||
- 封禁语义:GET 探测走常规阈值(默认 10 次 / 60 秒),**提交凭据才一次即封**。
|
||||
所以「扫了几十次怎么没封」不是 bug —— 见 [honeypot.md](honeypot.md) 的「两条封禁路径」。
|
||||
|
||||
---
|
||||
|
||||
## 8. 典型处置流程
|
||||
|
||||
1. **发现**:收到告警邮件(同一箱主 24h ≥ 8 条 或 7d ≥ 20 条)→ 或箱主后台出现「被举报 N 次」。
|
||||
2. **取证**:`reports --domain <domain>` 看条目、占比、历史处置。
|
||||
@@ -299,7 +349,7 @@ pg_dump -Fc <dbname> > before-$(date +%F-%H%M).dump
|
||||
|
||||
---
|
||||
|
||||
## 8. 排错
|
||||
## 9. 排错
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
@@ -312,12 +362,17 @@ pg_dump -Fc <dbname> > before-$(date +%F-%H%M).dump
|
||||
| 箱主说「我改了但没生效」 | 处置期内严格度被锁定 / L2 禁止设为公开 | 看后台条幅;`reports` 查当前处置 |
|
||||
| `--days 超过 90 天` 被拒绝 | 防手误设计,需要 `--force` | 确认时长后重试,或改用常规天数 |
|
||||
| 处置成功但箱主说没收到邮件 | SMTP 未配 / 发送失败 / 用了 `--no-notify` | 看终端 warn;`[mail]` 配好后可 `restrict` 重发(同级别重设会替换记录) |
|
||||
| 蜜罐记录的 `ip` 是 `127.0.0.1` | `trust_proxy` 未配,源站把所有人看成同一个代理 IP | 配 `trust_proxy`;**此时一次封禁会封掉整站**(见 [honeypot.md](honeypot.md) 的「部署」一节) |
|
||||
| 整站都返回 403 | 蜜罐把反代 IP 封了(上一条的后果) | 重启进程(封禁是内存态)或等 `honeypot_ban_minutes` 到期 |
|
||||
| 换了二进制但行为没变 | 进程没重启,跑的还是旧映像 | `ps -eo lstart,cmd \| grep tamabox` 比对启动时间与文件 mtime |
|
||||
| `strings 二进制 \| grep 中文` 恒为 0 | `strings` 只输出可打印 ASCII,中文 UTF-8 被丢弃 | 用 `grep -ac "中文字符串" 二进制` |
|
||||
| `honeypot` 命令不存在 | 二进制旧 / 两个构建入口未同步 | 重新编译后**重启进程** |
|
||||
|
||||
改完配置**必须重启**才生效(配置只在启动时读取一次)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 明确不做的事
|
||||
## 10. 明确不做的事
|
||||
|
||||
- **不做自动处置**:举报数达标只发邮件,不自动封禁。这个场景里举报本身就是攻击武器。
|
||||
- **不做申诉后台**:申诉走邮件,处置结果由 CLI 写入。
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
# 蜜罐(虚假后台登录页)
|
||||
|
||||
给扫描器一个假的 WordPress 登录页:它探后台路径时返回 200 假页面,它提交账号密码时把凭据记下来、并把它临时封掉。
|
||||
|
||||
一句话取舍:**这套功能的价值是「拿到对方正在用的字典」,不是「挡住扫描」。** 挡扫描靠 CDN 和 `autoban`;蜜罐只在「有人真的往假后台里填账号密码」时才有意义。
|
||||
|
||||
---
|
||||
|
||||
## 1. 生效范围
|
||||
|
||||
蜜罐挂在 `f.NotFound` 上 —— flamego 只有**路由表完全落空**时才会走到这里,所以:
|
||||
|
||||
- `/login`、`/register`、`/_/{domain}`、`/user/**`、`/api/v1/**` 这些真实路由永远不会被接管;
|
||||
- 命中后短路返回,不查业务表、不渲染业务模板、不调用 `ctx.Next()`;
|
||||
- 页面是纯静态 HTML,只含 canary 标记和假表单,不含任何真实信息。
|
||||
|
||||
匹配规则(`internal/security/honeypot/honeypot.go`):
|
||||
|
||||
| 类型 | 例子 |
|
||||
|---|---|
|
||||
| 段前缀 | `/admin`、`/wp-admin`、`/.ssh`、`/.env`、`/phpmyadmin`、`/actuator` |
|
||||
| 任意位置片段 | `/wp-includes/`、`/xmlrpc.php`、`/cgi-bin/`、`/.git/` |
|
||||
| 文件后缀 | `*.zip`、`*.sql`、`*.bak`、`*.pem`、`*.key`、`*.env` |
|
||||
| 自定义 | `[security] honeypot_paths`(逗号分隔,与内置清单取并集) |
|
||||
|
||||
业务路由前缀(`/login`、`/api`、`/static` 等)即使出现在自定义清单里也会被强制剔除,构成第二道保险。
|
||||
|
||||
改完路径后用 `tamabox honeypot paths` 看最终生效的完整清单。
|
||||
|
||||
---
|
||||
|
||||
## 2. 配置
|
||||
|
||||
```ini
|
||||
[security]
|
||||
enable_honeypot = true
|
||||
honeypot_paths =
|
||||
honeypot_threshold = 1
|
||||
honeypot_ban_minutes = 1440
|
||||
honeypot_retain_days = 30
|
||||
honeypot_auto_clean = true
|
||||
honeypot_max_rows = 200000
|
||||
honeypot_cred_dir = data/honeypot-creds
|
||||
```
|
||||
|
||||
| 键 | 缺省 | 说明 |
|
||||
|---|---|---|
|
||||
| `enable_honeypot` | `false` | 总开关。关闭后 `/wp-admin` 等路径恢复原先的 302 → `/` |
|
||||
| `honeypot_paths` | 空 | 追加诱饵路径,与内置清单取并集 |
|
||||
| `honeypot_threshold` | `1` | **提交凭据**的封禁阈值(见 §3) |
|
||||
| `honeypot_ban_minutes` | `1440` | 蜜罐封禁时长;显式 `0` 回退 `ban_duration_minutes` |
|
||||
| `honeypot_retain_days` | `30` | 元数据与凭据文件的保留天数;`<= 0` 也回退 30 |
|
||||
| `honeypot_auto_clean` | `true` | 每小时一轮后台清理;关闭后只能手动 `honeypot prune` |
|
||||
| `honeypot_max_rows` | `200000` | 元数据行数上限(第二道闸);显式 `0` = 不限 |
|
||||
| `honeypot_cred_dir` | `data/honeypot-creds` | 凭据文件目录,进程工作目录为基准 |
|
||||
|
||||
> ⚠️ **配置项一律写成「注释独占一行 + 裸值」**。配置加载器开启了 `IgnoreInlineComment`,行尾 `; 注释` 会被并入值:`honeypot_max_rows = 200000 ; 说明` 解析出来是 `"200000 ; 说明"`,int 解析失败而 `MapTo` 不报错 —— 该项静默回退零值,日志里只会说「配置值不对」。
|
||||
|
||||
改完配置**必须重启**(配置只在启动时读一次)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 两条封禁路径(最容易误解的地方)
|
||||
|
||||
| 行为 | 走哪条通道 | 阈值 | 是否立即封 |
|
||||
|---|---|---|---|
|
||||
| `GET /wp-login.php` 看一眼假页面 | 常规 `autoban.Record` | `ban_threshold_sensitive`(默认 10 次 / 60 秒) | 否 |
|
||||
| `POST` 带 `log` / `pwd` 提交 | 蜜罐 `autoban.RecordHoneypot` | `honeypot_threshold`(默认 1) | 是,立即封 |
|
||||
| `POST` 不带任何凭据字段 | 常规通道 | 同上 | 否 |
|
||||
|
||||
**为什么 GET 不立即封**:爆破脚本的标准流程是「先 GET 探表单 → 再 POST 提交」。如果 GET 就吃 403,假登录页只被看了一次,对方根本没机会填表,凭据永远拿不到 —— 而这个功能的全部价值就在那份凭据上。
|
||||
|
||||
蜜罐通道**不受 `enable_auto_ban` 约束**:关掉常规自动封禁(它对共享出口误伤多)不会连带让蜜罐变成只记录不封禁的旁观者。
|
||||
|
||||
纯探测也不会被刷爆:元数据落库按「每 IP 每分钟一条」采样,凭据则是「有就存」——那才是要保的东西。
|
||||
|
||||
---
|
||||
|
||||
## 4. 凭据存在哪里
|
||||
|
||||
**用户名/密码不进数据库**,只追加到按天分文件的 JSONL:
|
||||
|
||||
```
|
||||
data/honeypot-creds/creds-2026-10-21.jsonl
|
||||
```
|
||||
|
||||
```json
|
||||
{"time":"2026-10-21T16:13:50+08:00","ip":"117.x.x.226","method":"POST",
|
||||
"path":"/wp-login.php","username":"admin2","password":"test456",
|
||||
"fields":{"log":"admin2","pwd":"test456"},"raw_body":"log=admin2&pwd=test456",
|
||||
"user_agent":"curl/8.22.0","banned":true}
|
||||
```
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 目录权限 | `0700`(启动时强制 `chmod`) |
|
||||
| 文件权限 | `0600` |
|
||||
| 切分 | 按记录时间,一天一个文件 |
|
||||
| 坏行 | 跳过(进程被杀留下的半条 JSON 不会让整天数据报废) |
|
||||
| 清理 | 与 `honeypot_retain_days` 同步,只删「整天都过期」的文件 |
|
||||
|
||||
**为什么不入库**:明文账号密码一旦入库,就会跟着进备份、主从复制、以及任何有库只读权限的报表/BI 工具,事后无法彻底清除。文件可以单独挂卷、单独设权限、到期直接删。
|
||||
|
||||
数据库里只保留「发生过一次带凭据的提交」这个事实(`honeypot_hits.had_fields`),以及 IP / 路径 / UA / 是否封禁这些元数据。
|
||||
|
||||
> ⚠️ 这些是**外部攻击者提交的真实账号密码**,很可能是从别处泄露后复用的。请按敏感数据对待:不要提交进版本库(`/data/` 已在 `.gitignore`)、不要参与备份、不要贴到公开群组。
|
||||
|
||||
---
|
||||
|
||||
## 5. 部署:反向代理必须配 `trust_proxy`
|
||||
|
||||
这是**部署时最容易踩、后果最严重**的一条。
|
||||
|
||||
如果站点在 nginx / Cloudflare 之后而 `trust_proxy = false`:
|
||||
|
||||
- 源站看到的所有请求都来自代理地址(常见 `127.0.0.1`),**取证数据失去意义**;
|
||||
- 更糟:一次蜜罐命中封的是 `127.0.0.1`,而所有访客在源站眼里都是这个 IP —— **等于封掉整站**。
|
||||
|
||||
正确配置:
|
||||
|
||||
```ini
|
||||
[security]
|
||||
trust_proxy = true
|
||||
trusted_proxies = 127.0.0.1
|
||||
```
|
||||
|
||||
> ⚠️ 前提:**源站不能直接被公网访问**(Cloudflare Tunnel / Authenticated Origin Pull / 防火墙只放行回源网段)。`trust_proxy = true` 时程序会信任 `CF-Connecting-IP` 这类权威头,源站若可直连,攻击者能伪造它来绕过封禁、或反手封掉别人。
|
||||
|
||||
程序在「命中回环地址且 `trust_proxy` 未开」时会打一条一次性 WARN(24 小时冷却)提醒检查这里。
|
||||
|
||||
验证:触发一次蜜罐后看凭据文件里的 `ip` 是不是真实客户端地址。
|
||||
|
||||
---
|
||||
|
||||
## 6. 升级:清掉历史明文列
|
||||
|
||||
早期版本把 `username` / `password` / `fields` / `raw_body` 直接存在 `honeypot_hits` 上。结构体已经删掉这些字段,但 **GORM 的 AutoMigrate 不会删既有列** —— 老库升级上来后,历史明文还在表里。
|
||||
|
||||
启动时会打一条 WARN:
|
||||
|
||||
```
|
||||
honeypot_hits 上仍残留旧版明文凭据列,请执行 TamaBox honeypot purge-creds 清除
|
||||
```
|
||||
|
||||
清理(**不可逆**,先备份):
|
||||
|
||||
```bash
|
||||
pg_dump -Fc <dbname> > before-purge-$(date +%F-%H%M).dump
|
||||
./tamabox honeypot purge-creds # 列出四列,输入 yes 才执行
|
||||
```
|
||||
|
||||
只处理当前库;**历史备份与从库里可能仍有明文**,需要自行处理。
|
||||
|
||||
---
|
||||
|
||||
## 7. 日常运维
|
||||
|
||||
```bash
|
||||
./tamabox honeypot hits # 最近 24h 的凭据(密码默认脱敏)
|
||||
./tamabox honeypot hits --ip 1.2.3.4 --show-password
|
||||
./tamabox honeypot stats --days 7 # 来源 IP / 路径排行(库)+ 凭据排行(文件)
|
||||
./tamabox honeypot paths # 当前生效的诱饵路径
|
||||
./tamabox honeypot size # 表行数 + 磁盘占用 + 凭据文件体积
|
||||
./tamabox honeypot prune --days 30 # 手动按保留期清(元数据 + 凭据文件)
|
||||
```
|
||||
|
||||
`hits` / `paths` 不连数据库(DB 挂了也能查)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 排障
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
| `/wp-admin` 还是 302 | `enable_honeypot` 未开 / 配置未重启生效 | 看启动日志;`honeypot paths` 确认开关状态 |
|
||||
| 记录里 `ip` 是 `127.0.0.1` | `trust_proxy` 未配 | 见 §5;**注意此时封禁会封全站** |
|
||||
| 整站都返回 403 | 蜜罐把反代 IP 封了(上一条的后果) | 重启进程(封禁是内存态)或等 `honeypot_ban_minutes` 到期 |
|
||||
| GET 假页面不封禁 | 设计如此 | 提交凭据才立即封,见 §3 |
|
||||
| 记录里 `path` 带前导空白/奇怪字符 | 上游(nginx/CF)改写了 URI | 代码已清洗并打 WARN;根因建议查 `proxy_pass` / `rewrite` |
|
||||
| `honeypot` 命令不存在 | 二进制旧 / 两个构建入口未同步 | `go build -o tamabox .` 后**重启进程** |
|
||||
| 改了代码但行为没变 | 只替换了二进制,进程还在跑旧映像 | `kill` 旧进程后按原方式重新拉起 |
|
||||
| 凭据文件没有生成 | `honeypot_cred_dir` 不可写 | 看启动日志里的「凭据目录不可用」告警 |
|
||||
| 启动报 `honeypot_hits 上仍残留…` | 老库有历史凭据列 | `honeypot purge-creds`(§6) |
|
||||
|
||||
排障时两个容易误判的点:
|
||||
|
||||
- **验证二进制里有没有某个中文字符串,不能用 `strings`** —— 它默认只输出可打印 ASCII,UTF-8 中文整段被丢弃,`strings x | grep 中文` 恒为 0。用 `grep -ac "中文字符串" x`。
|
||||
- **替换二进制 ≠ 生效**,进程必须重启;前台跑 `./tamabox web` 若立刻返回,通常是端口被旧进程占着。
|
||||
|
||||
---
|
||||
|
||||
## 9. 设计取舍(已知限制)
|
||||
|
||||
- **GET 探测不封**:见 §3。代价是扫描器可以反复拿假页面,收益是能拿到凭据。
|
||||
- **假页面带 canary**:只放在 HTML 注释里(`<!-- build: tb-hp-... -->`),用于确认「这确实是蜜罐返回的页面」;`<meta name="generator">` 伪装成 `WordPress 6.4.3`,不主动暴露技术栈。
|
||||
- **只挡 NotFound**:如果将来给 `/admin` 之类路径注册了真实路由,蜜罐会自动让位 —— 这是刻意设计(业务优先)。
|
||||
- **告警邮件仍带明文用户名/密码**:这正是它的价值(看到对方在用什么字典),但也意味着邮件是敏感物,收件人只应是你自己。
|
||||
- **不主动反制**:不回打、不上报第三方、不做删除/篡改,只有「记录 + 临时封禁」。
|
||||
Reference in New Issue
Block a user