0x01 前言

在4月10日这天,我的博客遭遇了建站以来首次攻击,包含DDOS与CC攻击。虽然在调整防御策的时候经历过几次的短暂服务中断,但是整体来看还是挺成功的。

在遭受攻击前的过去2周里,我将站点的架构做了大调整,已经将数据库、PHP后端与前端进行分离。前端的nginx仅仅起到一个反向代理器的作用。

不知道攻击者的目的是什么,我的服务器仅运行着一个小博客,今天我针对这次事件做个详细的记录。

0x02 第一波攻击

  • 12:45:32 服务器监控告警,上行带宽超过限制:
Subject:
PROBLEM: enginx1 upload speed severe overload

Message:
Trigger: enginx1 upload speed severe overload 
Trigger date: 2018.04.10 12:45:31 
Trigger status: PROBLEM 
Trigger age: 0m 
Trigger severity: Disaster 
Trigger URL: 
Item values: 
Outgoing network traffic on eth0 (enginx1:net.if.out[eth0]): 21.78 Mbps 
Original event ID: 13754613
  • 随即通过SSH登入服务器,发现大量处于TIME_WAIT状态的连接:
#当时没有保留截图,这里仅展示当时所用的命令
[root@web ~]# netstat -anp | grep TIME_WAIT | wc -l
  • 通过iftop发现众多IP访问HTTP端口:

 

当时没有保留截图,这里仅展示当时所用的命令

  • 确认问题后随即通过iptables封禁80端口传入的请求:
[root@web ~]# iptables -I INPUT -p tcp --dport 80 -j DROP
  • 12:51:33 使用iptables封禁80端口后,流量随即下降,以下是告警消除通知:
Subject:
OK: enginx1 upload speed severe overload

Message:
Trigger: enginx1 upload speed severe overload 
Trigger recovery date: 2018.04.10 12:51:31 
Trigger status: OK 
Trigger age: 6m 
Trigger severity: Disaster 
Trigger URL: 
Item values: 
Outgoing network traffic on eth0 (enginx1:net.if.out[eth0]): 2.19 Mbps 
Original event ID: 13754613
  • 上行带宽恢复的同时,我检查了用于日志分析的ELK集群,发现攻击开始于12:44:59,每秒并发在5k~8k之间,并且还在持续:

  • 开始调整内核参数、启用防CC手段、调整fail2ban策略
  • 13:15:00 首波攻击结束,越30分钟的时间内,nginx共计收到约3,030,617次请求,记录到来自世界各地的1600多个IP地址:

此次攻击所跑的流量并不大,nginx的日志如下:

222.235.176.229 - - [10/Apr/2018:12:45:30 +0800] "GET / HTTP/1.1" ngx.hk 301 178 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; SV1)" "-" - - - "-" - > 0.000

可以看到,肉鸡通过HTTP协议访问我博客的域名,但我博客是强制使用HTTPS的,所以nginx返还了301给肉鸡。想不到肉鸡居然跟随了301进行跳转,生成了以下日志:

222.235.176.229 - - [10/Apr/2018:12:45:03 +0800] "\x00" - 400 166 "-" "-" "-" - - - "-" - > 0.000

所以每次攻击都会产生2次请求,这对我服务器的CPU与网络都造成极大的影响。

0x03 第二波攻击

我还以为攻击就这样结束了,然而并不是,在我正准备洗澡的时候,第二波攻击来了。

  • 20:20:33 服务器监控告警,上行带宽超限:
Subject:
PROBLEM: enginx1 upload speed severe overload

Message:
Trigger: enginx1 upload speed severe overload 
Trigger date: 2018.04.10 20:20:31 
Trigger status: PROBLEM 
Trigger age: 0m 
Trigger severity: Disaster 
Trigger URL: 
Item values: 
Outgoing network traffic on eth0 (enginx1:net.if.out[eth0]): 39.52 Mbps 
Original event ID: 13766221

其实这一波攻击不应该成功的,因为我在回到家后多次对fail2ban的策略进行调整,导致上午ban掉的IP全被释放了,因此第二波攻击才有机可乘。

  • 20:28:00 受限于fail2ban的性能,在经过8分钟后,已经快把所有的恶意IP都拉到黑名单了:

可能是因为攻击者的肉鸡都已经打完,连接数在缓慢地下降,而我的站点已知处于正常运行的状态。这一波的攻击从8点20分左右一直持续到11号的0点前后才结束。

