CentOS服务器只要暴露在公网上,每天都会遭受数以万计的暴力破解尝试。检查一下你的SSH登录日志,大概率会看到来自世界各地IP的连续尝试记录。攻击者利用自动化工具扫描22端口、3306端口等常见服务,用弱口令字典反复碰撞,一旦得手服务器就会被植入挖矿木马或勒索病毒。解决这个问题最直接有效的工具就是fail2ban——它能实时监控日志文件,识别恶意行为后自动封禁IP,把暴力破解的威胁消灭在萌芽状态。

fail2ban的工作原理

fail2ban本质上是一个日志分析引擎,它通过正则表达式匹配日志中的失败记录,当某个IP在设定时间内达到失败阈值,就调用系统防火墙规则将其屏蔽一段时间。整个流程分为三层:过滤器定义匹配规则,动作定义封禁方式,监狱将过滤器和动作组合起来应用到具体服务。默认情况下fail2ban使用iptables/firewalld来封禁IP,封禁到期后自动解封,避免误伤正常用户。这个机制的精妙之处在于它不修改任何服务配置,完全在系统层面做访问控制,对应用透明且安全可靠。

安装fail2ban的正确姿势

CentOS 7/8/9的EPEL仓库都收录了fail2ban,直接安装即可。注意不要用系统默认的firewalld和fail2ban同时操作iptables规则,会产生冲突。推荐做法是统一使用firewalld作为防火墙后端,fail2ban通过firewallcmd命令添加富规则。

# 安装EPEL仓库(如果还没装)
yum install -y epel-release

# 安装fail2ban
yum install -y fail2ban

# 设置开机自启
systemctl enable fail2ban
systemctl start fail2ban

安装完成后不要急着启动,先做好配置。fail2ban的配置文件在/etc/fail2ban/目录下,核心是jail.conf。官方强烈建议不要直接修改jail.conf,因为软件更新会覆盖它。正确做法是创建jail.local文件,里面的配置会覆盖jail.conf的同名参数。

核心配置文件jail.local详解

创建/etc/fail2ban/jail.local,这是整个防护体系的中枢。先看一个生产环境的基础配置:

[DEFAULT]
# 封禁时长,单位秒,86400就是24小时
bantime = 86400

# 计算失败次数的时间窗口,单位秒
findtime = 600

# 在findtime时间内允许的最大失败次数
maxretry = 5

# 忽略的IP列表,自己的固定IP加进去防止误封
ignoreip = 127.0.0.1/8 ::1 你的办公IP

# 封禁动作,使用firewalld
banaction = firewallcmd-ipset

# 动作执行方式
action = %(action_mwl)s

# 日志级别
loglevel = INFO

# 日志文件路径
logtarget = /var/log/fail2ban.log

这几个参数值得仔细琢磨。bantime设太短攻击者会换IP继续来,设太长可能误伤动态IP的合法用户。生产环境建议86400秒起步,对SSH这种高风险服务可以设到259200秒也就是三天。findtime和maxretry要配合使用,比如findtime=600、maxretry=5表示10分钟内失败5次就封禁。这个阈值要结合业务实际,设太严用户输错几次密码就被封,设太松又挡不住攻击。ignoreip一定要把自己的管理IP加进去,不然自己操作失误也会被封在外面,到时候只能通过VNC或者控制台救急。

SSH防护配置

SSH是攻击的重灾区,必须重点设防。在jail.local中添加SSH监狱:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 259200
findtime = 600

这里把maxretry降到3次,bantime提到3天。SSH登录输错密码的情况很少,正常用户不会连续错3次。如果你的SSH改了端口,port参数要改成实际端口号,比如port=2222。filter定义了匹配规则,sshd这个过滤器能识别常见的SSH认证失败日志,包括密码错误、密钥认证失败、无效用户等场景。

有个容易被忽略的细节:fail2ban默认只监控密码认证失败,如果你用了密钥认证,攻击者尝试用不存在的用户名连接也会产生日志,但默认过滤器可能匹配不到。建议检查/etc/fail2ban/filter.d/sshd.conf,确认包含了对"Connection closed by invalid user"这类日志的匹配规则。如果缺失,可以自定义过滤器文件sshd.local来补充。

Web服务防护

Nginx和Apache的防护思路类似,都是监控访问日志和错误日志中的异常模式。以Nginx为例,常见需要防护的场景包括:扫描敏感路径、暴力破解后台登录、恶意爬虫、SQL注入探测等。

[nginx-404]
enabled = true
port = http,https
filter = nginx-404
logpath = /var/log/nginx/access.log
maxretry = 30
bantime = 3600
findtime = 300

这个监狱专门对付扫描器,正常用户不会在5分钟内触发30个404页面。攻击者用目录扫描工具时会大量请求不存在的路径,这个规则能有效拦截。阈值设30次是为了避免误封,因为有些单页应用的前端路由也会产生404,需要根据自己网站情况调整。

对于WordPress或类似CMS的后台登录防护,需要自定义过滤器:

[nginx-wp-login]
enabled = true
port = http,https
filter = nginx-wp-login
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 7200
findtime = 600

然后在/etc/fail2ban/filter.d/nginx-wp-login.conf中定义匹配规则:

[Definition]
failregex = ^ -.*POST.*/wp-login.php.* HTTP/1.* 200
ignoreregex =

这条正则会匹配对wp-login.php的POST请求且返回200状态码的记录,也就是登录尝试。攻击者爆破WordPress后台时会产生大量这类日志,5分钟内超过5次就封IP两小时。

数据库防护

MySQL/MariaDB的3306端口也是攻击重点。配置数据库防护:

