翻看服务器日志,最触目惊心的不是报错,而是那些如潮水般涌来的SSH登录失败记录。一条两条是手误,但同一秒内从同一个IP地址冒出几十次尝试,用root、admin、test这些常见用户名反复撞门,这就是典型的暴力破解攻击。攻击者手里握着成百上千的密码字典,脚本一旦启动,就会像一台不知疲倦的机器,对着22端口发起密集冲锋。如果你看到的日志里,短短几分钟内出现了数百条“Failed password for root from 192.168.1.100 port 22 ssh2”这样的记录,说明你的服务器正在被正面强攻。

真正需要警惕的是那些慢速爆破。攻击者很狡猾,他们知道频繁连接会被封禁,于是把尝试间隔拉长到几十秒甚至几分钟一次。在日志里,这类攻击的特征是同一个IP地址每隔30秒或60秒出现一次失败记录,持续数小时甚至数天。这种低频攻击很容易淹没在正常的登录失败中,等你发现时,对方可能已经试完了上千个密码组合。还有一种分布式暴力破解,攻击来源不是单一IP,而是几十上百个不同的IP地址,每个IP只尝试几次就换下一个。这种攻击方式专门用来对付简单的频率限制策略,日志看起来像是大量用户同时忘记了密码,极具迷惑性。

从日志中精准识别攻击模式

先看一段典型的SSH暴力破解日志片段,取自/var/log/auth.log:

Oct 24 03:12:31 server sshd[21045]: Failed password for root from 45.33.32.156 port 45678 ssh2
Oct 24 03:12:32 server sshd[21045]: Failed password for root from 45.33.32.156 port 45678 ssh2
Oct 24 03:12:34 server sshd[21045]: Failed password for invalid user admin from 45.33.32.156 port 45678 ssh2
Oct 24 03:12:35 server sshd[21049]: Failed password for root from 45.33.32.156 port 45680 ssh2
Oct 24 03:12:37 server sshd[21049]: Failed password for invalid user test from 45.33.32.156 port 45680 ssh2
Oct 24 03:12:38 server sshd[21051]: Failed password for root from 45.33.32.156 port 45682 ssh2
Oct 24 03:12:40 server sshd[21051]: Failed password for invalid user oracle from 45.33.32.156 port 45682 ssh2

这段日志暴露了几个关键信息。第一,时间戳高度密集,6秒内出现了7次失败尝试。第二,目标用户名在root和多个无效用户之间切换,这是典型的字典攻击特征,攻击脚本内置了常用用户名列表。第三,源IP地址始终是45.33.32.156,但源端口在变化,说明攻击者不断建立新的TCP连接。第四,进程ID在变化,从21045到21051,意味着每次连接都fork了新的sshd子进程。这些特征组合在一起,可以百分百确定这是一次自动化暴力破解攻击。

再看一种更隐蔽的慢速攻击日志:

Oct 24 04:15:22 server sshd[22103]: Failed password for root from 103.45.67.89 port 50234 ssh2
Oct 24 04:16:25 server sshd[22118]: Failed password for root from 103.45.67.89 port 50245 ssh2
Oct 24 04:17:28 server sshd[22133]: Failed password for root from 103.45.67.89 port 50256 ssh2
Oct 24 04:18:31 server sshd[22148]: Failed password for root from 103.45.67.89 port 50267 ssh2

这里每次尝试间隔约63秒,非常规律。攻击者刻意避开了默认的触发阈值,如果你的Fail2ban配置只是简单统计60秒内的失败次数,这种攻击就能轻松绕过。识别这类攻击需要拉长时间窗口,或者分析失败尝试的时间序列规律。人工排查时,可以用awk对日志中的IP进行聚合统计,计算时间跨度内的尝试频率,那些间隔均匀且持续出现的IP就是重点怀疑对象。

Fail2ban基础配置的局限与突破思路

很多人安装Fail2ban后直接使用默认配置,这远远不够。默认的jail.local配置通常只监控SSH服务,查找“Failed password”关键词,60秒内失败6次就封禁10分钟。这个阈值在当下已经形同虚设。攻击者可以轻松将频率控制在每分钟5次以内,或者使用IP池轮换。更致命的是,默认配置只匹配标准失败信息,对于“Connection closed by authenticating user”、“Did not receive identification string”、“Bad protocol version identification”这类非标准但同样可疑的日志行视而不见。

要构建有效的防御,必须从日志分析反推规则设计。第一步是穷举所有可能的失败模式。除了最基础的密码错误,还有用户不存在、密钥验证失败、协议版本不匹配、连接后立即断开等行为。这些行为单独出现可能只是网络问题,但如果同一个IP在短时间内触发了多种不同类型的失败,那几乎可以肯定是恶意扫描或攻击前期的探测行为。

构建多维度匹配的高级过滤规则

在/etc/fail2ban/filter.d/目录下创建自定义过滤器文件sshd-advanced.conf,内容如下:

[Definition]
failregex = ^%(__prefix_line)sFailed password for .* from  port \d+ ssh2
            ^%(__prefix_line)sFailed password for invalid user .* from  port \d+ ssh2
            ^%(__prefix_line)sConnection closed by authenticating user .*  port \d+ \[preauth\]
            ^%(__prefix_line)sDid not receive identification string from  port \d+
            ^%(__prefix_line)sBad protocol version identification .* from  port \d+
            ^%(__prefix_line)sUnable to negotiate with  port \d+: no matching key exchange method found
            ^%(__prefix_line)sReceived disconnect from  port \d+:.*\[preauth\]
ignoreregex =

