Files
tamabox/.workbuddy/memory/2026-10-21.md
T
tamakyi 7d52155411 fix(honeypot): 蜜罐封禁与 enable_auto_ban 解耦 + 泄露扫描后缀匹配
- autoban: 新增 RecordHoneypot 独立通道(不受 enabled 约束)+ 独立阈值/时长;
  IsBanned 不再因 enable_auto_ban=false 而忽略已有封禁
- honeypot: 新增 honeypotSuffixes 后缀匹配,覆盖 /backup.zip、/db.sql、/id_rsa.pem
- 新增配置 honeypot_threshold / honeypot_ban_minutes
- 补测试:autoban/honeypot_test.go、route 侧禁用自动封禁仍封禁
2026-10-21 12:36:33 +08:00

10 KiB
Raw Blame History

2026-10-21

扫描者处置方案设计(未实施,仅设计文档)

  • 触发:线上频繁收到「疑似批量扫描已自动封禁」告警,命中路径以 wp 探测 + /.ssh/** 为主,来源多 Azure 云 IP 池。
  • 实测纠正一处错误认知:internal/route/route.go:432 的注释称「flamego 的 NotFound 会先写 404 状态再 Redirect」—— 错的。flamego v1.9.4 的 NotFound 只执行 handler,不写状态码;ctx.Redirect("/") 实测输出 **302 → "/"。 最小复现验证:/wp-admin/includes/...与/.ssh/id_rsa` 均得 302 + Location:/。 后果:扫描器跟随重定向 → 每个探测路径都触发一次首页渲染(1C1G 纯浪费),且被判为「站点有价值」。 同时告警邮件文案「产生大量 404」与实际 302 不符。
  • 方案分五层(L0 CDN 路径/UA 规则 → L1 真 404 分流 → L2 蜜罐假文件/假凭据 → L3 tarpit + 递进封禁 → L4 上游 abuse 举报):
    • 核心判断:对云厂商僵尸网络做 IP 封禁无效,有效的是按路径在 CDN 拦 + 蜜罐取证 + 上游举报。
    • 明确排除反向攻击/DoS/回打(法律风险 + Azure 共享出口伤及无辜 + 招致 CDN 封站)。
    • 蜜罐:命中 /.ssh/id_rsa、/.env、/wp-login.php 等诱饵路径 → 短路返回假内容(含 canary 标记)+ 永久封禁 + 留证。
    • 封禁需落库(当前内存 map,重启清零)+ 时长递进 15m→1h→6h→24h→7d→永久 + 多维(IP / IPv6 /64 / UA 家族)。
    • tarpit 必须带全局并发上限 + 单连接超时,否则 1C1G 自身变 DoS 入口。
  • 设计文档:outputs/scanner-defense-design-2026-10-21.md(含分级变更清单、文件级改动点、验收指标)。
  • 未动任何生产代码,等用户决定实施范围。

蜜罐(虚假后台登录界面 + 破解凭据记录)已实施

用户明确需求原文:「能不能把这些页面指向一个简单的虚假后台登录界面,然后记录这些破解的用户名密码等信息」。

落点决策(关键)

  • 蜜罐不改写成新中间件,而是改写 internal/route/route.go 的 f.NotFound:
    • NotFound 只在「路由表完全落空」时触发 → 与 /login /register /_/{domain} /user/** /api/v1/** 天然零交集;
    • 位于全局 f.Use 链之后 → requestBodyLimitMiddleware 已给 body 加 12 MiB 上限;
    • 组级中间件(含 CSRF)只挂具体路由,不作用于 NotFound → POST 表单可安全解析;
    • flamego context.run() 在 ResponseWriter().Written() 为真时 return → 写完响应不调 ctx.Next() 即安全短路。
  • 双重计数坑:autoban 中间件对敏感路径已 Record 一次,蜜罐再记一次会让阈值实际减半。 解决:autobanMiddleware(b, hp) 传入蜜罐匹配器,蜜罐命中的路径由蜜罐自己计数。

新增文件

  • internal/security/honeypot/honeypot.go — 匹配器(段前缀 + 片段子串 + 双重保险 forbiddenPrefixs 排除业务路由)、 凭据提取(表单 / JSON / BasicAuth / 查询串)、CanaryToken = "tb-hp-7f3a1c9e"。
  • internal/security/honeypot/page.go — 仿 WordPress 假登录页(经典字段 log/pwd/redirect_to),内联 CSS 不依赖站点静态资源。
  • internal/db/honeypot.go — HoneypotHit(不嵌 dbutil.Model,审计证据只追加)+ HoneypotStore。
  • internal/route/honeypot.go — 处理器:GET 假登录页 / POST 假失败响应 / 落库 / 复用 autoban 封禁 / 每 IP 告警一次。
  • internal/cmd/honeypot.go — TamaBox honeypot hits|stats|prune,密码默认脱敏(--show-password 显示明文)。
  • templates/mail/honeypot-alert.html — 告警邮件模板(mail/ 已在 embed 清单,无需改 embed.go)。

改动文件

  • internal/route/route.go(CRLF):NotFound 分流 + 蜜罐实例装配 + hph.sendAlert。
  • internal/route/autoban.go(LF):autobanMiddleware(b, hp),蜜罐路径跳过自身 Record。
  • internal/db/db.go(LF):AllTables 加 &HoneypotHit{},Init() 加 Honeypots = NewHoneypotStore(db)。
  • internal/conf/static.go(CRLF):enable_honeypot / honeypot_paths / honeypot_retain_days。
  • conf/app.sample.ini(CRLF):对应说明段。
  • internal/mail/mail.go(LF):SendHoneypotAlertMail + HoneypotAlert。
  • cmd/tamabox.go + 根 tamabox.go:注册 cmd.Honeypot(两处必须同步)。

踩坑

  • normalize() 里 for len(p)>1 { TrimSuffix(p,"/") } 无限循环(路径不含尾斜杠时字符串不变)→ 改成 HasSuffix 判断。
  • url.ParseQuery 遇非法百分号编码返回 error 但仍给出部分字段 → 蜜罐要「尽可能多拿凭据」,忽略 error 继续用部分结果。
  • JSON 凭据提取:手写解析器又脏又易错 → 改用 jsonPairRe 正则扫描扁平字段。
  • templates/mail/ 已在 embed 清单内 → 新增邮件模板不需要改 templates/embed.go(顶层文件才需要)。

验证

  • go build ./... 通过;go test ./... 全绿(含 DB 测试,postgres 门控)。
  • 新增测试:internal/security/honeypot/honeypot_test.go(匹配/业务路由零交集/编码绕过/凭据提取)、 internal/route/honeypot_test.go(假页含 canary、业务路由不被劫持、关闭时回落 302、POST 返回失败页)、 internal/db/honeypot_test.go(写入/倒序/三张聚合表/保留期清理)。

测试站黑盒验证 + P0 缺陷修复(蜜罐封禁被 enable_auto_ban 连坐)

测试站:https://box-dev-api.337845818.best(Cloudflare Tunnel + CF 边缘,cf-cache-status: DYNAMIC、无 Age → 不缓存,请求确实回源)。 生产站 https://box.tama.guru 强制 301 → www,本机不可达,无法做对照。

黑盒验证结果(全部通过)

  • 诱饵路径全部 200 + text/html:/wp-login.php 2529B、/wp-admin/ 2517B、/.ssh/id_rsa 2525B、/.env 2497B、/administrator 2533B。
  • 响应体含 canary tb-hp-7f3a1c9e(2 处)、<title>Log In ‹ WordPress</title>、name="log"/name="pwd"/name="redirect_to"。
  • 响应头:Cache-Control: no-store, no-cache, must-revalidate、x-robots-tag: noindex, nofollow、x-frame-options: DENY。
  • POST 凭据提交 → 200 + id="login_error">ERROR: Invalid username or password.。
  • 路径变体全命中:/WP-ADMIN(大小写)、/wp-admin%2f..%2fetc(编码绕过)、//wp-admin//x.php(双斜杠)、/.git/config、/.aws/credentials、/phpmyadmin/、/actuator/env、/xmlrpc.php、/vendor/phpunit/x.php、/console、/cgi-bin/test.cgi、/wp-content/plugins/x.php、/.env.bak、/wp-json/wp/v2/users。
  • 凭据来源全覆盖:表单 POST、JSON body、BasicAuth、查询串、POST 到 /.ssh/id_rsa。
  • 业务路由零交集(红线):/ 34192B、/login 23420B、/register 25194B、/_/admin6 39358B、/user 302、/api/v1/questions 302、/robots.txt 145B;非诱饵随机路径 /nonexistent-random-xyz 仍 302→/。

P0 缺陷:蜜罐命中 70+ 次但站点零封禁

  • 根因:autoban.Record() 第一行 if !b.enabled { return },b.enabled 来自 enable_auto_ban。 运维为避免共享出口误伤而关掉 enable_auto_ban → 蜜罐被另一个开关连坐,只落库不封禁。
  • 对照组证明非蜜罐专属:/index.php(autoban 敏感、蜜罐不匹配)连打 40 次 + 30 并发同样零封禁。
  • 排除项:CF 缓存(DYNAMIC/无 Age)、flamego NotFound 链(源码确认全局 Use 链生效)、蜜罐匹配器、阈值配置。

修复(已实施,测试全绿):

  • autoban.Options 新增 HoneypotEnabled / HoneypotThreshold(默认 1)/ HoneypotDuration(默认复用常规)。
  • 新增 Banner.RecordHoneypot(ip, path) bool:独立计数、独立阈值,不受 enabled 约束;键与 Record 一致(同样 shared.LimitKey 归一化),否则 IsBanned 判不出来。
  • IsBanned() 移除 !b.enabled 早退 —— enabled 只控制「是否主动创建封禁」,不控制「是否尊重已有封禁」。
  • internal/route/honeypot.go:banned = h.ab.RecordHoneypot(ip, path)。
  • internal/route/route.go(CRLF)装配三个蜜罐字段;conf/static.go + conf/app.sample.ini(CRLF)新增 honeypot_threshold / honeypot_ban_minutes。
  • 告警:仍走既有 sendAlert(「已封禁」)+ 蜜罐自己的告警邮件(「捕获了什么凭据」),互补。

后缀匹配补缺

  • /backup.zip、/db.sql、/id_rsa.pem 这类泄露扫描文件名主体随机,只有后缀稳定。
  • 原 honeypotFragments 用 "/.zip" 子串匹配 → .zip 前是 k 不是 /,全漏网。
  • 新增 honeypotSuffixes(.zip .tar .tar.gz .tgz .gz .rar .7z .bz2 .sql .sql.gz .dump .bak .old .orig .save .swp .env .env.bak .env.local .pem .key .p12 .pfx .jks .kdbx .ovpn),按 path.Base() 结尾匹配。

踩坑

  • ⚠️ 测试里 Duration: 60 = 60 纳秒,封禁瞬间过期 → 表现为「时序巧合的 200/200/403」,测试假失败。一律写 60 * time.Minute。
  • ⚠️ 路由测试只注册路由 + NotFound 会被绕过:执行 IsBanned → 403 的是 autobanMiddleware,测试服务器必须 f.Use(autobanMiddleware(ab, hp))。
  • ⚠️ internal/route/route.go、internal/conf/static.go、conf/app.sample.ini 实际是全 CRLF;grep | cat -A 不显示 ^M,极易误判为 LF。Python 脚本按 \n 锚点替换会静默失败 → 用 \r\n 锚点 + 写后统计 b'\r\n' 复核。
  • 假页纯静态 HTML(内联 CSS,不落盘)、告警走 SMTP → 取证数据只在 honeypot_hits 一张表,服务器无文件副本;autoban 封禁表是内存态(重启清零)。
  • 测试阶段产生的噪声凭据(testadmin/S3cr3tP@ss 等)可用 TamaBox honeypot prune 清理。

遗留(需用户侧)

  • 本轮修复需重新部署才在测试站生效;部署后 curl 一次 /wp-login.php 应 200、再一次 403。
  • 需确认测试环境 conf/app.ini 的 [security] enable_auto_ban(零封禁直接原因)+ enable_honeypot = true。
  • 无法 SSH(100.79.185.45:19999 全部账号 Permission denied),落库验证需用户执行 TamaBox honeypot hits --since 24h --show-password。