0x01 前言

近期很幸运地获取一些Google cloud的资源,然后我用这些资源测试我一直以来防CC攻击的软件组合架构。经测试发现效果挺理想的,而且打出了及其罕见的巨大流量。

这次测试涉及三款软件,nginx与通过lua语言编写的HTTPguard组合使用,fail2ban则独立运行。

0x02 应用环境

该架构主要实现抵御CC攻击的功能,选用nginx作为高性能反向代理器,在后端应用服务器前实现防护功能,该架构无法抵御DDOS攻击与任何渗透攻击。

目前市面上的CDN服务绝大部分都不包含抗CC攻击的服务或者属于付费服务,当防护量急剧增大的情况下及其容易导致费用暴涨,而该架构也适用于服务器处于CDN之后的主机,也支持部署在负载均衡器之后,但需要确认各个节点是否能正常传递“x-forwarded-for”这个HTTP header,如果无法正常传递,则不适用此防护架构。

以下为简单的架构示意图:

0x03 防护逻辑

CC攻击一般有以下几个特点:

  • 同一IP的连接数突然增大或持续保持在高位;
  • 不断访问同一URL或加入随机查询字段;
  • 不支持301、302或js跳转;
  • 不支持传递cookie。

因为前端服务无法分辨URL的有效性,所以无法通过URL判断访客的善恶与否。所以针对第二条,在这里不做判断,也不针对其进行拦截。

在攻击开始之初,iptables内并没有拦截规则,所有IP均可通过iptables到达nginx。当攻击开始后,大量请求到达nginx,HTTP请求次数急剧增大,当某个IP的请求书超过HttpGuard的阈值后,HttpGuard将对其启用设定的防护动作。如果该IP无法通过HttpGuard的测试,将通知nginx返还指定的HTTP Code。

而Fail2Ban一直在背后实时读取nginx访问日志,通过设定的正则匹配规则,寻找包含指定HTTP Code的nginx访问日志条目,而后提取该条目中的访客IP地址,通过预设的命令将其加入iptables中。
当已被加入iptables的IP再次访问服务器时,将被iptables阻挡,因为请求未能到达nginx,所以nginx的负载也会逐渐下降,从而使服务保持正常。

HttpGuard的防护测试正是利用CC攻击的特点,可以选用或同时启用以下测试功能:

  • 验证码验证;
  • 302跳转验证;
  • Js跳转验证;
  • Cookie传递验证。

0x04 验证环境

 在这里我使用以下版本的软件:

  • 操作系统:Centos 7.5
  • Nginx版本:15.1
  • HttpGuard:https://github.com/centos-bz/HttpGuard
  • Fail2Ban版本:9.7

以下为简单的测试架构图:

使用10台Google Cloud主机模拟攻击肉鸡,攻击Google CDN节点,而该Google CDN节点反向代理Nginx-Main这台主机。

攻击肉鸡使用Apache ab命令模拟高并发环境;使用GoldenEye模拟CC攻击;Google CDN节点并未做任何防护,仅仅用于流量分发;Nginx-Main运行本防御架构与PHP-FPM并运行着一个vhost,该vhost托管着一个phpinfo页面用于测试。

0x05 测试过程

测试过程分为两部分,首先关闭HttpGuard,使用10台攻击偷鸡分别使用Apache ab与GoldenEye对Google CDN进行攻击,;而后启用HttpGuard,再次实施攻击。

测试环境配置完成后,在预测试时确认功能正常并能实现预设的效果。Nginx访问日志截图如下:

当访客正常访问时,Nginx返还了200,而随着访问量持续上涨,使得HttpGuard动作,Nginx返还了403。而两种状态转换之间的500是因为Nginx中断了与proxy server的通讯导致的。

在关闭HttpGuard的状态下,Google CDN转发到Nginx-Main服务器中的流量高达2Gbps,而此时的连接数在3万左右:

同时PHP-FPM也使得Nginx-Main服务器的CPU处于满负荷状态:

