记一次 SSH 暴力破解触发 fail2ban 自动封禁的处置

公网服务器遭境外 IP 海量 SSH 暴破,fail2ban 自动封禁后分析攻击源并加固 SSH 防护。

一、问题背景

我司有一台面向公网的跳板/运维服务器 bastion01(CentOS 7.9,内核 3.10,公网 IP 203.xx.xx.30),用于 SSH 接入内网资产,对外开放 22 端口。为防范暴破,早年已部署 fail2ban 0.11,对 SSHD 的 sshd jail 配置为 5 分钟内失败 5 次即 ban 600 秒。同时系统启用 firewalld 作为底层防火墙。该服务器承载约 30 名运维人员的日常登录,平时流量平稳。

2025-12-16 下午 17:40 左右,我收到 Zabbix 告警:bastion01 的 CPU 软中断(si)与 sshd 进程数异常升高,同时 fail2ban 的 ban 动作日志出现频率激增。作为当班安全运维,我登录研判,确认为一次规模化 SSH 暴力破解攻击。

二、故障现象

fail2ban 日志(/var/log/fail2ban.log)在 17:38 起密集出现封禁记录:

1
2
3
4
2025-12-16 17:38:12 bastion01 fail2ban.actions[812]: NOTICE [sshd] Ban 193.xx.xx.22
2025-12-16 17:38:55 bastion01 fail2ban.actions[812]: NOTICE [sshd] Ban 45.xx.xx.91
2025-12-16 17:39:31 bastion01 fail2ban.actions[812]: NOTICE [sshd] Ban 218.xx.xx.170
... (17:38-17:58 共 Ban 412 个 IP)

系统侧现象:

  • journalctl -u sshd 显示海量 Failed password for invalid userFailed password for root,每秒数十次。
  • iptables -L f2b-sshd -n -v 中 fail2ban 自动生成的链已积累数百条 DROP 规则,单链接近上限。
  • CPU 中 si(软中断)占用达 35%,sshd 子进程堆积到 200+,last 显示无成功暴破登录(说明账号侧暂未失守,但服务被"噪声"拖慢)。

直观判断:服务器正遭受来自大量境外 IP 的分布式 SSH 暴破,fail2ban 在自动封禁,但规则膨胀已影响性能。

三、排查过程

第一步:量化攻击规模与来源。

统计 /var/log/secure 中的失败登录来源与尝试账号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head
#   412 193.xx.xx.22
#   388 45.xx.xx.91
#   351 218.xx.xx.170
#   ... (Top 来源为俄罗斯/巴西/越南 ASN)
grep "Failed password" /var/log/secure | awk '{print $9}' | sort | uniq -c | sort -rn | head
#   root       2981
#   admin       1102
#   test        883
#   oracle      540

确认攻击者主要爆破 root 及常见弱账号名,且使用分布式 IP 池以规避单 IP 封禁阈值。

第二步:确认是否已有成功入侵。

检查成功登录记录与异常会话:

1
2
3
4
last -ai | head
# 仅见到 30 名已知运维的公网/办公网 IP,无境外成功登录
grep "Accepted password" /var/log/secure | grep -vE "58.xx.xx.10|10.20" | wc -l
# 0  (无异常成功登录)

同时核查 authorized_keys/etc/passwd 有无新增可疑账号,未发现后门。结论:攻击被拦截在认证阶段,尚未得手。

第三步:评估 fail2ban 自身压力。

1
2
3
4
iptables -L f2b-sshd -n | wc -l
# 470  (规则接近 iptables 单链 500 条经验上限,匹配性能下降)
systemctl status fail2ban | grep -i memory
# 内存占用较平日上涨 3 倍 (规则表过大)

确认 fail2ban 虽在正常工作,但 ban 列表无上限增长已造成性能劣化,需要临时减负。

四、解决方案

1. 临时减压(止血):

  • 缩短 ban 时长并限制单链规模,避免规则堆积拖垮性能:将 bantime 临时调为 3600 秒、并启用 maxmatches 去重。
  • 对攻击最密集的 3 个 ASN 段在 firewalld 上做整体封锁(比逐 IP 高效):
1
2
3
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=193.xx.xx.0/24 reject'
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=45.xx.xx.0/24 reject'
firewall-cmd --reload
  • 重启 fail2ban 以清空过度膨胀的 ban 列表:systemctl restart fail2ban

2. 加固 SSH 防护(治本):

编辑 /etc/ssh/sshd_configsystemctl restart sshd

1
2
3
4
5
PermitRootLogin no
PasswordAuthentication no        # 全面改用密钥登录
MaxAuthTries 3
LoginGraceTime 20
AllowUsers ops1 ops2 ops3        # 显式白名单

为 30 名运维统一签发 ED25519 密钥并禁用口令,从根本上消除口令暴破面。

3. 收口网络暴露面:

通过边界防火墙将 22 端口仅对运维办公网段(58.xx.xx.10/24)与 VPN 网段开放,公网不再直接暴露;保留 fail2ban 作为纵深防御的最后一道。

处置后 18:10 观测,失败登录归零,sshd 进程数回落到个位数,CPU si 恢复正常。

五、根因分析

  1. 暴露面过大:bastion01 的 22 端口直接对公网全开,等于把认证入口暴露在全球暴破僵尸网络面前,这是事件发生的根本原因。
  2. 口令认证脆弱:即便有 fail2ban,口令登录仍给攻击者留下"试错"空间;分布式 IP 池还能稀释单 IP 失败计数,绕过敏感阈值。
  3. fail2ban 配置粗放bantime 与规则上限未设防,攻击洪峰下 ban 列表无限膨胀,反而成为新的性能负担,属于"防御机制被攻击放大"的次生问题。
  4. root 可登录PermitRootLogin yes 直接给攻击者明确的爆破目标用户名。

六、预防措施

  • 最小化暴露:所有公网 SSH 仅对受信网段/VPN 开放,非必要不暴露 22;可改用非标准端口 + 端口敲门(port knocking)进一步降低被扫概率。
  • 密钥优先:推行 PasswordAuthentication no + ED25519/RSA 密钥 + 可选硬件 Key(FIDO2),口令暴破从根上失效。
  • 精细化 fail2ban:设置 bantime.increment = true 递增封禁、maxretry 收紧、maxmatches 限制规则数,并配合 ipset 而非纯 iptables 链以承载大规模封禁。
  • 部署防暴破纵深:前置 fail2ban 之外,可叠加 Cloudflare/网关层的 WAF 与速率限制,或在堡垒机启用 MFA(TOTP)。
  • 常态化监控:将 fail2ban 的 ban 速率、sshd 失败计数接入 Zabbix/Grafana 看板,设置"单位时间内 ban 数突增"的主动告警,先于性能劣化被发现。

七、总结

这次事件是公网 SSH 暴露面被分布式暴破的典型案例。fail2ban 在认证层成功拦截了全部入侵尝试、未让任何境外 IP 登录得手,证明了它在"最后一道防线"上的价值;但它也用自身规则膨胀的副作用提醒我们:防御机制本身需要被正确调参,否则在攻击洪峰下会变成新的瓶颈。真正的解决之道不在"封得更狠",而在"暴露得更小"——把 22 端口收进受信网段、用密钥替代口令,让暴破连"试错的机会"都没有。安全运维的常态,就是用一层层收敛的攻击面,把麻烦挡在门外头。

使用 Hugo 构建
主题 StackJimmy 设计