InfraGuard

自动封禁流水线

后台防御流程:采集器与调度器自动将命中可配置触发条件的攻击者 IP 列入名单,并通过内核 ipset 强制生效。

概述 —— 这个功能做什么

自动封禁是 InfraGuard 的后台流水线,无需人工介入即可检测、甄别并封禁攻击者 IP。它不是一个页面,而是横跨采集器、调度器与内核 ipset 的一条流程。本文说明其整体结构。

该流水线包含两条相互独立的封禁路径,它们最终汇聚到同一个生效点(ipset)。

路径 运行形式 写入目标 粒度 来源
① 采集器自动封禁 各主机上的 Node 采集器(srct_tx · kts_tx · eve_ingest) blacklist_info /24(C 类) Suricata L7 · Kitsune ML
② 调度器自动封禁 InfraGuard 应用(infr:auto-block → AutoBlockService) infr_block /32(单个)或 /24 suricata_alert_ct · suricata_tls_ct
③ 强制生效(共用) infr_sync_black.mjs(主机 cron) 内核 ipset blacklist hash:net ① + ② 的并集

两条路径在封禁前都会立即执行白名单与无罪判定检查。 只有确认存在滥用行为的条目才会保留;很可能是误报的候选会被豁免。路径 ②(调度器)默认关闭,必须由管理员启用。

何时使用

页面构成

自动封禁没有专属页面。它的控制点与观测点如下:

使用方法

1. 路径 ② 调度器自动封禁 —— 启用与调优

默认为关闭(autoblock_on=0)。先在运维策略 ▸ 安全设置中拨动总开关,然后再调整策略。

模式(mode)

取值 含义
global(默认) 在一台服务器上发现的入侵会应用到整个机群(server_index=0)。
per_server 仅应用于被攻击的那台服务器 —— 目标 dest_ip 会通过 infr_server_index 解析为 server_index。

范围(scope)

取值 调度器写入 说明
balanced(推荐,默认) 单个 /32 IDS 程序使用 /24;InfraGuard 追加单个 /32 —— sync_black 会跳过任何已被某个 /24 覆盖的 /32。
broad /24 封禁整个 C 类网段 —— 覆盖面广,但有误伤无辜邻居 IP 的风险。
granular 单个 /32 一次一个 IP —— 精确,但面对轮换时反应较慢。

触发条件(OR —— 候选只需满足其中一项)

① IDS 规则集严重级别   suricata_alert_ct
   WHERE alert_ts >= NOW() - INTERVAL {window_min} MINUTE
     AND severity <= {sev_max}          -- crit=1, warn=2(Suricata:1=最严重 … 3=低)
   GROUP BY src_ip HAVING COUNT(*) >= {sev_count}

② JA3 轮换僵尸网络   suricata_tls_ct
   WHERE event_ts >= NOW() - INTERVAL {ja3_window_min} MINUTE
     AND ja3_hash IS NOT NULL
   GROUP BY src_ip HAVING COUNT(DISTINCT ja3_hash) >= {ja3_rotation}

严重级别触发条件使用的时间列是 alert_ts,而 JA3 触发条件使用的是 event_ts(因表而异)。调整阈值时,请留意各自的时间窗是按哪一列过滤的。

策略默认值(OpsPolicy::autoBlock())

设置键 默认值 取值限制 含义
autoblock_on 0 — 总开关(1=开启)。默认关闭
autoblock_mode global global|per_server 封禁模式
autoblock_scope balanced broad|granular|balanced 封禁范围
autoblock_interval_min 10 ≥1 实际扫描间隔(分钟)
autoblock_window_min 10 ≥1 严重级别评估时间窗(分钟)
autoblock_sev_on 1 — 启用触发条件 ①
autoblock_severity crit crit|warn crit=sev≤1,warn=sev≤2
autoblock_sev_count 1 ≥1 时间窗内最少告警条数
autoblock_ja3_on 1 — 启用触发条件 ②
autoblock_ja3_rotation 50 ≥2 不同的 JA3 指纹数 ≥ N
autoblock_ja3_window_min 360 ≥1 JA3 评估时间窗(分钟 · 6 小时)
autoblock_innocent_on 1 — 启用无罪判定否决
autoblock_innocent_days 30 ≥1 自动加入白名单的时长(天)
autoblock_reputation_cap 25 0–200 每轮扫描的信誉查询次数