经过多次压力测试,在其中一次测试中把Nginx-Main中的Nginx-Main线程打崩溃,但随后主进程自动重建,服务经历短暂不可用后恢复正常:

完成对照测试后,重新配置HttpGuard并启用,再重新进行测试,经检测,连接数保持在3万左右:

在启用HttpGuard后,因为Nginx自动对恶意请求返还403,请求并未到达后端的PHP-FPM,所以CPU负载较未启用HttpGuard前有很大改善:

与此同时,因为无需返还HTML数据,所以带宽的使用也维持在150Mbps以下。但因Nginx需要处理的内容较未启用HttpGuard前要多,所以相应地,Nginx的CPU使用率也有所上升。

0x06 生产环境

在部署前有一些参数与内容需要提前确认。首先是业务正常运行期间,单IP的连接数与请求数均值;确定需要对恶意访客返还的HTTP Code;确定检测到多少特定的HTTP Code后需要该访客的IP使用iptables屏蔽等。

因为需要用到第三方模块,所以建议自行编译Nginx并选择最新版本的Nginx。而对操作系统则无特定要求,但建议使用Centos 7.5。

0x06.1 HttpGuard配置建议

默认情况下,所有防御模块都应该被关闭,因为这有可能为真实访客带来影响,而任何跳转模块都不利于SEO。如果线上服务处于24小时有人值守的状态,建议在紧急情况下按需手动启用防御模块。

当然,也可以保持在启动状态,但不建议将跳转模块设为启动状态,而仅仅将限制请求模块(limitReqModules)启动即可,例如:

limitReqModules = { state = "On" , maxReqs = 200 , amongTime = 60, urlProtect = baseDir.."url-protect/limit.txt" },

以上配置的意义为:在60秒内,访客访问的URL如果匹配“limit.txt”中的正则且请求次数超过200次,则调用拦截动作(blockAction)。

建议根据实际情况调整“amongTime”与“maxReqs”的数值,“maxReqs”的值设置在业务繁忙期请求数均值的80%会比较好。

需要注意的是,限制请求模块(limitReqModules)所统计的并不是连接数,而是请求数,且不区分vhost域名与端口,将对真个Nginx服务生效。

与此同时,建议配置自动开启防御模块功能(autoEnable),实现自动启动防御模块。例如:

监控80端口,自动开启cookieModules:

autoEnable = { state = "On", protectPort = "80", interval = 10, normalTimes = 3,exceedTimes = 2,maxConnection = 50, ssCommand = "/usr/sbin/ss" ,enableModule = "cookieModules"},

以上配置的意义为:调用ss命令每10秒检查一次80端口的连接数,如果连续2次超过50,则启动cookieModules,如果连续3次小于50,则关闭cookieModules。

同样的,可以采用同样的配置方法监听其他端口并启用防御模块:

autoEnable = { state = "On", protectPort = "80", interval = 10, normalTimes = 3,exceedTimes = 2,maxConnection = 100, ssCommand = "/usr/sbin/ss" ,enableModule = "redirectModules"},
autoEnable = { state = "On", protectPort = "443", interval = 10, normalTimes = 3,exceedTimes = 2,maxConnection = 50, ssCommand = "/usr/sbin/ss" ,enableModule = "cookieModules"},
autoEnable = { state = "On", protectPort = "443", interval = 10, normalTimes = 3,exceedTimes = 2,maxConnection = 100, ssCommand = "/usr/sbin/ss" ,enableModule = "redirectModules"},

这样配置对搜索引擎比较友好,不需要一直启用跳转。另外需要注意的是,自动开启防御模块功能(autoEnable)并不监控Nginx的请求数,而是通过ss命令监控系统的连接数。

如果服务器处于负载均衡器或CDN之后,必须要启用realIpFromHeader功能,以便从“x-forwarded-for”HTTP header中获取真实的用户IP,否则将导致负载均衡器或CDN节点的IP被拦截甚至屏蔽。

如果blockAction设置为forbidden,那么在多次验证失败后,Nginx将返还444给访客,强制访客断开连接。如果需要修改为其他HTTP Code,则需要修改“guard.lua”文件:

