RHEL 9 配置使用Fail2Ban

RHEL 9 配置使用Fail2Ban

本文介绍怎样在RHEL9系统中使用Fail2Ban结合防火墙nftables来阻止来自网络的恶意访问,以保护计算机免受暴力破解。

Fail2Ban 能干什么

Fail2ban是由Python语言开发的程序,它通过监视服务器的各种系统日志,并对日志里的各种行为进行实时判断,以确定该行为是否为恶意攻击,并会统计该攻击的频次,然后根据用户配置的各种规则执行对应的屏蔽操作。

举例说明,一台Linux服务器正遭到远程暴力破解SSH登录,每一次用户名密码登录错误都会在ssh的登录日志里留下一条错误记录,Fail2ban程序从该日志文件里捕捉到一定频次的这些错误后,就可以及时采取应对措施,比如把这个IP加入防火墙的禁止访问(屏蔽)列表,从而阻断该IP的继续尝试。

我尽量把Fail2Ban的应用情景写的通俗些,但是限于我的知识面,可能不全。 Fail2Ban能处理如下各种问题:

  • 阻断来自某IP地址的恶意多次尝试破解SSH登录密码;
  • 阻断来自某IP地址的连续尝试访问各种路径下的用户敏感文件;
  • 阻断来自某IP地址的爬虫程序尝试连续下载某个路径下的所有媒体文件,比如domain/video/1.mp4, domain/video/2.mp4,…, domain/video/999.mp4;

针对这些行为,Fail2Ban 可以自动采取各种应对措施,比如通过防火墙屏蔽该IP以禁止它连接一段时间。当然也可以采取其他动作,比如发送邮件给管理员,或者记录此类行为到日志等。

Fail2Ban 是如何工作的

下面具体展开它的一些技术细节。

Fail2Ban的名字就包含了2个最重要的部分: fail(失败) 和 ban(屏蔽)。另外还涉及到jails(监控),filters(过滤),actions(行动) 这几个词,后续会陆续介绍。

Fail2Ban的处理流程可以分为如下几个部分。

1. 监控系统日志文件

  • Fail2Ban的输入是来自用户指定的某个或某些日志文件(例如 Nginx 的 access.log 或 SSH 的认证日志 auth.log)。 它会持续监控这些文件, 类似 tail -F xx.log的效果。
  • 这些日志文件的位置是通过后面用户设置的每个 jail(监控规则)中的 logpath 参数定义的。

2. 从日志文件中尝试捕捉fail(失败)

  • 用户在每个启用的监控 jail 里都可以配置一个或多个过滤规则(filter)。
  • 每个filter里可以使用一个或多个正则表达式(failregex)来定义恶意或不合法行为的模式。
  • 如果日志文件中有内容与 某个failregex 匹配,则该日志条目会被标记为一次fail“失败”。

举例说明:
如果用户在某个jail里定义了针对Nginx的access.log日志的如下一个failregex规则:

1
failregex = <HOST> -.*GET.*(\.env|phpinfo\.php|PHPINFO\.php).*HTTP.* 4\d{2}

则下面这一条错误访问日志就会被匹配到:

1
52.81.78.71 - - [18/Jan/2025:16:09:20 -0700] "GET /.env HTTP/1.1" 404 230 "-" "Go-http-client/1.1" "-"

此时,Fail2Ban就会把它标记为1个fail,并记录它的时间和访问IP。这就好比一个人犯了一次错被记录在案,注意此时还只是记录在案,并不会立即采取后续行动(处罚)。

3. 统计一定时间内失败次数来决定是否采取行动

Fail2Ban会记录来自同一个IP的各种错误,并和用户事先设好的处罚屏蔽标准进行比较,以决定是否应该屏蔽该IP。每个监控 jail指定的处罚屏蔽标准(filters) 都有以下参数:

  • maxretry:触发行动的最大失败次数。
  • findtime:统计失败次数的时间窗口(单位为秒)。
  • 如果同一个 IP 在 findtime 时间窗口内触发的失败次数达到或超过了 maxretry,Fail2Ban 会采取响应的的动作,比如禁止(ban)。