JA3 轮换调优(实测):现实中的 JA3 轮换攻击者通常在每个 /32 会话约 32 个不同 JA3 处达到峰值(数据中心/云端机器人,不使用 GREASE,因此稳定且可封禁)。因此默认的 ja3_rotation=50 什么也捕获不到。 要让它真正触发,请降到 15–20 左右。先运行 --dry 查看候选数量。

2. 候选甄别顺序(AutoBlockService::run)

每个收集到的候选 IP 在真正被封禁之前,必须依次通过以下关卡。

  1. 内部/自有网段排除 —— 若 SecurityScope::isInternal($ip) 成立则跳过(skipped_internal)。该判定把已登记实例的 IP(/24 前缀+精确 IP)与运维策略中的 internal_networks 白名单合并考虑。查询阶段还会通过 NOT LIKE 预先排除 excludePrefixes()。
  2. 白名单豁免 —— 若 infr_whitelist 中存在有效且未过期的 /32 或 /24 条目则跳过(skipped_wl)。
  3. 无罪判定否决 —— 仅适用于没有硬信号(严重级别触发条件)的纯 JA3 候选。在 reputation_cap 的预算内查询 AbuseIPDB 信誉;如果该 IP 完全清白(滥用评分为 0 且 举报数为 0 且 非 Tor),则不予封禁,而是自动加入白名单 innocent_days 天(infr_whitelist(source=auto),即 innocent)。严重级别候选会跳过此关卡,直接封禁。
  4. 解析 server_index —— global→[0],per_server→通过 ServerIndexResolver::byIp 解析目标 dest_ip(回退为 [0])。
  5. 写入 —— 通过 InfrBlock::updateOrCreate([server_index, cidr], …) upsert 到 infr_block,并带上 source='schedule'、is_active=true、reason='[InfraGuard Auto] …'。
收集候选(OR) → 排除内部 → 白名单 → (纯 JA3)无罪判定否决 → server_index → 写入 infr_block

当信誉查询失败、未配置或无法判定时,isSpotless() 返回 false(即谨慎封禁)。无罪豁免是一道保守的关卡,只有在 IP 明显清白时才会生效。

3. 调度器与命令

infr:auto-block 是一条只评估一次就退出的单次命令,而不是常驻守护进程。调度器每分钟触发一次,但实际间隔由 autoblock_interval_min 决定。

# 检查门控(若策略为关闭则立即空操作),随后评估并写入一次
php artisan infr:auto-block

# 只统计候选数量,不写入(用于调优/验证)
php artisan infr:auto-block --dry

# 绕过策略门控(开关+间隔),强制运行一次
php artisan infr:auto-block --force

# 未产生封禁时抑制输出(用于定时调度)
php artisan infr:auto-block --quiet-if-ok

调度注册(routes/console.php):

Schedule::command('infr:auto-block --quiet-if-ok')
    ->everyMinute()
    ->withoutOverlapping(2)
    ->onOneServer()
    ->when($is_leader);   // 主节点门控 —— 整个机群中只有一个节点执行

门控逻辑:

输出示例:

infr:auto-block —— 候选 12(sev 3 · ja3 9) · 封禁 7 · wl-skip 1 · internal-skip 2 · innocent 2

4. 路径 ① 采集器自动封禁(Node)

各主机上的 Node 采集器消费 Suricata 事件,并按 /24 粒度直接写入 blacklist_info 完成封禁。该路径独立于调度器(路径 ②),且始终运行。

采集器 层次 检测内容
infr_eve_ingest.cjs 摄取 Suricata eve.json → 载入 suricata_http_ct · suricata_tls_ct · suricata_flow_ct · suricata_alert_ct
infr_srct_tx.cjs 特征/启发式 聚合 Suricata L7 → 5 类扫描器/机器人检测 → blacklist_info /24
infr_kts_tx.cjs Kitsune ML conntrack + 自编码器异常检测(anomaly_score) → blacklist_info /24

srct_tx 的五类威胁:

(A) [HTTP] URL 扫描器        .env / .git / wp-login 等带扫描器特征的 URL
(B) [HTTP] 机器人 User-Agent curl / python-requests / sqlmap 等工具特征
(C) [HTTP] 暴力探测          短时间内大量 4xx 响应(路径暴力枚举)
(D) [TLS]  JA3 异常          JA3 数量多、缺失 SNI 等僵尸网络指纹
(E) [FLOW] TCP 标志异常      大量短促的扫描会话(端口扫描)

