前一段公司国内几个网站的CDN流量被刷的厉害,解决问题之后一直忙于其他事情,没有时间分享,今天分析一下这个问题。
一、情况描述
公司在百度和阿里云上都开CDN的账户,大概是7月份,前后差不了几天,分别收到了百度和阿里云的短信通知,账户欠费,CDN被停用了,续费买了流量包,第二天又消耗完了,更可气的是阿里云竟然倒欠了大几百费用。要知道公司几个官网,平时一个TB流量能用大半年,出现这种情况肯定是被攻击了。
二、情况分析
分别进入百度和阿里云后台,可以看到流量攻击主要集中在晚八点到凌晨3点这个时间段。各种对图片,视频的请求纷至沓来,一个小时十几个G的流量就消耗殆尽了,这些情况有些是一个IP并发连续不停请求,有些是不同IP请求相同资源,所以尝试在CDN后台的配置根本无法抵抗这么大强度的攻击。
网络上出现了很多CDN流量被盗刷的抱怨,关于此类攻击的原因,网络上有很多说法,一些说传统CDN厂家对云服务商的攻击,还有一些说运营商跨省流量结算,一些地方需要下行流量平衡账单,等等,反正这种行为挺令人头疼。
三、解决方案
第一选项是上防火墙,阿里云WAF一年是8000左右,百度WAF按月付费最低700一月,这个选项直接排除,要知道我们服务器成本才99一年(阿里云活动),这个成本虽然不多,但不是长久之计。
第二选项是所有请求全部限速,IP,User-agent白名单,等等。很明显这无法满足要求,要么限速过低,正常访客浏览受限,要么限速过高,无法抵抗攻击。
第三选项安全圈内大佬提醒,使用第三方或自行开发类似cloudflare的“挑战-应答”模型,这是一种很好的过滤非法访问的方法,但要么占用服务器资源,要么可能会造成爬虫访问困难,进而影响网站在搜索引擎上的排名,或者影响网站访问速度,影响用户体验,最终也是没有采用。
找到一种低成本,高效率的方法过滤这些攻击,是理想的方法,通过分析上述的攻击情况,其实可以发现一些规律。
1,对于企业网站,HTML代码的体积是很小的,一般都是200KB以下,而图片视频等媒体资源,动则几MB,甚至几百MB,所以一些刷流量的请求通常不断访问媒体资源。
2,这些蹩脚的攻击,也会进行一些低成本的伪装,例如更换IP,伪造User-agent和Referer等,攻击也是需要成本的,越复杂的攻击,需要的投入越多。
3,正常的请求,一般都会先访问HTML或者JSON,然后才会访问CSS,JS,图片,视频等资源。
有了这个就好办了,我们可以这样设计一个控制模式:
对所有非HTML和JSON的请求,设置一个较低的默认访问带宽(2KB),同时限制单IP并发,这样可以把非法访问限制在一个消耗范围内,限制无法连接可能让这些攻击者去研究其他方式。
对于HTML和JSON请求,设置一个可正常浏览的速度(200KB),也限制单IP并发,并对没有附带安全COOKIE的请求,下发一个安全COOKIE,可以以用户的IP+安全字符作为令牌。
对于非HTML和JSON的请求,判断是否附带和附带的安全COOKIE是否正确,如果正确,设置一个较高的传送速度(一般800KB/s足够),对于未附带或者附带的安全COOKIE不正确的请求,设置一个低速(第一条说的2KB),不至于让攻击者发现。
对于搜索引擎爬虫,单独判断并设置一个传送速度。
使用CDN的边缘脚本执行,完全独立于网站服务器,不用对网站做任何更改变动。
最终,我开发部署了如下脚本:
百度云:
var crypto = require('crypto'); var gethash = (str)=> { var hmac = crypto.createHmac('sha256', 'Happy birthday'); return hmac.update(str).digest('hex'); }; var getcookie = (name)=> { var cookies = r.headersIn.Cookie ? r.headersIn.Cookie.split(';') : []; for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].split('='); if (cookie && cookie[0].trim() === name) { return cookie[1].trim(); } } return null; }; var setcookie = (name, value)=> { var d = new Date(); d.setTime(d.getTime() + 3600000); var expires = d.toUTCString(); r.headersOut['Set-Cookie'] = `${name}=${value}; expires=${expires}; Max-Age=3600; path=/; domain=example.com; SameSite=Lax; secure; HTTPOnly`; }; var getContentType = ()=> { var mimetype = r.headersOut['Content-Type'].split(';'); if(mimetype) return mimetype[0].trim(); return mimetype; }; var is_search_bot = ()=> { var spiders = ['Googlebot', 'AdsBot-Google', 'Bingbot', 'AdIdxBot', 'BingPreview', 'MicrosoftPreview', 'Baiduspider', 'YandexBot', 'Applebot', 'DuckDuckBot', 'Sogou Pic Spider', 'Sogou head spider', 'Sogou web spider', 'Sogou Orion spider', 'facebookexternalhit', 'Slurp', '360Spider', 'YisouSpider']; var ua = r.headersIn['User-Agent']; for (var i = 0; i < spiders.length; i++) { if (ua.includes(spiders[i])) return true; } return false; }; var get_protected_speed = ()=> { var mimetype = getContentType(); var uri = r.uri; var referer = r.headersIn['Referer']; var hash = gethash(r.remoteAddress); if ( mimetype != 'text/html' && uri == referer ) return '1k'; if ( is_search_bot() ) return '100k'; if ( mimetype.includes('video/') && getcookie('captchaprotect') == hash ) return '800k'; if ( getcookie('captchaprotect') == hash ) return '500k'; if ( mimetype == 'text/html' || mimetype == 'application/json' ) return '200k'; return '2k'; } var modify_header = ()=> { var mimetype = getContentType(); var hash = gethash(r.remoteAddress); if( ( mimetype == 'text/html' || mimetype == 'application/json') && getcookie('captchaprotect') != hash ) setcookie('captchaprotect', hash); var speed = get_protected_speed(); r.headersOut['X-Cache-BD'] = speed; r.variables.limit_rate = speed; }; r.respHeader(modify_header);
阿里云
reqhost = req_host() requri = req_uri() referer = req_referer() cookie = req_cookie('captchaprotect') hash = tohex(hmac('Happy birthday', $remote_addr, 'sha256')) def setcookie (name, value) { expires = cookie_time(add(time(), 3600)) add_rsp_cookie(name, value, ['expires' = expires, 'max_age' = 3600, 'domain' = reqhost, 'path' = '/', 'samesite' = 'lax', 'secure' = true, 'httponly' = true]) } def is_resource () { suffixs = ['.rar', '.zip', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.pdf', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tif', '.gif', '.psd', '.svg', '.mp4', '.m4p', '.mp3', '.mpg', '.mpeg', '.m4v', '.wmv', '.mov', '.qt', '.avi', '.flv', '.rmvb', '.rm', '.3gp', '.webm', '.mkv', '.vob', '.m3u8', '.ts', '.mts', '.m2ts', '.ogg', '.eot', '.otf', '.woff', '.woff2', '.ttf', '.txt', '.css', '.js'] for i, suff in suffixs { start = sub(0, len(suff)) target = lower(substr(requri, start, -1)) if eq (target, suff) { return true } } return false } def is_video () { suffixs = ['.mp4', '.m4p', '.mpg', '.mpeg', '.m4v', '.wmv', '.mov', '.qt', '.avi', '.flv', '.rmvb', '.rm', '.3gp', '.webm', '.mkv', '.vob', '.m3u8', '.ts', '.mts', '.m2ts', '.ogg'] for i, suff in suffixs { start = sub(0, len(suff)) target = lower(substr(requri, start, -1)) if eq (target, suff) { return true } } return false } def is_search_bot () { spiders = ['Googlebot', 'AdsBot-Google', 'Bingbot', 'AdIdxBot', 'BingPreview', 'MicrosoftPreview', 'Baiduspider', 'YandexBot', 'Applebot', 'DuckDuckBot', 'Sogou Pic Spider', 'Sogou head spider', 'Sogou web spider', 'Sogou Orion spider', 'facebookexternalhit', 'Slurp', '360Spider', 'YisouSpider'] for i, bot in spiders { if req_user_agent(bot) { return true } } return false } def get_protected_speed () { if and ( eq (is_resource(), true), eq (requri, referer) ) { return 1 } if eq (is_search_bot(), true) { return 100 } if and ( eq ( is_video(), true), eq ( hash, cookie) ) { return 800 } if eq ( hash, cookie ) { return 500 } if eq (is_resource(), false) { return 200 } return 2 } if and ( eq (is_resource(), false), ne(cookie, hash) ) { setcookie('captchaprotect', hash) } speed = get_protected_speed() add_rsp_header('X-Cache-BD', tostring(speed)) limit_rate_after(0, 'k') limit_rate(speed, 'k')
在调试的过程中,发现了一些问题
1,百度CDN扩展支持较强,能完全实现上述设想
2,阿里云CDN无法实现全部设想,一是最小限速最低是50KB,二是它的边缘脚本有一个执行位置选择,总共支持三个,但后台只支持两个,需要发工单人工更改到可以限速的执行位置,但这个执行位置无法获取返回的MIMETYPE,只能通过请求的资源后缀判断,可能不准确。
在CDN上部署上述脚本之后,目前整个流量消耗已经平稳,阿里云虽然没有完全实现,但也抵御了很多攻击。整个方法虽然并不无懈可击,但目前解决问题,后续可以根据情况变化进行升级。
阅读资料:
阿里云CDN可能因为盗刷流量出现高额账单(超过充值金额很多): https://www.alibabacloud.com/help/zh/cdn/product-overview/configure-high-bill-alerts
如何避免CDN被恶意攻击和恶意刷流量: https://help.aliyun.com/document_detail/362059.html