# 打开文件
[root@pub-ngx HttpGuard]# vim guard.lua

# 修改561行中的内容
--拒绝访问动作
function Guard:forbiddenAction()
  ngx.header.content_type = "text/html"
  ngx.exit(444)
end

0x06.2 fail2ban配置

Fail2ban是通过正则匹配文本日志中的条目,从匹配的条目中找出指定的IP地址,而后调用相应的命令,对IP进行相应的动作。

在这里主要用iptables对IP实施丢包的动作。

首先需要准备好正则匹配的语句,以下是Nginx的默认日志格式:

10.1.2.73 - - [21/Jul/2018:21:59:56 +0800] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"

在这里我需要匹配所有HTTP响应码为444的日志条目,并且提取出第一个字段的IP地址。简单的正则如下:

<HOST> - - \[.*\] \".*\" (403|444) \d*\s\".*

IP地址的匹配正则用“<HOST>”代替,fail2ban将自动提取该IP地址,为了防止日志的其他地方也出现444等字符,务必将规则写得更加详细,防止匹配错误的日志条目。

而后进入fail2ban的程序目录并复制一份配置文件用于配置软件:

# 进入相应的目录
[root@ngx-main ~]# cd /etc/fail2ban/

# 复制配置文件
[root@ngx-main fail2ban]# cp jail.conf jail.local

因为后缀名为“local”的配置文件的优先级高于后缀名为“conf”的配置文件,所以无需将“jail.conf”删除。

而后再“jail.local”文件的最后追加以下内容:

[ngx-log-40x]
enabled = true
port = http,https
filter = ngx-40x
logpath = /var/log/nginx/access.log
action   = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
           %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
maxretry = 10
findtime = 1800
bantime = 86400
ignoreip = 127.0.0.1 10.1.1.122

请务必将本地环回与本机上的其他IP地址加入“ignoreip”中,这些IP一旦被封禁,很有可能造成网络或系统异常。

上面的配置信息的意义是:使用筛选器(filter)中的正则实时匹配指定的日志文件(logpath),如果在指定的时间内(findtime)被匹配超过阈值(maxretry),则调用封禁动作(action)中的“ban”动作,直至封禁时间超过设定值(bantime),随后使用封禁动作(action)中的“unban”动作解封。

完成jail文件的修改后,在“filter.d”目录下建立名为“ngx-40x.conf”的筛选器配置文件:

# 新建文件
[root@ngx-main fail2ban]# vim filter.d/ngx-40x.conf

# 填入内容
[Definition]
failregex = <HOST> - - \[.*\] \".*\" (403|444) \d*\s\".*
ignoreregex =

将匹配的正则语法填写在“failregex”字段中即可,完成后使用以下命令测试该筛选器:

[root@ngx-main 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: 1476 total
|-  #) [# of hits] regular expression
|   1) [1476] <HOST> - - \[.*\] \".*\" (403|444) \d*\s\".*
`-

Ignoreregex: 0 total

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

Lines: 2248 lines, 0 ignored, 1476 matched, 772 missed
[processed in 0.46 sec]

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

默认情况下,fail2ban中的iptables动作为“reject”,该动作会主动通知访客请求被拒绝,该动作会给网络上行造成压力,所以建议改成“drop”。

打开以下文件并修改:

# 打开文件
[root@ngx-main fail2ban]# vim action.d/iptables-common.conf

# 修改blocktype
blocktype = DROP

完成后将fail2ban设为开机启动并立即启动。

0x07 结语

Nginx、HttpGuard与fail2ban三者配合使用对CC攻击的防御效果更好,HttpGuard可以提供多种高效的检测方式,而fail2ban则提供实时监控日志文件的功能,不但可以调用iptables,还可以调用email等实现提前预警。

无论何种跳转验证都会对搜索引擎的爬虫造成影响,绝大部分爬虫不会跟随跳转,尤其是js跳转,同时也不会传递cookie。如果不是出于攻击的状态下,请不要启用相关的模块,以免对收录造成影响。

0x08 文件下载