举例说明,如果某个网页需要进行basic auth的登录认证,如果用户输入错误的用户名和密码,则会在Nginx里留下一条代码401的日志。而 如果用户针对这个401代码,设置了一个只允许在10分钟内,最多有3次401错误的代码的规则,那么在这个用户10分钟内连续输入3次错误信息后,Fail2Ban就会采取措施,把这个IP放入jail,也就是会采取下一步的屏蔽错误。打个比方,就好比一个小偷在1个月内连续3次被人抓住,而当地法律规定了如果一个人1个月内连续偷窃达到或超过3次就要被投进监狱,那么这个小偷就要被扔进监狱。

4. 采取行动以屏蔽该IP

如果前一步里一定时间内多个fail触发Fail2Ban将此IP标记为应该对该IP采取行动(比如放入jail)后,Fail2Ban 就会执行用户设置的相关操作(actions),一般是把此IP放入防火墙的封禁列表并屏蔽一定时长,比如1小时。1小时后才会恢复该IP的访问。

5. 总结

最后总结一下上面出现的这些词的关系:fail(失败) , ban(屏蔽),jails(监控),filters(过滤规则)和actions (动作)。

  • 用户可以设定多个jails,每个jail 实现1个监控目标。比如创建1个以跟踪SSH日志用于监控SSH暴力登录,再创建1个以跟踪Nginx的访问日志用于监控Nginx网络爬虫等。另外,也可以根据实际需用对同一个日志设置多个jails,比如针对Nginx的访问日志,创建1个jail用来监控各种 .env/.php等后门探测,再创建1个用来监控各种暴力破解网页上的basic auth的用户名密码暴力破解等。
  • 每个监控目标jail里必须指定一个日志文件路径logpath,一个fail捕捉过滤规则filter, 以及一组触发该监控惩罚规则的条件参数:maxretry, findtime等
  • 每个fail捕捉过滤规则filter文件里里可以包含一条或多条正则表达式, 用来判断该条记录是否应该被标记为fail。比如下面的这个filter就使用了两条规则来捕捉所有的404 或403 代码对应的请求:
    1
    2
    3
    4
    5
    6
    7
    [Definition]

    # Match 404 errors
    failregex = ^<HOST> - \S+ \[.*\] ".*" 404

    # Match 403 errors
    failregex = ^<HOST> - \S+ \[.*\] ".*" 403

RHEL9上实际操作

1. 安装:

安装比较简单,一行命令解决:

1
sudo dnf install fail2ban

安装完毕后,它会在系统里添加fail2ban这个系统服务,因此,可以使用各种常规服务操作命令来启动,停止,重启此服务或查询它的运行状态:

1
sudo systemctl start/stop/restart/status fail2ban

2. 配置和使用

根据前面所述,针对1个监控目标就要设定一个jail,而每个jail里必须指定一个日志文件路径logpath,一个fail捕捉过滤规则filter, 以及一组触发该监控惩罚规则的条件参数:maxretry, findtime。 我们需要把这些item放在两个新建的文件里:

  • 一个包含一条或多条正则表达式的filter 配置文件 *.conf
  • 一个包含日志文件路径和惩罚触发规则的配置文件 *.local.

具体可以参见下面的示意图。系统里可以有多个jail,每个jail都需要创建两个文件:
Fail2Ban系统示意图

应用实例 1: 阻断Nginx 后门探测 404

近期发现Nginx的访问日志 access.log里多了一些试图探测各种.env文件的访问:

1
2
3
4
5
6
7
8
9
78.153.140.224 - - [17/Jan/2025:15:05:34 -0700] "GET /temp/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/14.0.1" "-"
78.153.140.224 - - [17/Jan/2025:15:05:37 -0700] "GET /logs/.env HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Windows NT 6.0; Win128; x128) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.18 Safari/537.36 OPR/37.0.2178.54" "-"
78.153.140.224 - - [17/Jan/2025:15:05:41 -0700] "GET /setup/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.8.1.4) Gecko/20070622 Firefox/2.0.0.4" "-"
78.153.140.224 - - [17/Jan/2025:15:05:42 -0700] "GET /httpd/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) QupZilla/1.9.0 Safari/538.1" "-"
78.153.140.224 - - [17/Jan/2025:15:05:43 -0700] "GET /bin/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/58.0" "-"
78.153.140.224 - - [17/Jan/2025:15:05:45 -0700] "GET /twilio/.env HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 OPR/42.0.2393.94" "-"
78.153.140.224 - - [17/Jan/2025:15:05:46 -0700] "GET /legal/.env HTTP/1.1" 404 555 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.3)" "-"
78.153.140.224 - - [17/Jan/2025:15:05:50 -0700] "GET /themes/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Galeon/2.0.6 (Ubuntu 2.0.6-2)" "-"
78.153.140.224 - - [17/Jan/2025:15:05:50 -0700] "GET /wwwroot/.env HTTP/1.1" 404 153 "-" "Mozilla/5.0 (iPad; CPU OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.0 Mobile/14G60 Safari/602.1" "-"

