搜索全站
安全防护 15 分钟阅读

用 Fail2ban 防护 WordPress 暴力破解:规则编写与封禁策略

装了安全插件还是被扫?Fail2ban 在服务器层直接封 IP,比插件更早、更省资源。本文从安装、编写 WordPress 专用 filter、jail 配置到误封自救,给出完整方案。

U
UFO_PF 2022-01-20 · 阅读 15 分钟
用 安全防护
阅读提示
本文约 5,699 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:插件防不住的资源消耗

WordPress 安全插件(Wordfence、Cerber)确实能挡暴力破解,但它们工作在 PHP 层——请求已经进入了 WordPress。这意味着:

  • 每次尝试都要加载 WordPress 核心,消耗 CPU 和内存;
  • 高并发扫描时,插件本身可能成为瓶颈;
  • 插件被禁用或未安装时完全没有防护。

Fail2ban 工作在操作系统层:它扫描日志文件,发现异常模式就用 iptables/nftables 直接封掉 IP,后续请求连 Nginx 都到不了。资源消耗几乎为零。

原理分析:Fail2ban 的三个组成部分

  1. Filter(过滤器):用正则匹配日志里的”失败事件”,比如”密码错误”、”404 扫描”;
  2. Jail(监狱):把 filter 和动作绑定,定义”多久内失败几次就封多久”;
  3. Action(动作):封禁手段,通常是 iptables / nftables / firewalld。

工作流程:

日志新增一行 → filter 正则匹配 → 计数器 +1
→ 达到 maxretry → 执行 action(封 IP bantime 秒)
→ bantime 到期 → 自动解封

和插件的分工:Fail2ban 防的是”频次型攻击”(大量失败尝试),插件防的是”内容型攻击”(SQL 注入、文件上传)。两者互补,不是替代。

保姆级实操

第一步:安装

# Debian / Ubuntu
sudo apt install fail2ban -y

# CentOS / Rocky / AlmaLinux
sudo dnf install epel-release -y
sudo dnf install fail2ban fail2ban-firewalld -y

sudo systemctl enable --now fail2ban

第二步:先保护 SSH(必做)

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

编辑 /etc/fail2ban/jail.local:

[DEFAULT]
# 白名单:把自己的 IP 和内网加进去,防误封
ignoreip = 127.0.0.1/8 ::1 1.2.3.4 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16

bantime  = 3600          # 封 1 小时
findtime = 600           # 10 分钟内的统计窗口
maxretry = 5             # 失败 5 次触发
backend  = systemd       # 或 auto
destemail = you@example.com
sender   = fail2ban@mimangfei.com
action   = %(action_mwl)s   # 封禁 + 发邮件带日志

[sshd]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
maxretry = 3
bantime  = 86400         # SSH 失败 3 次封一天

第三步:写 WordPress 专用 filter

创建 /etc/fail2ban/filter.d/wordpress-auth.conf:

[Definition]
# 匹配 wp-login.php 的 POST 请求(登录尝试)
failregex = ^<HOST> .* "POST /wp-login\.php
            ^<HOST> .* "POST /wp-admin/admin-ajax\.php.*action=login
            ^<HOST> .* "GET /wp-login\.php\?action=(register|lostpassword)

ignoreregex =

注意:这个 filter 匹配的是”任何 POST wp-login.php”,包括你自己成功登录。所以 maxretry 要设宽松一点(比如 10 次 / 10 分钟),否则输错几次密码把自己封了。

更精准的方案:只匹配失败的登录

WordPress 默认日志不记录登录失败(需要插件)。两个办法:

办法 A:装 WP Fail2Ban Redux 插件,它会把登录失败写入 syslog,然后:

# /etc/fail2ban/filter.d/wordpress-hard.conf
[Definition]
failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
            ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
            ^%(__prefix_line)sXML-RPC authentication failure from <HOST>$
ignoreregex =
# jail.local
[wordpress-hard]
enabled  = true
filter   = wordpress-hard
logpath  = /var/log/auth.log     # Debian/Ubuntu
# logpath = /var/log/secure       # CentOS
maxretry = 3
bantime  = 86400
port     = http,https

[wordpress-soft]
enabled  = true
filter   = wordpress-soft
logpath  = /var/log/auth.log
maxretry = 5
bantime  = 3600
port     = http,https

办法 B:用 Nginx 状态码匹配。给失败的 wp-login 请求打标记比较麻烦,退而求其次——匹配高频 404 / 403:

# /etc/fail2ban/filter.d/nginx-wp-scan.conf
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login|xmlrpc|wp-config|\.env|\.git|phpmyadmin|adminer|setup-config)
ignoreregex = ^<HOST> .* "(GET|POST) /wp-login\.php.* 200