[mysqld-auth]
enabled = true
port = 3306
filter = mysqld-auth
logpath = /var/log/mysql/error.log
maxretry = 5
bantime = 86400
findtime = 600

这里有个前提条件:MySQL的错误日志需要开启并记录认证失败信息。在MySQL配置文件/etc/my.cnf中添加:

[mysqld]
log_error = /var/log/mysql/error.log

然后重启MySQL使配置生效。如果数据库只允许本地访问,这个防护可以不开。但如果你的应用服务器和数据库分离,或者有远程管理需求,这个防护就很有必要。

邮件服务防护

Postfix和Dovecot的防护主要针对SMTP认证暴力破解。攻击者破解邮箱账号后可以发送垃圾邮件,导致服务器IP被列入黑名单。

[postfix]
enabled = true
port = smtp,465,587
filter = postfix
logpath = /var/log/maillog
maxretry = 5
bantime = 86400
findtime = 600

[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps
filter = dovecot
logpath = /var/log/maillog
maxretry = 5
bantime = 86400
findtime = 600

邮件服务的防护阈值可以适当放宽,因为用户配置邮件客户端时可能会多次输入错误密码。如果业务量不大,建议把maxretry调到8-10次,减少用户投诉。

高级技巧:自定义过滤器和动作

预置的过滤器覆盖了常见场景,但实际生产环境往往需要定制。比如你的应用把登录日志写到了自定义路径,就需要自己写过滤器。过滤器文件的核心是正则表达式,用failregex定义匹配模式,ignoreregex排除误报。

举个例子,假设你的Java应用日志格式是这样:

2024-01-15 10:23:45 WARN Login failed from IP 192.168.1.100, username admin

对应的过滤器可以写成:

[Definition]
failregex = ^\s*Login failed from IP , username .*$
ignoreregex =

这里的HOST是fail2ban的内置占位符,会自动提取IP地址。写好过滤器后放到/etc/fail2ban/filter.d/目录下,然后在jail.local中引用即可。

动作方面,除了默认的firewalld封禁,还可以配置邮件通知、Slack告警、自定义脚本等。在jail.local的action参数中可以叠加多个动作:

action = %(action_mwl)s
         slack-notify[webhook_url=https://hooks.slack.com/services/xxx]

这样每次封禁和解封都会发送Slack通知,方便运维团队实时掌握安全动态。

fail2ban日常运维要点

fail2ban运行起来后需要定期维护。几个常用命令必须掌握:

# 查看所有监狱状态
fail2ban-client status

# 查看某个监狱的封禁记录
fail2ban-client status sshd

# 手动封禁一个IP
fail2ban-client set sshd banip 192.168.1.100

# 手动解封一个IP
fail2ban-client set sshd unbanip 192.168.1.100

# 检查过滤器正则是否匹配到日志
fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/sshd.conf

最后一个命令特别有用,在部署新过滤器之前先用它测试,确保正则能正确匹配到目标日志行。很多fail2ban配置不生效的原因就是正则没写对,用fail2ban-regex可以直观看到匹配结果。

日志监控也不能忽视。fail2ban的日志在/var/log/fail2ban.log,里面记录了每次封禁和解封的详细信息。建议配置logrotate防止日志撑满磁盘,/etc/logrotate.d/fail2ban通常已经配好,检查一下确认rotate周期符合需求。

常见坑和解决方案

第一个坑是时区问题。fail2ban默认使用系统时区,如果日志时间戳和系统时区不一致,会导致findtime计算偏差。统一使用UTC或者确认所有日志和系统时区一致。

第二个坑是Docker环境。如果服务跑在容器里,日志可能通过stdout输出而不是写文件,fail2ban监控不到。解决方案是用docker的日志驱动把日志写到宿主机文件,或者用journald统一收集。

第三个坑是IPv6。默认配置可能只处理IPv4,如果你的服务器开启了IPv6,攻击者会走IPv6绕过防护。在jail.local的DEFAULT段加上:

[DEFAULT]
protocol = tcp
banaction = firewallcmd-ipset[family=inet6]

第四个坑是CDN和反向代理。如果服务器前面套了CDN,fail2ban看到的所有请求都来自CDN节点IP,封禁会把正常流量也挡掉。这种情况需要在应用层获取真实IP并写入日志,然后fail2ban解析真实IP。Nginx可以用X-Forwarded-For头,但要注意这个头可以被伪造,需要配合set_real_ip_from指令只信任CDN的IP段。

性能优化和资源控制

fail2ban本身资源消耗很低,但封禁列表太大会影响iptables/firewalld的性能。如果服务器遭受大规模DDoS,封禁列表可能膨胀到几万条,防火墙规则匹配会变慢。建议配合ipset使用,firewallcmd-ipset动作会把封禁IP加入ipset集合,比逐条添加iptables规则高效得多。

另外可以设置封禁上限,在jail.local中:

[DEFAULT]
maxmatches = 1000

这个参数限制单个监狱同时封禁的IP数量,超过后旧的封禁会被新的替换,防止内存和规则表溢出。

定期清理过期封禁也很重要,fail2ban会自动解封到期IP,但如果服务异常重启可能导致部分封禁残留。可以写个cron脚本每周执行一次fail2ban-client unban --all,然后重启fail2ban服务,让所有规则从干净状态重建。

把fail2ban部署好之后,服务器的安全基线就提升了一个台阶。它不能替代防火墙、WAF等纵深防御措施,但作为主机层的自动化防护,性价比极高。攻击者面对一个会主动封IP的目标,大概率会转向更容易得手的猎物。安全防护的本质就是提高攻击成本,fail2ban在这方面做得相当出色。