这个过滤器同时捕获了七种不同的失败场景。第一条匹配已知用户的密码错误,第二条匹配尝试不存在的用户,第三条是认证过程中连接被关闭,第四条是连接后没有发送任何身份标识字符串,第五条是协议版本标识异常,第六条是密钥交换算法不匹配,第七条是预认证阶段收到断开连接请求。把这些规则组合在一起,攻击者无论用哪种方式试探,都会被记录在案。

光有过滤器还不够,需要在jail.local中设置更智能的触发策略。下面是一个进阶配置:

[sshd-advanced]
enabled = true
filter = sshd-advanced
logpath = /var/log/auth.log
maxretry = 3
findtime = 300
bantime = 7200
action = iptables-multiport[name=ssh-advanced, port="ssh", protocol=tcp]
         %(action_mwl)s

这里把findtime拉长到300秒,也就是5分钟,maxretry降到3次。意味着5分钟内只要触发任意3次匹配规则,IP就会被封禁2小时。这个配置对慢速攻击有很好的压制效果,因为攻击者很难在5分钟内只尝试2次密码——那样破解效率太低了。同时,多种失败类型的组合捕获让攻击者即使切换手法也会被累计计数。

针对分布式攻击的联动防御策略

面对IP池轮换的分布式暴力破解,单靠Fail2ban封禁单个IP已经不够用了。这时候需要引入行为分析和联动机制。一个实用的思路是监控短时间内被封禁IP的网段集中度。如果发现来自同一/24网段的多个IP在短时间内相继被封禁,说明攻击者可能控制了整个C段的主机,或者在使用某个云服务商的IP池。这种情况下,可以编写一个简单的脚本来分析Fail2ban日志,当检测到同一网段被封IP超过阈值时,自动添加整个网段的临时封锁规则。

以下是一个bash脚本示例,可以放在crontab中定期执行:

#!/bin/bash
# 分析fail2ban日志,检测同一/24网段被封IP数量
BANNED_IPS=$(fail2ban-client status sshd-advanced | grep "Banned IP list" | cut -d: -f2)
THRESHOLD=5

declare -A SUBNET_COUNT

for IP in $BANNED_IPS; do
    SUBNET=$(echo $IP | cut -d. -f1-3)
    SUBNET_COUNT[$SUBNET]=$((SUBNET_COUNT[$SUBNET] + 1))
done

for SUBNET in "${!SUBNET_COUNT[@]}"; do
    if [ ${SUBNET_COUNT[$SUBNET]} -ge $THRESHOLD ]; then
        # 检查是否已经封锁了整个网段
        if ! iptables -L INPUT -n | grep -q "${SUBNET}.0/24"; then
            iptables -A INPUT -s ${SUBNET}.0/24 -p tcp --dport 22 -j DROP
            echo "$(date) Blocked entire subnet ${SUBNET}.0/24 due to distributed attack" >> /var/log/subnet-block.log
        fi
    fi
done

这个脚本每运行一次,就检查当前被封禁的IP列表,按/24网段聚合统计。如果某个网段被封IP达到5个,就自动封锁整个网段。这种策略虽然可能误伤同一网段下的正常用户,但在遭受严重攻击时是必要的取舍。实际部署时可以根据业务情况调整阈值,或者结合白名单机制排除可信网段。

日志监控与持续优化

规则部署之后,工作只完成了一半。持续监控日志才能发现规则是否有效、是否存在漏网之鱼。建议每天检查一次Fail2ban的封禁列表和日志摘要,重点关注那些触发规则但最终没有被封禁的IP,分析它们的行为模式,看看是否需要调整maxretry或findtime参数。同时要留意误封情况,如果有正常用户反馈无法登录,需要检查其IP是否在封禁列表中,并分析触发封禁的日志行,判断是规则过于严格还是用户确实操作异常。

一个实用的监控命令是统计每天各时段被封IP的数量变化:

grep "Ban" /var/log/fail2ban.log | awk '{print $1, $2}' | cut -d, -f1 | sort | uniq -c

这条命令可以快速看出攻击活动的时间分布。如果发现某个时段封禁数量激增,说明那个时段是攻击高峰,可以考虑在高峰时段临时收紧规则。另外,定期用fail2ban-regex工具测试日志样本也是好习惯,把最新的攻击日志片段保存下来,用自定义过滤器去匹配,验证是否能全部命中。命令格式为:

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-advanced.conf

这个工具会输出详细的匹配统计,包括哪些行被匹配、哪些行被忽略、匹配率是多少。如果发现某些明显的攻击行为没有被匹配到,就需要回到过滤器文件补充新的正则表达式。

从被动封禁到主动防御的思维转变

大多数运维人员使用Fail2ban停留在“等攻击来了再封”的阶段。更高级的用法是利用日志数据进行威胁情报积累。那些反复出现在封禁列表中的IP,可以整理成黑名单,通过脚本自动同步到其他服务器的iptables规则中,实现集群级的联防。更进一步,可以把这些恶意IP提交到公开的威胁情报平台,或者从这些平台拉取已知的恶意IP列表,导入到Fail2ban的ignoreip反向使用——不是忽略,而是作为预置黑名单直接封锁。

还有一种主动防御思路是设置蜜罐端口。在非标准端口上运行一个伪装的SSH服务,记录所有连接尝试,任何连接这个端口的IP都直接加入封锁列表。因为正常用户不会连接你故意隐藏的端口,所有访问者都是扫描器或攻击脚本。这种策略的误报率极低,可以作为高可信度的封禁来源。

日志分析的最高境界不是被动响应,而是通过长期积累的数据,建立起攻击者的行为画像。你会发现某些IP段专门在凌晨活动,某些用户名组合是特定僵尸网络的标志,某些失败模式预示着新型扫描工具的出现。这些洞察不仅能优化Fail2ban规则,更能让你在安全防御上始终领先一步。