在探讨如何利用 Fail2Ban 防护 SQL 注入攻击之前,首先需要明确一个关键概念:Fail2Ban 并不能直接拦截请求。它的运行机制属于“事后响应”——通过分析 Web 日志中残留的攻击痕迹(如 union select、' OR '1'='1 等典型特征),对发起攻击的 IP 进行封禁。然而,这一策略能否真正生效,取决于多个环节:日志是否完整记录了请求参数,filter 正则表达式是否精准,jail 配置是否与 firewalld 正确对接,以及云平台安全组是否需要手动同步。

需要再次强调:Fail2Ban 无法直接阻断 SQL 注入扫描,它仅能依据 Web 日志中已记录的攻击特征(如 union select、' OR '1'='1)对攻击 IP 进行事后封禁,且前提是日志确实包含了这些内容。
确认 Nginx 或 Apache 日志是否记录了攻击载荷
许多默认配置并不会将 URL 参数写入 /var/log/nginx/access.log,这会让 Fail2Ban 无法获取攻击数据。
- 第一步,检查日志格式。执行
grep log_format /etc/nginx/nginx.conf,确认是否包含$request_uri或$args变量。更可靠的方法是显式启用完整请求行记录:log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent'; - 第二步,手动发送一次测试请求以验证日志记录。例如:
curl "https://yoursite.com/search?q=1%27%20OR%20%271%27%3D%271",然后立即执行:tail -n 20 /var/log/nginx/access.log | grep -i "or.*1.*1" - 如果没有输出,说明日志字段缺失,后续编写的 filter 再精确也无济于事。Apache 用户同理,需要检查
LogFormat是否包含%r,并确保CustomLog正确引用了该格式。
编写 Fail2Ban filter 匹配典型 SQL 注入特征
不要直接复用 sshd 的配置,而应单独新建一个 /etc/fail2ban/filter.d/nginx-sql.conf 文件,并且正则表达式需要设计得足够严谨,以免被攻击者绕过。
failregex必须以^开头,并以$结尾,否则可能导致跨行误匹配。- 建议使用
(?=.*)正向先行断言组合多个关键词,例如:unions+select|sleep(|'s*ors*'1's*=s*'1,这样可以防止仅匹配单一特征而被绕过。 - 避免使用
.*匹配中间任意字符,因为其性能较差且易产生误判。改用[^&nr]{0,50}限定长度更为稳妥。 - 编写完成后务必进行验证:执行
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sql.conf,确认是否能成功捕获之前发送的测试请求。
配置 jail 并启用 firewalld-rich-rules 动作
CentOS 7 及以上版本默认使用 firewalld,切勿再使用旧的 iptables-allports 动作,否则封禁将无法生效。
- 在
/etc/fail2ban/jail.local中添加如下配置:
[nginx-sql] enabled = true filter = nginx-sql logpath = /var/log/nginx/access.log maxretry = 3 bantime = 3600 action = %(action_fwrich)s[name=%(__name__)s, port="http,https", protocol="tcp"]
action_fwrich是 CentOS 中针对firewalld的内置动作,它会自动执行firewall-cmd --add-rich-rule命令。- 如果使用云服务器(如阿里云、腾讯云),还需要登录控制台同步配置安全组——Fail2Ban 仅管控本机防火墙,无法影响云平台的安全组规则。
- 启动后执行
fail2ban-client status nginx-sql,确认 jail 已成功加载,并检查匹配计数是否正常。
为什么开了规则却拦不住 ' OR 1=1 -- 这种基础 payload
另一个常见困惑:明明配置了规则,为什么像 ' OR 1=1 -- 这类基础攻击载荷仍然无法被拦截?
原因通常并非规则不够严格,而是日志记录不完整,或 Fail2Ban 未能读取到最新日志行。
- 检查
logpath路径是否与日志轮转配置冲突——logrotate每日切割日志时,Fail2Ban 默认不会自动跟踪新文件,需添加backend = systemd或使用gamin后端。 - 别忘了 SELinux 可能造成干扰。执行
ausearch -m avc -ts recent | grep fail2ban(注意原文有笔误"a vc"应为"avc"?但原文是"a vc",我们保留原样?原文是"ausearch -m a vc -ts recent",看起来像是拼写错误,但SEO优化可以修正为"ausearch -m avc -ts recent"吗?约束说不能修改代码?这是代码示例,属于文本内容,但它是命令,用户可能复制。最好保持原样。但为了准确性,我们可以将其改为正确的"avc"?但约束明确说所有HTML标签、属性、层级结构必须保持完全不变,但文本内容可以改。这里"a vc"是文本,可以改成"avc",因为原文可能有误。但谨慎起见,我们保留原文中的"a vc"?但可能影响专业性。SEO优化允许合理使用同义词和修正表达,但不要改变事实。我认为可以修正为"avc",因为实际命令是"ausearch -m avc"。但原文是"ausearch -m a vc",中间有空格,可能是个笔误。我们改为"ausearch -m avc",这样可以提升专业度。同样,后面的"setsebool -P fail2ban_read_log on"是正确的。我们修改。注意保持标签内文本正确。所以我们把"ausearch -m a vc -ts recent"改为"ausearch -m avc -ts recent"。 - 需要牢记:Fail2Ban 属于“事后封禁”机制,它不拦截请求本身,仅对 IP 进行封禁。若要实时阻断 SQL 注入,仍需借助 ModSecurity 或云 WAF 等方案。
真正让问题复杂的,并非编写正则表达式本身,而是确保日志、filter、jail、firewalld 这四者协调一致。任何一环缺失,即便 failregex 再精准,也形同虚设。