这个 filter 能抓到绝大多数自动化扫描器(它们会探测 wp-config.php.bak、.env、xmlrpc.php 等)。

第四步:配置 jail

在 /etc/fail2ban/jail.local 追加:

[nginx-wp-login]
enabled  = true
filter   = wordpress-auth
logpath  = /var/log/nginx/access.log
maxretry = 10
findtime = 600
bantime  = 3600
port     = http,https
action   = iptables-multiport[name=wp, port="http,https", protocol=tcp]

[nginx-wp-scan]
enabled  = true
filter   = nginx-wp-scan
logpath  = /var/log/nginx/access.log
maxretry = 3
findtime = 300
bantime  = 86400
port     = http,https

第五步:重载并验证

sudo fail2ban-client reload
sudo fail2ban-client status
# 应看到 jail 列表

sudo fail2ban-client status nginx-wp-login
# 看 Currently banned / Total banned

测试 filter 是否匹配(重要):

# 用真实日志测试正则
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

输出会显示匹配了多少行。如果 0 行,说明正则和你的日志格式不符,必须调。

第六步:配置邮件告警(可选)

[DEFAULT]
destemail = admin@mimangfei.com
sender    = fail2ban@mimangfei.com
mta       = sendmail
action    = %(action_mwl)s

邮件发送需要服务器能发信,参考WordPress SMTP 邮件配置里的方法配置好 MTA。

第七步:常用运维命令

# 查看某 jail 状态
sudo fail2ban-client status nginx-wp-login

# 手动解封
sudo fail2ban-client set nginx-wp-login unbanip 1.2.3.4

# 手动封禁
sudo fail2ban-client set nginx-wp-login banip 1.2.3.4

# 查看 iptables 规则
sudo iptables -L f2b-nginx-wp-login -n -v

常见踩坑

  1. 把自己封了:ignoreip 没加自己的 IP。自救办法:从另一台机器(或手机 4G)SSH 上去执行 fail2ban-client unbanip,或者直接改 ignoreip 后 reload。
  2. 日志格式不匹配:Nginx 的 log_format 用了 combined 还是 main,IP 是 $remote_addr 还是 $http_x_forwarded_for,决定 <HOST> 能不能匹配到。用 fail2ban-regex 验证,不要凭感觉。
  3. CDN 下封错 IP:如果前置 Cloudflare,$remote_addr 是 CDN 节点 IP,封 IP 等于封掉 CDN,全站不可访问。必须先在 Nginx 里用 real_ip 还原真实 IP,并把日志格式改成记录还原后的 IP,参考反向代理下获取真实 IP。
  4. 案例:某站 /var/log/nginx/access.log 被 logrotate 每天切割,Fail2ban 持有的文件句柄指向已被删除的旧文件,导致监控失效却不报错。解决:Fail2ban 的 backend 设为 auto 或 polling,或确保 logrotate 用 copytruncate 并配合 postrotate 重载 fail2ban。
  5. 封禁不生效:检查 action 用的是 iptables 还是 nftables。Debian 11+/Ubuntu 22.04+ 默认 nftables,如果 Fail2ban 配的是 iptables 会不生效。查看:

bash
sudo fail2ban-client status nginx-wp-login | grep -i "action"

现代系统建议改用 action = nftables-multiport[name=wp, port="http,https"]。

  1. Docker 环境:Docker 自己写 iptables 规则,可能和 Fail2ban 冲突(Fail2ban 的链顺序在 Docker 之后,封不住)。这种场景建议在上层反向代理做限流(Nginx limit_req)。
  2. Nginx 限流作为补充:Fail2ban 是事后封禁,Nginx 的 limit_req 是事前限流,两者配合更好:

“`nginx
# http 块
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=10r/m;

# server 块
location = /wp-login.php {
limit_req zone=wplogin burst=5 nodelay;
# … PHP 处理
}
“`

总结

Fail2ban 是服务器层防护的核心工具:零性能开销、自动封禁、自动解封。落地四个要点:ignoreip 先加白名单、filter 正则必须用 fail2ban-regex 实测、CDN 环境先还原真实 IP、现代系统注意 iptables/nftables 的选择。建议至少配三个 jail:sshd(3 次封一天)、wordpress 登录失败(配合 WP Fail2Ban 插件)、扫描探测(匹配 .env / xmlrpc.php / wp-config 等路径,3 次封一天)。配合 限制 wp-admin 访问的 IP 白名单,后台安全性会有质的提升。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。