实战 Fail2ban:配置 banip + 中断连接,暴力封锁恶意扫描器
安全实践

Fail2ban 暴力封锁恶意扫描器

banip + 中断连接,构建“露头就秒”的自动防御体系
📅 2026年8月
⏱ 阅读约 10 分钟

WordPress 站点上线后,访问日志中很快就会出现大量针对 xmlrpc.php、wp-login.php、wp-admin/install.php 等路径的扫描请求。这些请求来自自动化扫描器,频率高、IP 池大,手动封禁显然不现实。利用 Fail2ban 构建一套“露头就秒”的自动化封禁体系,是应对这类攻击的有效手段。

适用场景:不对外开放注册和登录的个人站点、博客、内部系统。

核心策略:maxretry = 1

对于不对外开放注册和登录的个人站点,所有对敏感路径的访问都来自扫描器或攻击者。将 maxretry 设置为 1,意味着任何 IP 只要访问一次被监控的敏感路径,就会被立即封禁。findtime 参数在这种配置下实际影响不大——无论统计窗口多长,第一次匹配就触发封禁。

bantime 设置为 604800 秒(7 天),足够阻断慢速扫描器的长期试探。

以下是一份基础的 jail.local 配置:

# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 管理员公网IP
bantime = 604800
findtime = 60
maxretry = 1

[wordpress-attack]
enabled = true
filter = wordpress-scan
port = http,https
logpath = /var/log/nginx/example.com.log
maxretry = 1
findtime = 60
bantime = 604800

ignoreip 中需要添加管理员的公网 IP,防止误封。

自定义过滤器:覆盖 WordPress 攻击面

过滤器定义在 /etc/fail2ban/filter.d/wordpress-scan.conf 中,用于从访问日志中匹配恶意请求。以下是一份覆盖常见攻击路径的 failregex:

