0x01 前言

最近遭受大量扫描与各种渗透攻击,因为我的服务器配置了modsecurity保护nginx,所以目前还没有造成影响。

虽然通过waf可以阻隔恶意访问,但这个并不是长久的解决办法。为此,我决定配置fail2ban拦截恶意IP,以提高安全性。

0x02 准备

fail2ban简单来说就是通过不断读取设定的日志文件,并通过正则校验每条日志是否符合规则。

一旦符合,则提取日志中的IP地址与时间戳,然后写入到fail2ban的数据库中。写库的同时进行计数,如果该IP在设定的时间间隔内被匹配的次数超过阈值,则调用iptables,将其的请求reject。

为此,再继续之前需要准备以下内容:

  1. iptables
  2. 日志路径
  3. 拦截内容

0x02.1

在绝大部分的云服务商中,因为没有安全组的概念,所以iptables都是启用的,这时候就可以跳过此项。

但在诸如腾讯云、阿里云或亚马逊云等大型云服务商中,可以通过安全组控制公网连入的IP与端口。这种情况下,iptables一般是禁用的,这时候就需要检查相关服务:

[root@web-dev ~]# systemctl status iptables.service 
● iptables.service - IPv4 firewall with iptables
   Loaded: loaded (/usr/lib/systemd/system/iptables.service; disable; vendor preset: disabled)
   Active: inactive (dead) since Mon 2018-04-02 16:54:05 CST; 1 weeks 0 days ago
  Process: 32056 ExecStop=/usr/libexec/iptables/iptables.init stop (code=exited, status=0/SUCCESS)
 Main PID: 29668 (code=exited, status=0/SUCCESS)

从上方可以看到服务处于停止inactive (dead)的状态,通过以下命令可以看到并没有规则处于启用状态:

[root@web-dev ~]# iptables -L -vn
Chain INPUT (policy ACCEPT 209 packets, 14257 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 115 packets, 11222 bytes)
 pkts bytes target     prot opt in     out     source               destination

这时候就需要要启用并删除2条默认的规则:

#开机启动iptables
[root@web-dev ~]# systemctl enable iptables.service 
Created symlink from /etc/systemd/system/basic.target.wants/iptables.service to /usr/lib/systemd/system/iptables.service.

#记录启动iptables
[root@web-dev ~]# systemctl start iptables.service 

#删除INPUT链中的默认reject规则
[root@web-dev ~]# iptables -D INPUT 5

#删除FORWARD链中的默认reject规则
[root@web-dev ~]# iptables -D FORWARD 1

#保存修改内容
[root@web-dev ~]# service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[  OK  ]

#重启iptables
[root@web-dev ~]# systemctl restart iptables.service

0x02.2 日志路径

fail2ban支持使用通配符匹配日志路径,如:

/usr/local/html/ngx.hk/logs/ngx_access.log
/usr/local/html/enginx.cn/logs/ngx_access.log
/usr/local/html/enginx.org/logs/ngx_access.log
/usr/local/html/enginx.uk/logs/ngx_access.log

如果需要匹配上面的日志,则需要写成以下形式即可:

