igozhang

——

    路径探测自动封禁方案(临时黑名单)

    项目:igozhang(Go 单二进制 Web 文件服务)
    实施日期:2026-07-03
    方案:滑动窗口 + ip-auto-ban.json 临时封禁(方案一)


    ENV

    类别 版本 / 信息
    操作系统 Windows 10(10.0.19045)
    Shell PowerShell 5.1(Build 19041)
    Go(构建机) go1.26.0 windows/amd64
    模块 github.com/igokorea/igozhanggo.mod 声明 go 1.22
    应用 igozhang 单二进制,默认监听 :3003
    数据目录 DATA_ROOT/secret07/(配置、黑名单、自动封禁 JSON)
    部署形态 容器 / K8s(hostPath 挂载 DATA_ROOT);探针 GET /health
    相关既有能力 永久黑名单 ip-blacklist.conf;API 分钟桶限流 light_rl.go;运维看板 /ops

    现象

    站点对外提供目录浏览、文章、MP3、文件中转等能力。运维侧观察到(或预期存在)典型路径扫描行为:

    • 同一来源 IP 在极短时间内连续请求大量不同 URL
    • 其中包含大量不存在路径(404)或敏感路径试探(如 /wp-admin/.env 等);
    • 现有 永久黑名单需人工在 /ops 看板维护,无法自动应对突发扫描;
    • 既有 browse/stash 按 IP 限流仅覆盖单个 API,无法识别「跨路径广度探测」。

    结论与根因

    结论:已在最早 HTTP 中间件 requestGate 落地路径探测自动封禁——同一 IP 在 60 秒内访问 ≥20 个不同路径时,自动临时封禁 1 小时(全站 403),状态持久化至 secret07/ip-auto-ban.json不写入永久黑名单。

    根因

    1. 防护层缺口:永久黑名单 + 单接口限流,缺少「单位时间内不同路径数」这一扫描特征检测;
    2. 访客看板 recordVisitor 只记录可识别页面/功能,无法覆盖大量 404、多段路径扫描;
    3. 临时封禁若仅内存实现,Pod 重启后失效,对 K8s 滚动发布不可靠。

    问题形成原因推测:公网暴露的 Web 服务长期会遭遇自动化漏洞扫描器;扫描器策略是「广撒网、低单路径 QPS」,恰好绕过按单 API 的分钟限流,直到人工发现异常访问日志才拉黑,响应滞后。


    定位与推导过程

    1. 确认现有拦截链路与缺口

    cd e:\gitee\code\igozhang
    rg "requestGate|isIPBlacklisted|allowBrowseRL" cmd/igozhang -n
    

    关键输出(摘要):

    • main.gohttp.ListenAndServe(..., requestGate(dataRoot, logRequests(handler))) — 全站最早中间件;
    • ip_blacklist.go:永久黑名单,命中 403;
    • light_rl.gobrowseRLPerMinute=150stashRLPerMinute=80,仅覆盖 /api/browse/api/stash
    • visitor_board.gorecordVisitorvisitorActionFromRequest 过滤,多段路径 / 纯 404 不计入看板 trail。

    推导:检测逻辑必须独立于访客看板,且在 requestGate 内、业务 handler 之前执行;临时封禁应与 ip-blacklist.conf 分离。

    2. 参考既有「临时锁定 + JSON 持久化」模式

    rg "opsAuthFailRL|persistOpsAuthFailRL" cmd/igozhang -n
    

    ops_auth_rl.go 已实现:内存 map + secret07/ops-auth-rl.json 落盘,重启可恢复。路径探测封禁复用同一模式,文件为 ip-auto-ban.json

    3. 单元测试验证阈值与持久化

    cd e:\gitee\code\igozhang
    go test ./cmd/igozhang/ -run "TestPathProbe" -count=1 -timeout 15s
    

    关键输出:

    ok  	github.com/igokorea/igozhang/cmd/igozhang	0.256s
    

    覆盖:阈值触发、同路径去重、排除前缀、JSON 持久化重载、requestGate 返回 403。


    方案

    架构

    请求 → 永久黑名单(ip-blacklist.conf)? → 自动封禁(ip-auto-ban.json)? → 记录路径并判定 → 业务
                  ↓403                         ↓403                    ↓触发则403
    

    中间件接入点(visitor_board.go):

    if ip != "" && isIPBlacklisted(ip) {
        writeIPForbidden(w)
        return
    }
    if ip != "" && !checkPathProbeGate(w, r, dataRoot) {
        return
    }
    

    核心模块:cmd/igozhang/path_probe_ban.go
    配置段:igo.conf [security]
    运维:/ops 看板展示 autoBansPOST /api/ops/auto-ban 手动解除。

    配置(DATA_ROOT/secret07/igo.conf

    修改后须重启 Pod(无热加载)。源码:cmd/igozhang/igo.conf,同步命令:

    cd e:\gitee\code\igozhang
    go generate ./cmd/igozhang/...
    

    当前生产建议配置:

    [security]
    path_probe_enabled = on
    path_probe_window = 1m
    path_probe_max_paths = 20
    path_probe_ban_duration = 1h
    path_probe_exclude_prefix = /stream/mp3/,/assets/doc-pic/
    

    参数对照

    参数 当前值 建议值 原因 预期效果
    path_probe_enabled on on 公网站点建议常开 自动拦截扫描,无需人工盯日志
    path_probe_window 1m 1m(可调 30s–2m) 与需求「一分钟内 20 路径」一致 60s 滑动窗,边界无分钟桶漏洞
    path_probe_max_paths 20 20(高误伤时可试 2530 正常用户极少 1 分钟内访问 20 个不同页面 扫描器广撒网时快速触发
    path_probe_ban_duration 1h 1h(反复扫描可改 2h 临时惩罚,避免永久误伤 封禁期内全站 403,到期自动解除
    path_probe_exclude_prefix /stream/mp3/,/assets/doc-pic/ 保持;若 /files/ 误伤可加前缀 MP3 连播、文章多图会短时间产生多 URL 降低正常用户误封概率

    内置不计入(代码固定,无需配置):/health/favicon*/api/ops/*

    构建与部署

    cd e:\gitee\code\igozhang
    go build -o igozhang.exe ./cmd/igozhang
    

    本地验证(示例):

    $env:DATA_ROOT = "E:\temp\igozhang.cn"
    .\igozhang.exe
    

    容器发版按项目惯例:

    docker build -t krccr.ccs.tencentyun.com/igokorea/igozhang:$(date +%Y%m%d-%H%M%S) .
    

    验证方法

    1. 自动化测试

    go test ./cmd/igozhang/ -run "TestPathProbe|TestRequestGate" -count=1
    

    2. 运行日志确认触发

    封禁触发时写入 secret-log/igozhang.log

    auto-ban: ip=203.0.113.x reason=path_probe paths=20 until=2026-07-03T16:xx:xx+08:00
    

    3. 手工模拟扫描(勿对生产他人 IP 测试)

    $base = "http://127.0.0.1:3003"
    1..21 | ForEach-Object { Invoke-WebRequest -Uri "$base/probe-$_" -UseBasicParsing -ErrorAction SilentlyContinue }
    Invoke-WebRequest -Uri "$base/" -UseBasicParsing
    # 预期:最后一次或第 21 次后,同 IP 请求返回 403 Forbidden
    

    4. 持久化文件

    Get-Content "$env:DATA_ROOT\secret07\ip-auto-ban.json"
    

    示例结构:

    {"203.0.113.x":{"until":1719999999,"reason":"path_probe","paths":21,"bannedAt":1719996399}}
    

    5. 运维看板

    • 登录 /ops → 「自动封禁」列表可见 IP 与剩余时间;
    • 手动解除:POST /api/ops/auto-ban,body {"token":"…","action":"remove","ip":"x.x.x.x"}

    后续预防与运维建议

    1. 误封处理:优先在 /ops 看板「自动封禁」解除;反复误伤则调高 path_probe_max_paths 或增加 exclude_prefix
    2. 永久拉黑:对确认的恶意源,仍用看板「黑名单」写入 ip-blacklist.conf(自动封禁到期后会再次访问);
    3. 监控:对 igozhang.logauto-ban: 行做简单告警(如 1h 内 >5 次);
    4. 维护:每小时维护任务会自动清理过期 ip-auto-ban.json 条目,无需 cron;
    5. 备份ip-auto-ban.json 不进文章 ZIP 导出,属运行时状态;永久策略仍以 ip-blacklist.conf 为准。

    涉及文件清单

    文件 作用
    cmd/igozhang/path_probe_ban.go 滑动窗统计、封禁判定、JSON 持久化
    cmd/igozhang/visitor_board.go requestGate 接入
    cmd/igozhang/ops_config.go [security] 配置解析
    cmd/igozhang/igo.conf 配置真源(embed)
    cmd/igozhang/ops.html / ops_page.go 看板展示与解除 API
    secret07/ip-auto-ban.json 运行时临时封禁(自动生成)

    附录:与永久黑名单的区别

    维度 永久黑名单 路径探测自动封禁
    文件 ip-blacklist.conf ip-auto-ban.json
    触发 人工 / ZIP 导入 自动(路径数超阈)
    时长 永久直至删除 默认 1h TTL
    ZIP 备份 非空时导出 不导出

    MP3