# /etc/fail2ban/filter.d/wordpress-scan.conf
[Definition]
failregex = ^<HOST> [^”]* “(?:GET|POST) /+xmlrpc\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-admin/install\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-admin/upgrade\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-admin/setup-config\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “(?:GET|POST) /wp-login\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “(?:GET|POST|OPTIONS) /wp-json/wp/v2/users[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /+[^”]*\?[^”]*author=\d+[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /+[^”]*\?[^”]*author=[a-zA-Z][^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-includes/wlwmanifest\.xml[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /readme\.html[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /license\.txt[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-config\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /\.env[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /\.git[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /wp-content/[^”]*\.(?:sql|bak|log|tar\.gz|zip)[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /phpmyadmin[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /adminer[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /phpinfo\.php[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /backup\.zip[^”]*” (?:200|301|302|403|404|500|401|444)
^<HOST> [^”]* “GET /docker-compose\.yml[^”]*” (?:200|301|302|403|404|500|401|444)
ignoreregex =

状态码列表中包含了 200、301、302、403、404、500、401 和 444,覆盖了 Nginx 可能返回的各种响应。/+ 用于匹配路径中可能出现的多个斜杠(如 //xmlrpc.php),author= 规则分别匹配数字型和非数字型作者 ID 枚举。

配置完成后,通过 fail2ban-regex 验证过滤器能否正确匹配日志中的攻击请求。

自定义 Action:封禁 + 中断连接

默认的 iptables-multiport action 只会添加防火墙规则,不会中断已建立的 TCP 连接。攻击者如果复用了 Keep-Alive 长连接,即使 IP 被加入黑名单,已建立的连接仍可继续发送请求,导致封禁后日志仍在刷屏。

关键点:防火墙规则只对“新”连接生效,无法踢掉已经建立的连接。必须用 ss -K 强制中断。

解决方法是创建自定义 action,在封禁的同时强制中断该 IP 的所有 TCP 连接。

新建 /etc/fail2ban/action.d/iptables-killconn.conf:

# /etc/fail2ban/action.d/iptables-killconn.conf
[Definition]
actionban = iptables -I INPUT 1 -s <ip> -j REJECT –reject-with icmp-port-unreachable
ss -K dst <ip>
actionunban = iptables -D INPUT -s <ip> -j REJECT –reject-with icmp-port-unreachable

ss -K dst <ip> 会强制终止来自该 IP 的所有 TCP 连接。-I INPUT 1 确保规则插入到 INPUT 链最前面,优先于任何允许规则。

在 jail.local 中引用该 action:

# 在对应的 jail 中指定
[wordpress-attack]
action = iptables-killconn

Nginx 层前置拦截

在 Nginx 层对敏感路径直接返回 404 或 444,可以在请求到达后端之前就完成拦截,减少 PHP 资源消耗。

在站点配置的 server 块中添加:

# nginx server 块配置
# 静默丢弃 xmlrpc.php 和 install.php
location = /xmlrpc.php {
return 444;
access_log off;
log_not_found off;
}

location = /wp-admin/install.php {
return 444;
access_log off;
log_not_found off;
}

# 普通屏蔽
location = /wp-config.php {
return 404;
}

return 444 是 Nginx 特有状态码,直接关闭连接而不返回任何响应。配合 access_log off 和 log_not_found off,这些请求不会在访问日志和错误日志中留下痕迹。

对于 REST API 用户枚举和作者枚举等路径,使用 if 指令在 location 匹配之前拦截:

# 在 server 块中,location 之前
if ($request_uri ~* “^/wp-json/wp/v2/users”) {
return 404;
}

if ($args ~* “\bauthor=\d+”) {
return 404;
}

Nginx 层拦截与 Fail2ban 形成双重防护:Nginx 在应用层静默拒绝,Fail2ban 在网络层封禁 IP。

常用管理命令

命令 说明
sudo fail2ban-client status 查看所有启用的 jail
sudo fail2ban-client status <jail名> 查看特定 jail 的封禁详情
sudo fail2ban-client set <jail名> banip <IP> 手动封禁一个 IP
sudo fail2ban-client set <jail名> unbanip <IP> 手动解封一个 IP
sudo fail2ban-client reload 重载配置文件
sudo iptables -L INPUT -n | grep <IP> 确认防火墙规则是否存在

效果与验证

配置完成后,访问日志中针对敏感路径的请求会显著减少。Fail2ban 日志(/var/log/fail2ban.log)中会显示类似以下的封禁记录:

2026-08-12 21:06:42,370 fail2ban.actions [626585]: WARNING [wordpress-attack] 103.69.220.254 already banned

already banned 表示该 IP 已被封禁且防火墙规则存在,后续请求虽然仍可能出现在访问日志中(因为 Nginx 在防火墙之前记录日志),但实际已被网络层拦截。

通过 iptables -L INPUT -n | grep <IP> 可以确认防火墙规则确实存在。若发现某个 IP 被封禁后仍持续产生大量日志,说明该 IP 在封禁前已建立了 TCP 连接,ss -K 可以强制中断这些连接。

验证要点:封禁后若仍有个别日志,是因为 Nginx 在防火墙之前写入日志,属正常现象。大量持续日志则需用 ss -K 处理。

总结

这套方案的核心逻辑是:不给扫描器任何试探的机会。maxretry=1 确保任何对敏感路径的访问都会被立即封禁;自定义 action 在封禁的同时中断已有连接;Nginx 层前置拦截进一步降低后端负载。三者结合,形成了一套“露头就秒”的自动化防御体系。

对于不对外开放注册和登录的个人站点,这套配置几乎没有误报风险,能够在最小化运维成本的前提下,有效抵御自动化扫描攻击。

核心启示:安全配置没有终点,只有持续观察与调整。这套方案在实际站点运行后,日志中的恶意扫描量显著下降,服务器负载也更加稳定。

评论

  1. r'w'q'r
    2 月前
    2026-8-20 15:45:13

    1321231231231231测试

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