事情是这样发生的,由于商业竞争热烈,不免有些公司会出现恶意竞争的现象,其实CC攻击算是最简单实施的一种DDoS类别攻击吧,在之前我所在的公司就遭受了一波商业竞争者发起的CC攻击。具体细节就不多说了,这种事情其实见怪不怪,来分享一下我的解决方案。
其实会有很多人说,像WAF什么的设备都可以有效抵挡住CC攻击,包括开源WAF也具备这个功能,有必要自己去写一个脚本来防护吗?
我的想法其实很简单,对于不知道怎么搭建开源WAF,或者资金投入并不大的公司而言,能有一个简单防御解决方案,这是我写这篇文章想实现的一个意义,也算是对这个职位的一些回馈吧,当然其实写防御脚本基本是每一位安全人员都会而且很熟练的一个技能,在这里我就先抛砖引玉,分享一下关于防CC脚本的设计思路。
首先要给读者普及一下什么是CC攻击?
CC(ChallengeCoHapsar,挑战黑洞)攻击是DDoS攻击的一种类型,使用代理服务器向受害服务器发送大量貌似合法的请求。CC根据其工具命名,攻击者使用代理机制,利用众多广泛可用的免费代理服务器发动DDoS攻击。许多免费代理服务器支持匿名模式,这使追踪变得非常困难。
CC攻击的原理是什么?
CC攻击的原理就是攻击者控制某些主机不停地发大量数据包给对方服务器造成服务器资源耗尽,一直到宕机崩溃。CC主要是用来攻击页面的,每个人都有这样的体验:当一个网页访问的人数特别多的时候,打开网页就慢了,CC就是模拟多个用户(多少线程就是多少用户)不停地进行访问那些需要大量数据操作(就是需要大量CPU时间)的页面,造成服务器资源的浪费,CPU长时间处于100%,永远都有处理不完的连接直至就网络拥塞,正常的访问被中止。
CC攻击的特征是什么?
1. 业务流量激增
2. 访问日志持续大量增长
3. 大量相同来源IP访问同一域名
基于以上介绍,我们来思考一下如何限定CC攻击。
首先不管是开源的WAF还是商业的WAF,CC攻击的防御大多都是阻断,而非扔进黑洞,这两者有什么区别呢?阻断大体是返回403响应码,使得本应正常返回的包返回403页,举个例子相当于本应该返回1M大小的包返回1K,这样做能很大程度的减轻网络压力以及服务器处理压力,能够减轻CC攻击所带来的影响。
我们是存在WAF的,但是为什么有WAF的防CC攻击,还要自行开发防CC脚本,在这里有一个问题需要思考的是,如果服务器在云端,网络走的是云端的按量计费方式,就算你不停的返回403,一样会计费,对于一个攻击者而言,只要你存在损失他就已经成功了,即使这个损失对你来说微不足道,但是CC的成本并不高甚至可以说是没有成本,比耐心的话可能最后你付出的会更多。
抱着这个心态我研究了一下怎么能直接让他的CC打不进来。
首先来看一下整体的一个思路:
攻击者发起CC攻击,由于域名在云环境,所以会走云计费通道进入到代理服务器,通过代理服务器转发到应用服务器,由应用服务器返回相应请求内容给代理服务器,代理服务器通过计费通道给出响应。
那么按照这个流程,综合考虑我们的目的,就需要在代理服务器上动手让非正常的请求无法通过,如下:
如此出口计费CC攻击请求不会返回给攻击者,也就不会扣除流量费用。
那么怎么判断请求是否为CC?
首先Nginx有本身的功能参数可以做到防CC,上面已经叙述过CC攻击的特征,根据特征我们可以使用下面这个参数
limit_req_zone $binary_remote_addr zone=one:10m rate=300r/s; limit_req zone=one burst=30;
意思是同一IP每秒请求限定为300次,持续30秒的请求将被阻断返回503
返回503和403有什么区别呢?单从这一点出发是没有任何区别的,所以需要用到下面这个模块来帮助我们继续开发。
Nginx有很多强大的模块,下面为大家介绍一个模块,记得编译安装时加载~
ngx_log_if模块,详细信息见 https://github.com/cfsego/ngx_log_if
这个模块的功能是筛选日志,用到这个模块的逻辑在于将503日志中的源IP筛选出来,加到防火墙中阻断。
逻辑中涉及的问题是为何每分钟只加入第一条503日志对应的IP,而非做出判断后一起加入防火墙阻断,其实这点是考虑到并发量大判断逻辑会加重服务器负载的问题,所以还是一条一条加吧,当然这里也可以优化一下。
首先定义的是日志格式,我们按照nginx默认的日志格式来,如果这里有不了解的朋友们可以搜一下nginx日志默认格式。
access_log /var/log/nginx/503/errortest.log;
接下来用到的是awk筛选出日志中的ip,这里如果有不了解awk的朋友可以参考 https://www.cnblogs.com/xudong-bupt/p/3721210.html 。
status=`awk -v i=$i -F ' ' 'NR==i{print $9}' /var/log/nginx/503/errortest.log`
做出判断
if [ "$status" = '503' ];then ip_503=`awk -v i=$i -F ' ' 'NR==i{print $1}' /var/log/nginx/503/errortest.log`
添加进防火墙
sed -i "6 i-A INPUT -s $ip_503/32 -j DROP" /etc/sysconfig/iptables
整体考虑到503日志为空以及非503状态的IP记录,做出以下脚本
#!/bin/bash i=1 while : do status=`awk -v i=$i -F ' ' 'NR==i{print $9}' /var/log/nginx/503/errortest.log` if [ "$status" = '503' ];then ip_503=`awk -v i=$i -F ' ' 'NR==i{print $1}' /var/log/nginx/503/errortest.log` echo $ip_503 sed -i "6 i-A INPUT -s $ip_503/32 -j DROP" /etc/sysconfig/iptables echo "" > /var/log/nginx/503/errortest.log service iptables restart nginx -s reload break elif [ `cat /var/log/nginx/503/errortest.log|awk -F ' ' 'NR==i{print $9}' /var/log/nginx/503/errortest.log`="" ];then break else i=$[i+1] fi done
之后使用crontab每分钟运行一遍或者更短时间运行这个脚本就可以了。
*本文作者:煜阳yuyang,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。