0x04 防御

0x04.1 网站架构

我博客的架构在3月底至4月初已经完成改造,详细的描述我会在后期撰写文章说明,这里先描述下大体情况。

用户访问到的服务器仅用于前端,仅运行着nginx用于反向代理。我站点的mariadb、apache与PHP已经迁走,甚至zabbix proxy server也已经完成迁移。因此,用户访问到的页面都是缓存在本地的静态页面,只会对带宽造成压力,而后端依然坚挺。

我的后端运行着mariadb、Apache与PHP,且为双节点热备模式:一台运行在我家里的服务器中,另一台则运行在某云服务商的VPS中。

其中,运行在我家中的节点为主节点,在心跳包正常的情况下,所有需要后端请求的流量都会发送至这里。为了安全起见,一切需要登入后台的操作仅能在我家中操作,通过公网访问敏感目录一律返还403。

另外还解决了双节点的文件、session与数字证书同步等问题。

最后,所有静态文件都使用CDN进行分发,减轻前端服务器的压力。

这个架构较以前的一箩筐模式在抗攻击能力上有质的飞越:

  • 当遭受攻击时,无需担心后端崩溃,可在后端建立WAF,对特定的客户IP进行拦截;
  • 当遭受攻击时,通过前端服务器可以更便捷地拦截流量;
  • 当遭受攻击时,可以随时调整DNS记录,通过分流肉鸡流量进行防御;
  • 静态页面能有效提升防御能力
  • … …

更多内容与实现方式计划在新文章中详细阐述。

0x04.2 HttpGuard

HttpGuard原本是一个开源项目,但是作者已经转为收费服务了。但是开源版本的源代码依旧可以在GitHub中下载并安装使用,简单的安装与配置可以参考以下文章:

原本我并没有启用HttpGuard,在遭受攻击后重新调整并启用防御功能。

首先启用被动防御,限制请求模块:

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

每分钟请求超过100次的访客将会被要求输入验证码:

blockAction = "captcha",

因为我站点中的静态文件使用了CDN,所以在正常浏览情况下,同时打开多个页面也不会出发该规则。

因为肉鸡中的自动化程序不会输入验证码,在超过10次失败后将被拉黑24小时:

captchaToIptables = { state = "on", maxReqs = 10 , amongTime = 10},
blockTime = 86400,
whiteTime = 1800,

但是这个设定并不会调用iptables,而是返还HTTP响应码,默认情况下是返还444,但为了方便fail2ban统计,我将返还的响应码改为403:

#打开并修改以下文件中的第561行
vim guard.lua

#拒绝访问动作
function Guard:forbiddenAction()
                ngx.header.content_type = "text/html"
                ngx.exit(403)

但是我并未就此罢休,我还设置了自动启用的防御模块:

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

当在2次检查中,10秒内的连接数超过150,则启用重定向模块。一般情况下,CC攻击一般不会跟随跳转,所以这个重定向模块会有一定的用处,但对SEO则是一个灾难,所以该模块仅在必要时启用。

0x04.3 iptables & netstat & iftop

这三款软件在遭受攻击时非常有用,首先是iptables,在设置防御或者拦截规则时,首先采用丢包的模式而不是拒绝,因为丢包不需要给客户端返还内容,可以节省带宽,也可以起到一个迷惑攻击者的作用。

另外,在手动添加拦截规则时建议将规则插入到第一行,而不是最后一行,这样可以确保规则一定会生效。但是这种临时的规则并不需要保存,所以需要删除规则时,仅需要reload即可:

iptables -I INPUT -p tcp --dport 80 -j DROP
  • -I:将规则插入到INPUT链的首行
  • -p:匹配TCP协议
  • –dport:首访端口为HTTP的80端口
  • -j:将数据包丢弃

而netstat则可以检查当前的网络连接情况,例如:

[root@web ~]# netstat -anp | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1644/nginx: master  
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      1644/nginx: master  
tcp        0      0 10.1.1.14:80            10.1.2.73:12135         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53422         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.2.73:10819         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.2.73:13655         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53430         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.2.73:11905         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53428         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.2.73:14674         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53438         ESTABLISHED 1645/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.2.73:12352         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53352         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53418         ESTABLISHED 1646/nginx: worker  
tcp        0      0 10.1.1.14:80            10.1.1.22:53420         ESTABLISHED 1646/nginx: worker