/usr/local/html/*/logs/ngx_access.log

当然,也可以写完成的路径。

0x02.3 拦截内容

因为是针对HTTP服务,所以只需要匹配HTTP响应码即可。

正常情况下只会返回2种响应码:200与404;如果后端无响应的话,会返还50x;但被modsecurity拦截的请求会返回403,一些需要授权的页面则会返还400。

所以在我的环境里主要针对产生403与400这2种响应码的IP进行拦截。

0x03 安装配置

安装非常简单,建议使用epel的源进行安装:

[root@web-dev ~]# yum install fail2ban -y

安装完成后先不要启动,首先进入软件的目录:

[root@web-dev ~]# cd /etc/fail2ban/

0x03.1 jail

先复制一份jail文件:

[root@web-dev fail2ban]# cp jail.conf jail.local

默认的jail.conf不要修改,以备日后参考。

在fail2ban中,会先读取 .conf 后缀名的文件,然后读取 .local 后缀名的文件;而且后读取的配置可以覆盖先读取的配置。

然后在jail.local最后添加以下内容:

[ngx-log]
enabled = true
port = http,https
filter = ngx-40x
logpath = /var/log/nginx/access.log
action = iptables-multiport[name=ModsecWaf, port="http,https", protocol=tcp]
         %(action_mwl)s
maxretry = 10
findtime = 1800
bantime = 3600
ignoreip = 127.0.0.1 103.15.217.210 193.112.151.220

以上每行内容的大致意义如下:

  • [ngx-log]:定义jail名称
  • enabled:是否启用该jail,默认的所有规则都没有该项,需要手动添加
  • port:指定封禁的端口,默认为0:65535,也就是所有端口,但可以在jail中设定
  • filter:指定过滤器名称
  • logpath:日志路径
  • action:达到阈值后的动作
  • maxretry:阈值
  • findtime:时间间隔
  • bantime:封禁时长
  • ignoreip:忽略的IP

在这里有几点要注意的:

  • logpath与action可以有多行,如action中的设定:
    • 调用iptables-multiport封禁目标IP访问的多个端口
    • 调用sendmail发送告警邮件
  • findtime不是检查日志的时间间隔,日志的检查是实时的。因为fail2ban自带数据库,所以可以在设定的时间内统计匹配次数
  • ignoreip添加后端服务器的IP或CDN的IP

0x03.2 action

自带的action完全可以满足我们的需求,如果需要手动编写,也非常简单。我们来看看上面jail.local中[ngx-log]这个jail的action:

iptables-multiport[name=ModsecWaf, port="http,https", protocol=tcp]

在action.d目录中我们可以找到相应的内容,把注释去掉之后的内容如下:

[root@web-dev fail2ban]# cat action.d/iptables-multiport.conf 
[INCLUDES]

before = iptables-common.conf

[Definition]
actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>

actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
             <iptables> -F f2b-<name>
             <iptables> -X f2b-<name>

actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>

actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>

[Init]

从上面的内容可以发现该action定义了5种不同的动作,其中actionstart与actionstop为自动调用时所用的,另外3种则可以通过fail2ban-client调用。

为了方便自动化调用,上面的各种action都包含用“<>”符号包裹的变量,调用的时候只需要指定即可。

0x03.3 filter

过滤器也是很重要的一部分,主要提供匹配服务,使用正则寻找IP。在jail中指定需要调用的过滤器文件名即可完成调用工作:

filter = ngx-40x

如上面的配置,调用了filter.d目录下ngx-40x.conf文件中的过滤器:

[Definition]
failregex = <HOST> - - \[.*\] \".*(403|400).*\"
ignoreregex =

我需要匹配的日志如下:

#403
123.126.113.184 - - [09/Apr/2018:22:51:11 +0800] "GET /category/%e8%99%9a%e6%8b%9f%e5%8c%96/kvm%e8%99%9a%e6%8b%9f%e5%8c%96/page/2 HTTP/1.0" ngx.hk 403 162 "-" "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)" "123.126.113.184" - - - "-" - > 0.000

#400
123.126.113.184 - - [09/Apr/2018:22:51:06 +0800] "GET /robots.txt HTTP/1.1" ngx.hk 400 264 "-" "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)" "-" - - - "-" - > 0.000

题外话:我屏蔽了sogou等一系列的爬虫,因为这些爬虫不但没有给我带来流量,而且每次爬行都像CC攻击一样,都不知道他们是怎样开发的

定义过滤模块有两个需要注意的点:

  1. 无需匹配完整日志
  2. 日志中需要有标准的日期格式字段

其实fail2ban只关心1个要素:日志中的IP地址。

所以可以在生成日志的时候可以将正常与不正常的日志分开存储,然后匹配规则这样写:

failregex = <HOST> - -

但我的并不是这样,而是所有日志都在同一个文件中,所以需要写成这样:

failregex = <HOST> - - \[.*\] \".*(403|400).*\"

“<>”符号为固定的变量名称,用于匹配IP地址,往后需要匹配HTTP响应码,这里匹配403与400两种情况,至此已经完成匹配工作,fail2ban并不关心后面的内容,所以可以忽略。

如果有多条正则,可以换行添加。

建立完过滤文件后可以使用以下命令检查匹配情况:

[root@web-dev fail2ban]# fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/ngx-40x.conf 

Running tests
=============

Use   failregex filter file : ngx-40x, basedir: /etc/fail2ban
Use         log file : /var/log/nginx/access.log
Use         encoding : UTF-8


Results
=======

Failregex: 1143 total
|-  #) [# of hits] regular expression
|   1) [1143] <HOST> - - \[.*\] \".*(403|400).*\"
`-

Ignoreregex: 0 total

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

Lines: 42889 lines, 0 ignored, 1143 matched, 41746 missed
[processed in 14.49 sec]

Missed line(s): too many to print.  Use --print-all-missed to print all 41746 lines

在Results中的Failregex字段显示出有多少条日志符合要求;在Date template hits字段中可以看到有多少条日志被默认的时间正则匹配上。

而Lines字段则显示出该日志文件的行数等情况。

fail2ban对日志中的时间格式有要求,如果匹配不上则无法计算findtime,在这里需要考虑时间格式不标准的情况。

因此,需要手动编写时间的匹配规则,如以下日志:

c4.hk 119.139.198.243 - "GET /admin HTTP/2.0" 403 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" 152316870061.619255 - /var/log/modsecurity//20180408/20180408-1425/20180408-142500-152316870061.619255 0 1269.000000 md5:d6859189629af76f03b4f75bc0e49b15

比较明显的时间格式如下:

20180408-1425

那么时间的匹配规则应该是这样的:

%Y%m%d-%H%M%S

不过在写入过滤器的时候需要多加一个“%”符号,用于转义:

[root@web-dev fail2ban]# cat filter.d/ngx-enginx-net.conf 
[Definition]
failregex = .* <HOST> - .*(403|400)
ignoreregex =

[Init]
datepattern = %%Y%%m%%d-%%H%%M%%S

运行fail2ban-regex检查结果:

[root@web-dev fail2ban]# fail2ban-regex 'c4.hk 119.139.198.243 - "GET /admin HTTP/2.0" 403 0 - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" 152316870061.619255 - /var/log/modsecurity//20180408/20180408-1425/20180408-142500-152316870061.619255 0 1269.000000 md5:d6859189629af76f03b4f75bc0e49b15' /etc/fail2ban/filter.d/ngx-enginx-net.conf  -v

Running tests
=============

Use   failregex filter file : ngx-enginx-net, basedir: /etc/fail2ban
Use      datepattern : YearMonthDay-24hourMinuteSecond
Use      single line : c4.hk 119.139.198.243 - "GET /admin HTTP/2.0" 403 ...


Results
=======

Failregex: 1 total
|-  #) [# of hits] regular expression
|   1) [1] .* <HOST> - .*(403|400)
|      119.139.198.243  Sun Apr 08 14:02:05 2018
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] YearMonthDay-24hourMinuteSecond
`-

Lines: 1 lines, 0 ignored, 1 matched, 0 missed
[processed in 0.00 sec]

从上面的结果可以看出,自定义的时间匹配规则已经生效。

0x04 运行

完成陪之后即可启动fail2ban:

#设定开机启动
[root@web-dev fail2ban]# systemctl enable fail2ban.service 
Created symlink from /etc/systemd/system/multi-user.target.wants/fail2ban.service to /usr/lib/systemd/system/fail2ban.service.

#立即启动
[root@web-dev fail2ban]# systemctl start fail2ban.service 

#检查状态
[root@web-dev fail2ban]# systemctl status fail2ban.service 
● fail2ban.service - Fail2Ban Service
   Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2018-04-09 23:47:36 CST; 6s ago
     Docs: man:fail2ban(1)
  Process: 19718 ExecStart=/usr/bin/fail2ban-client -x start (code=exited, status=0/SUCCESS)
 Main PID: 19721 (fail2ban-server)
   CGroup: /system.slice/fail2ban.service
           └─19721 /usr/bin/python2 -s /usr/bin/fail2ban-server -s /var/run/fail2ban/fail2ban.sock -p /var/run/fail2ban/fail2ban.pid -x -b

Apr 09 23:47:36 web-dev systemd[1]: Starting Fail2Ban Service...
Apr 09 23:47:36 web-dev fail2ban-client[19718]: 2018-04-09 23:47:36,662 fail2ban.server         [19719]: INFO    Starting Fail2ban v0.9.7
Apr 09 23:47:36 web-dev fail2ban-client[19718]: 2018-04-09 23:47:36,662 fail2ban.server         [19719]: INFO    Starting in daemon mode
Apr 09 23:47:36 web-dev systemd[1]: Started Fail2Ban Service.

通过fail2ban-client可以进行一些简单的操作,如:封禁IP、解封IP与检查状态等:

#检查所有jail的状态
[root@hk1 ~]# fail2ban-client status
Status
|- Number of jail:	1
`- Jail list:	ngx-log

#检查特定jail的状态
[root@hk1 ~]# fail2ban-client status ngx-log
Status for the jail: ngx-log
|- Filter
|  |- Currently failed:	5
|  |- Total failed:	240
|  `- File list:	/var/log/nginx/access.log
`- Actions
   |- Currently banned:	2
   |- Total banned:	5
   `- Banned IP list:	58.250.143.116 58.243.74.153

#解封IP
[root@hk1 ~]# fail2ban-client set ngx-log unbanip 127.0.0.1

#封禁IP
[root@hk1 ~]# fail2ban-client set ngx-log banip 127.0.0.1

其他更多操作请参考使用手册。

0x05 结语

如果需要使用邮件告警,请修改jail.local文件中的destemail字段为你的电子邮箱地址,然后在jail的action中添加:

%(action_mwl)s

fail2ban是建立在日志基础上的封禁软件,只有在日志足够详细的情况下才能发挥应有的作用。

在过去一个月的使用后发现,fail2ban可以很好地提高安全防护水平。