同一个IP交替使用各种客户端来查找各种可能路径下的.env文件,因为都不存在,所以访问日志里都是返回404。这种行为很可疑,所以建议加入IP屏蔽列表进行屏蔽。

1. 创建1个filter过滤规则文件

因此针对这些请求,我们可以在/etc/fail2ban/filter.d/ 目录下创建1个包含两条简单正则表达式的filter配置文件,起名为nginx-sensitive.conf

1
sudo nano /etc/fail2ban/filter.d/nginx-sensitive.conf # 文件命名为nginx-sensitive.conf

里面写入如下内容:

1
2
3
[Definition]
failregex = <HOST> -.*(?:GET|POST).*(\.env|phpinfo\.php|PHPINFO\.php|\.php).*HTTP.* 4\d{2}
ignoreregex = "GET .*\.css.*" "GET .*\.js.*" "GET .*\.png.*" "GET .*\.gif.*" "GET .*\.jpeg.*"

第一条就是用来匹配各种试图使用GET或POST请求来访问/*/.env, /*/.phpinfo.php, /*/.PHPINFO.php 而导致服务器返回4xx错误代码的访问记录,找到一条就标记为1个fail。第二行的ignoreregex 的意图是让Fail2Ban对忽略静态资源的访问日志的处理。

2. 验证创建的filter过滤规则文件

写完这些规则后,在重启fail2ban服务使它生效前,可以对这个filter里的两个正则表达式进行一些模拟测试来验证是否正确。

首先创建1个模拟测试的日志文件 “~/test.log”,包含如下5条访问记录,可以看出4条应该被标记为fail,第4条应该被忽略(不处理):

1
2
3
4
5
193.189.100.204 - - [17/Jan/2025:21:42:25 -0700] "POST /test.php HTTP/1.1" 404 230 "-" "Mozilla/5.0"
192.168.1.1 - - [17/Jan/2025:21:42:30 -0700] "GET /wp-comments-post.php HTTP/1.1" 403 0 "-" "-"
192.168.1.2 - - [17/Jan/2025:21:42:31 -0700] "POST /wp-content/.env HTTP/1.1" 404 0 "-" "-"
192.168.1.3 - - [17/Jan/2025:21:42:32 -0700] "GET /static/style.css HTTP/1.1" 404 0 "-" "-"
192.168.1.4 - - [17/Jan/2025:21:42:33 -0700] "GET /admin/login.php HTTP/1.1" 404 0 "-" "-"

然后执行如下测试命令:
sudo fail2ban-regex ~/test.log /etc/fail2ban/filter.d/nginx-sensitive.conf

运行后返回测试结果,我在里面加了解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Running tests
=============

Use failregex filter file : nginx-sensitive, basedir: /etc/fail2ban
Use log file : ~/test.log
Use encoding : UTF-8


Results
=======

Failregex: 4 total # 总共捕捉到4条标记为fail的记录
|- #) [# of hits] regular expression
| 1) [4] <HOST> -.*(?:GET|POST).* (\.env|phpinfo\.php|PHPINFO\.php|\.php).*HTTP.* 4\d{2}
#使用第1条正则表达式捕捉到4条fail记录
`-

Ignoreregex: 0 total
# 没有任何行被Ignoreregex 标记为fail。
# 这个有些难理解,ignoreregex 的作用是排除某些日志行,避免它们被计入“失败”记录。
# 日志中Ignoreregex只有一条行符合 ignoreregex 的条件(.css 文件),但它已经被跳过,
# 不在统计范围内,因此不会被标记为fail,表明这个Ignoreregex工作正常

Date template hits:
|- [# of hits] date format
| [5] Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour:Minute:Second(?:\.Microseconds)?(?: Zone offset)?
`-

Lines: 5 lines, 0 ignored, 4 matched, 1 missed
[processed in 0.08 sec]

|- Missed line(s): # 跳过不处理的记录行
| 192.168.1.3 - - [17/Jan/2025:21:42:32 -0700] "GET /static/style.css HTTP/1.1" 404 0 "-" "-"
`-

3. 配置jail监控设置

上面已经创建了一个filter的配置文件.conf, 接下来需要新建1个jail的配置文件来使用这个filter匹配规则。

/etc/fail2ban/jail.d目录下新建1个名为nginx-sensitive.local的配置文件,并写入如下内容:

1
2
3
4
5
6
7
8
[nginx-sensitive]
enabled = true
filter = nginx-sensitive
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 600
bantime = 3600
action = nftables-multiport[name=nginx-sensitive, port="80,443", protocol=tcp]

为了方便解释,我在每一行后添加了注释,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[nginx-sensitive] 
#定义了一个名为nginx-sensitive的jail。每个jail是Fail2Ban的一个规则集,
#用于监控特定的日志文件并根据定义的规则处理匹配的日志条目。

enabled = true #启用了该 jail。只有设置为true的jail才会被Fail2Ban激活并开始工作。

filter = nginx-sensitive #指定使用名为 nginx-sensitive 的过滤器(filter)。
#这个过滤器对应 /etc/fail2ban/filter.d/nginx-sensitive.conf 文件,该文件定义了要匹配的日志模式(正则表达式)。


logpath = /var/log/nginx/access.log #指定要监控的日志文件路径。
maxretry = 3 #允许的最大失败次数。
# 如果同一个 IP 在 findtime 时间内触发的失败次数达到或超过 3次,
# Fail2Ban就会立即触发动作(如屏蔽该IP)。

findtime = 600 #失败次数的统计时间窗口,单位为秒。这里是10分钟。
# 如果一个 IP 在 10 分钟内触发失败次数达到或超过 maxretry,则会被屏蔽。

bantime = 3600 #屏蔽时间,单位为秒。
# 在这里设置为 3600 秒(1 小时)。被屏蔽的 IP 在 1 小时后会自动解除屏蔽。

action = nftables-multiport[name=nginx-sensitive, port="80,443", protocol=tcp]
# fail2ban应该对该IP采取的动作,这里是使用 nftables 防火墙规则屏蔽 IP。
# name=nginx-sensitive:给动作定义一个名字,方便区分规则。
# port="80,443":屏蔽 IP 对 HTTP 和 HTTPS 服务(端口 80 和 443)的访问。
# protocol=tcp:只屏蔽 TCP 协议流量(HTTP/HTTPS 使用的是 TCP 协议)。

注意一点,RHEL9 已经默认使用nftables, 不再使用iptables了,所以对应的防火墙规则要使用nftables的规则。而且上面的atction里只屏蔽该IP访问80和443 端口,如果想完全禁止该IP访问所有端口,则可以修改为:

1
action = nftables-allports[name=nginx-auth]

配置完毕后,重启fail2ban 服务使它生效:

sudo systemctl restart fail2ban

4. 检查jail 配置以及运行日志

运行后,可以使用如下命令来检查当前的监控情况:

1
2
3
4
5
6
sudo fail2ban-client status

Status
|- Number of jail: 2
`- Jail list: nginx-basic-auth, nginx-sensitive.
# 当前运行了两个jail

还可以直接指定jail的名字来看它的具体内容:

1
2
3
4
5
6
7
8
9
10
11
sudo fail2ban-client status nginx-sensitive
Status for the jail: nginx-sensitive
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/nginx/access.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list: #目前还没有应该被屏蔽的IP

从我的电脑浏览器里输入https://zangchuantao.com/test/1.php来访问一个不存在的php文件,以触发这个规则,试了3次后,被屏蔽了,此时我浏览器里显示:

1
2
3
4
5
6
7
This site can’t be reached
zangchuantao.com refused to connect.
Try:

Checking the connection
Checking the proxy and the firewall
ERR_CONNECTION_REFUSED

再次查看该jail的状态,可以发现它已经把我的IP屏蔽了:

1
2
3
4
5
6
7
8
9
10
sudo fail2ban-client status nginx-sensitive
Status for the jail: nginx-sensitive
|- Filter
| |- Currently failed: 0
| |- Total failed: 3
| `- File list: /var/log/nginx/access.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 24.8.72.142

5. 手动解除屏蔽某个IP

1
sudo fail2ban-client unban <IP>

6. 关于fail2ban的日志

它的日志默认保存为 /var/log/fail2ban.log,可以通过tail —f/F 来实时查看 (推荐-F),例如刚才我的测试操作在日志里就对应如下记录:

1
2
3
4
5
6
7
8
9
10
11
12
sudo tail -f /var/log/fail2ban.log

2025-01-19 06:48:04,105 fail2ban.actions [1083892]: INFO banTime: 3600
2025-01-19 06:48:04,105 fail2ban.filter [1083892]: INFO encoding: UTF-8
2025-01-19 06:48:04,105 fail2ban.filter [1083892]: INFO Added logfile: '/var/log/nginx/access.log' (pos = 145355, hash = 381a097c0c4fa35a87df6d27c5e9a9037d6c4fa5)
2025-01-19 06:48:04,109 fail2ban.jail [1083892]: INFO Jail 'nginx-basic-auth' started
2025-01-19 06:48:04,112 fail2ban.jail [1083892]: INFO Jail 'nginx-sensitive' started
2025-01-19 07:30:35,247 fail2ban.filter [1083892]: INFO [nginx-sensitive] Found 24.8.72.142 - 2025-01-19 07:30:35
2025-01-19 07:30:37,342 fail2ban.filter [1083892]: INFO [nginx-sensitive] Found 24.8.72.142 - 2025-01-19 07:30:37
2025-01-19 07:30:38,135 fail2ban.filter [1083892]: INFO [nginx-sensitive] Found 24.8.72.142 - 2025-01-19 07:30:38
2025-01-19 07:30:38,421 fail2ban.actions [1083892]: NOTICE [nginx-sensitive] Ban 24.8.72.142
2025-01-19 07:34:53,385 fail2ban.actions [1083892]: NOTICE [nginx-sensitive] Unban 24.8.72.142

实例2: 阻止爬虫连续下载媒体文件

Fail2Ban 完全可以用于阻止爬虫连续下载你的服务器上的媒体文件。假如服务器的 /files/video/路径下有大量*.mp4 文件,文件名使用了数字编号从1到10000,为了阻止爬虫连续下载以爬取你的媒体文件,可以为它设置一个jail,具体做法如下。

1. 创建filter过滤规则文件

创建一个新的 Fail2Ban 过滤规则文件,比如 /etc/fail2ban/filter.d/nginx-media-download.conf:

1
sudo nano /etc/fail2ban/filter.d/nginx-media-download.conf

在文件中添加以下正则规则:

1
2
3
4
5
6
7
8
9
[Definition]

# 匹配访问 /files/video/*.mp4 的日志条目
failregex = ^<HOST> -.*GET /files/video/.*\.mp4 HTTP/.* 200


# 可选:匹配其他媒体文件类型
# failregex = ^<HOST> -.*GET /files/video/.*\.(mp4|mkv|avi) HTTP/.* 200

2. 创建自定义 Jail

1
sudo nano /etc/fail2ban/jail.d/nginx-media-download.local

配置 Jail 添加以下内容:

1
2
3
4
5
6
7
8
9
[nginx-media-download]
enabled = true
filter = nginx-media-download
logpath = /var/log/nginx/access.log
maxretry = 50 #允许的最大访问次数,超过后触发屏蔽(这里设置为 50 次)。
findtime = 300 #统计时间窗口(300 秒,即 5 分钟内)。
bantime = 3600 #屏蔽时间(3600 秒,即 1 小时)。
action = nftables-allports[name=nginx-media-download] # 屏蔽整个 IP 的所有端口流量。

3. 重启 Fail2Ban 服务

重启 Fail2Ban 服务以应用新规则就可以阻断爬虫连续下载了:

1
sudo systemctl restart fail2ban

RHEL 9 配置使用Fail2Ban
https://pub.zangchuantao.com/20250119/RHEL 9 配置使用Fail2Ban.html
作者
Chuantao
发布于
2025年1月19日
许可协议