当需要统计某个状态的连接数时,可以使用以下命令:

[root@web ~]# netstat -anp | grep nginx | grep ESTABLISHED | wc -l
13

在遭受攻击时,服务器可能变得非常缓慢,所以命令越简单越好。

最后是iftop,一款实时展示网络连接情况的软件,使用iftop命令打开展示界面后,分别按下以下按键:

  • t:在同一行内展示上下行情况,节省空间
  • T:展示总流量
  • p:展示端口

最后的界面应该类似这样的:

但是这里面的端口太混乱了,我只需要HTTP与HTTPS的访问情况,这时候按下小写字母“L”,输入:

HTTP|HTTPS

后回车即可查看HTTP与HTTPS协议的连接情况。

先使用软件分析实际情况,然后进行分析,最后进行策略调整。

0x04.4 fail2ban

fail2ban的基本安装配置可以参考以下文章:

在第一波攻击的时候我就调整了策略,收紧对nginx日志中响应码为403的访客的控制,一旦超过限值则调用iptables将其请求丢弃:

[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

一般情况下不会触发403响应码,如果客户在30分钟以内触发超过10次403状态码,则拉黑24小时。

但通过检查ELK中的分析发现,肉鸡触发最多的并不是403,而是301与200。为此,针对我的实际情况,制定了以下规则

[ngx-log-30x]
enabled = true
port = http,https
filter = ngx-30x
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 = 50
findtime = 1800
bantime = 86400
ignoreip = 127.0.0.1

[ngx-log-20x]
enabled = true
port = http,https
filter = ngx-20x
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 = 200
findtime = 1800
bantime = 86400
ignoreip = 127.0.0.1

0x04.5 nginx

但是在第二波攻击时我又发现访问次数上限太高,fail2ban的压力太大。因此,我利用nginx自带的limit_req模块进行限流:

#定义zone
limit_req_zone                      $binary_remote_addr zone=main:50m rate=140r/s;

#调用zone并定义令牌桶
limit_req                  zone=main burst=150 nodelay;

#超限将返还403
limit_req_status           403;

配置完nignx后可以发现可疑的IP确实被fail2ban中检测403的jail规则拦截了。

但我并没有删除fail2ban中检测301与200的规则,这部分还需要进一步测试,以便分析性能与可靠性。

0x04.6 内核

针对TIME_WAIT的问题,我选择通过调整内核的方式进行优化:

#修改超时时间为30秒,某些情况可以进一步降低该值
net.ipv4.tcp_fin_timeout = 30

#将keepalive的发送频率降低为20分钟一次
net.ipv4.tcp_keepalive_time = 1200

#开启SYN Cookies,当SYN队列溢出时启用Cookies
net.ipv4.tcp_syncookies = 1

#开启TIME-WAIT sockets重用功能
net.ipv4.tcp_tw_reuse = 1

#开启TIME-WAIT sockets快速回收功能
net.ipv4.tcp_tw_recycle = 1

#加大SYN队列
net.ipv4.tcp_max_syn_backlog = 8192

#最大TIME_WAIT保持数,超过将全部清除
net.ipv4.tcp_max_tw_buckets = 5000

调整后发现异常的连接数大幅度减少。

0x05 数据

上下行峰值为44Mbps左右:

CPU负载情况如下图:

因为前端服务器有缓存与防护,所以后端服务器一切正常:

流量也正常:

数据库也未见异常:

从中午12点至第二天的0点这中间的12小时里,共计接受了10,178,740次访问、流量为1.058GB,共记录到3,004个IP地址:

流量与请求分布情况如下图:

肉鸡主要来自亚洲,大部分来自我国:

CDN也受到波及,跑了一波流量,但随后触发了抗CC规则,IP被拉黑了:

攻击CDN的肉鸡大多来自亚洲,以我国的IP为主:

0x06 结语

我博客的架构与防护软件的功劳不可小觑,另一部分因素是攻击者的肉鸡不够了,不过从大体看来,这次攻击的肉鸡质量不错。

我比对了两波攻击的IP,发现大部分都是一样的。肉鸡居然可以用那么久,由此可见,此次肉鸡的质量非常好。

只是不明白为什么要选择我这个小博客作为目标 🙁