srct_tx 同样会在封禁前执行白名单过滤+自动无罪豁免:只有 E-only(仅短扫描)候选才会做 AbuseIPDB 信誉查询,若明显清白(举报数 0 · 滥用评分 0 · 非 Tor)则登记为 30 天豁免(infr_whitelist(source=auto))并跳过封禁。只要存在任一硬信号(A/A2/B/C/D),就直接封禁。其常量与阈值在规则与阈值中管理。

5. 路径 ③ 强制生效 —— sync_black → ipset

infr_sync_black.mjs 在各主机上周期性运行,把两份名单同步进内核 ipset blacklist。

# 按主机运行,并传入其 server_index(需要 root)
node infr_sync_black.mjs sv=4

# crontab(每 5 分钟一次,用 flock 防止重叠执行)
*/5 * * * * /usr/bin/flock -n /var/run/sync_black.lock \
  /usr/bin/node /root/server_monitor/infr_sync_black.mjs sv=4 \
  >> /var/log/sync_black.log 2>&1

行为:

  1. 按 server_index IN (0, <sv>) AND is_active/is_use=1 从 blacklist_info(/24)+ infr_block(/32)载入数据。
  2. balanced 去重 —— 跳过父级 /24 已经覆盖的任何 /32。
  3. 强制把内核 ipset 设为 hash:net(类型不符则重建),先载入临时集合,再原子交换(无中断)。
  4. 确保 iptables -I INPUT -m set --match-set blacklist src -j DROP 存在(幂等)。
  5. 安全网 —— 私有地址(RFC1918)、回环地址与自有网段前缀(HOME_NET_PREFIX)即使混入数据库,也绝不会被载入 ipset。

sync_black 需要 root 权限以及内核的 ipset/iptables 工具,并且目标主机上必须已经存在 blacklist ipset:

ipset create blacklist hash:net family inet hashsize 8192 maxelem 65536
iptables -I INPUT -m set --match-set blacklist src -j DROP

数据来源

所有来源表与记录表都位于 legacy_glory 连接上(采集器与强制生效脚本共用的数据库)。

表 连接 作用 粒度
suricata_alert_ct legacy_glory 严重级别触发条件的来源(列 alert_ts · severity · src_ip · dest_ip · signature) —
suricata_tls_ct legacy_glory JA3 轮换触发条件的来源(列 event_ts · ja3_hash · src_ip) —
blacklist_info legacy_glory 采集器自动封禁名单(IDS 程序 · srct/kts) /24
infr_block legacy_glory 由 InfraGuard 调度器拥有的封禁名单(迁移 000751) /32·/24
infr_whitelist legacy_glory 封禁豁免(手动 · 自动无罪 · 种子数据) /32·/24
infr_server_index default(MAIN) 稀疏、非连续的 server_index ↔ 别名/实例对照表(000750) —

infr_block 表结构(摘要)

列 类型 含义
server_index int 0=全局 · 其他=通过 infr_server_index 解析出的服务器
cidr varchar(43) 单个 IP(/32)或归一化后的 /24 · UNIQUE(server_index,cidr)
ip varchar(45) 原始 IP(用于展示)
reason varchar(500) 触发依据([InfraGuard Auto] sev1 x3 …)
source varchar(16) auto · manual · schedule
is_active bool sync_black 是否载入该条目
expires_at datetime NULL=永久
last_seen_at datetime 最近一次重新检出(用于清理判定)

访问权限

自动封禁没有面向用户的执行界面。相关的权限门控如下(级别:demo=1 · staff=5 · admin=10):

对象 所需权限
自动封禁策略配置(运维策略 ▸ 安全设置) 管理员(管理,级别 ≥5 / admin)
查看封禁结果(封禁列表) 安全只读(staff,级别 ≥2)
手动封禁/解除 · 写入白名单 staff(级别 ≥2)+审计日志
调度器执行(infr:auto-block) 系统(并非登录用户)—— 受主节点门控

调度器所用的凭据从不硬编码 —— 它们被外部化到 infr_collector / .env。sync_black 的数据库凭据与自有网段前缀同样通过环境变量或“清单 ▸ 工具”的混搭替换注入(不做硬编码)。

故障排查与常见问题

已经开启,但什么都没被封禁。

封禁没有落到真正的防火墙上。

无辜的 IP 被封禁了。

明明是 per_server,记录却落成了全局(server_index=0)。

调度器在多台服务器上同时运行。

相关条目

자동 차단 스위치가 있는 운영 정책 보안 탭
자동 차단 스위치가 있는 운영 정책 보안 탭
자동 차단 결과가 쌓이는 차단 목록
자동 차단 결과가 쌓이는 차단 목록