最近查看 Nginx 的访问日志,发现有不少恶意扫描漏洞的记录,同时也有不少的伪造蜘蛛的爬虫导致消耗了不少服务器资源,由此准备做一些缓解措施。其实 Nginx 自带的 limit_req_zonelimit_conn_zone 参数已经可以限制连接频率和并发数量,但对于一些“聪明”的恶意爬虫来说,显然是不够的。

fail2ban-nginx

下面介绍一下如何用 Fail2Ban 工具匹配分析 Nginx 的日志文件,禁止特定 IP,以达到减缓恶意扫描或者是应用层 DDoS 攻击的目的。

关于 DDoS 攻击的一些知识,可以看看我先前写的这篇文章:聊聊 DDoS 攻击那些事

这里先简单说说 fail2ban 这款工具,其实 Linux 用户可能都不会陌生,fail2ban 是一款入侵检测系统框架工具,通过扫描日志文件并用正则匹配分析,然后通过更新防火墙规则来禁止某些有恶意迹象的 IP(密码失败过多,寻求漏洞利用等)来提高服务器安全性。开箱即用的 fail2Ban 自带了用于各种服务(Apache、Courier、SSH 等)的过滤器,最常用的场景是扫描 SSH 连接日志,禁止失败次数过多的 IP,防止 SSH 暴力破解。

安装 Fail2Ban

演示环境为 Debian 11,使用 root 用户,大多数 Linux 发行版软件包仓库中都有 Fail2ban,直接执行安装命令即可:

apt install -y fail2ban

安装成功后配置文件都位于 /etc/fail2ban/ 目录中,其中需要关注的有以下三个文件或文件夹:

  • /etc/fail2ban/filter.d/:恶意行为的过滤规则,其中预设于了 SSH、Nginx、Apache 的监控规则
  • /etc/fail2ban/action.d/:发现恶意 IP 后采取的操作,其中预设了许多常用操作,其中预设了 iptables、firewalld、sendmail 等操作
  • /etc/fail2ban/jail.conf:即小黑屋,Jail 由 Filter 和 Action 组成,作用是通过组合前面的配置来设置 fail2ban 的工作行为

常用命令:

1. 查看指定 Jail 规则下被封禁的ip情况:

fail2ban-client status <JAIL>

2. 添加/解除指定 IP 的封禁:

fail2ban-client set <JAIL> banip/unbanip <IP>

3. 添加/解除指定 IP 的忽略:

fail2ban-client set <JAIL> addignoreip/delignoreip <IP>

4. 测试匹配规则是否正确:

fail2ban-regex <日志文件> <过滤规则>

5. 查看所有命令:

fail2ban-client -h

6. 查看日志:

tail /var/log/fail2ban.log

配置 Fail2Ban

添加过滤规则

首先设置过滤规则,在 /etc/fail2ban/filter.d/ 目前下新建一个 .conf 文件,名字自取,比如我新建的是 nginx-zatp-com.conf,然后进行设置:

[Definition]
failregex = 
ignoreregex =

其中:

failregex:表示过滤规则的正则表达式;
ignoreregex:表示忽略规则的正则表达式,可以设置为 .*(webp|svg|jpg|png) 忽略对图片文件的请求,防止图片文件过多误伤;

而这里要实现我们想要的效果,也有两个选择,配合开篇提到的 Nginx 流控产生的日志文件(error.log)进行匹配过滤或者直接对 Nginx 的访问日志文件(access.log)进行匹配过滤。原理都一样,fail2ban 预置了很多常见服务的日志文件匹配模板,在 /etc/fail2ban/filter.d/ 目录下可以找到。如果你修改了日志格式,那么需要根据你的日志文件格式改写相应的表达式

这里我用 Nginx 的 limit_req_zone 流控模块做一个示例,下面是一条超过限制产生的错误信息:

2021/10/13 01:02:39 [error] 14174#0: *41792 limiting requests, excess: 5.920 by zone "request", client: x.x.x.x, server: www.zatp.com, request: "HEAD /?feed=rss HTTP/2.0", host: "www.zatp.com"

参考自带的 /etc/fail2ban/filter.d/nginx-limit-req.conf 模板,可以写成下面的表达式:

[Definition]
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone ".*", client: <HOST>,
ignoreregex = 

failregex 也可以直接简写为:

failregex = limiting requests, excess:.* by zone.*client: <HOST>

其中 <HOST> 是必须包含的,fail2ban 通过这个来获取 IP 地址,测试自定义的规则是否生效:

fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-zatp-com.conf

如果成功匹配会返回匹配到的信息:

Results
=======

Failregex: 4 total
|-  #) [# of hits] regular expression
|   1) [4] ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone ".*", client: <HOST>,
`-

Ignoreregex: 0 total

关于一些正则表达式的基本语法,网上有很多不错的教程,有兴趣可以去了解一下。

配置 Jail 规则

Fail2ban 读取配置文件的顺序如下,.local 会覆盖 .conf

  • /etc/fail2ban/jail.conf
  • /etc/fail2ban/jail.d/*.conf
  • /etc/fail2ban/jail.local
  • /etc/fail2ban/jail.d/*.local

对于大多数用户来收,最简单的方式是直接在 /etc/fail2ban/ 目录下将 jail.conf 复制为 jail.local 文件:

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

然后在 jail.local 中修改即可,其中可配置项非常丰富,并且都有详细的介绍,你可以根据自己的需要进行修改。

个人推荐设置一下白名单,方便你自己的本地 IP 测试使用,直接在 jail.local 中找到 ignoreip 参数,取消注释,然后添加你希望设置白名单的 IP:

ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 1.1.1.1

可以直接在 jail.local 的末尾添加你自定义的规则,我这里选择了新建一个 /etc/fail2ban/jail.d/custom.conf 文件,便于后期的查看和维护:

[nginx-zatp-com]
enabled = true
port = http,https
filter = nginx-zatp-com
logpath = /var/log/nginx/access.log
bantime = 12h
findtime = 60
maxretry = 5

简单说明一下参数:

  • port:封禁的端口,这里设置的是一般做站的 80 和 443 端口
  • filter:过滤规则,使用前面自定义的 nginx-zatp-com
  • logpath:监控的日志文件路径
  • bantime:封禁时间,单位为秒,-1 表示永久
  • findtime:查找时间段,单位为秒
  • maxretry:允许的最大失败次数,结合前面的 findtime,比如这里配置的是每 60 秒内触发 5 次规则,那么就封禁掉该 IP 12 小时

然后重启 fail2ban 即可生效:

service fail2ban restart
fail2ban-client status nginx-zatp-com

此时你可以在另外一台服务器上创建一个 shell 脚本,执行 curl 请求循环来检测是否生效:

#!/bin/bash
for ((i=1;i<=50;i++)); do
curl -H "Fail2ban test" https://your-domian/test > /dev/null 2>&1
done
echo "done"

注意:如果服务器上启用了 ufw 防火墙,会出现 Fail2ban 无法阻止 IP 的情况。这是由于 iptables 的顺序问题,关于 ufw 的具体使用可以参考我前面写的文章:修改SSH端口使用密钥登录并配置防火墙

要解决这个问题,你可以将 fail2ban 默认的 iptables 替换为 ufw,首先确认 /etc/fail2ban/action.d/ 目录下有 ufw.conf 这个文件。然后修改 /etc/fail2ban/jail.local 中的 banaction 参数:

banaction = ufw
banaction_allports = ufw

最后,你也可以添加一个 cron 任务定期清理 Nginx 和 fail2ban 的日志文件,减少硬盘空间占用,这里就不再介绍了。