数字营销 · Web开发 · 基础设施

企业网站CDN被刷流量怎么办

从企业网站 CDN 流量被恶意刷取的真实案例出发,分析异常请求的行为特征,并通过 CDN 边缘脚本结合 Cookie、资源类型和访问速度进行分层控制,在不影响正常访客的情况下减少异常流量消耗。

前一段时间,公司国内几个网站的 CDN 流量被刷得非常厉害。问题解决之后,一直忙于其他事情,没有时间整理,今天终于有时间把这个问题记录下来。

情况描述

公司在百度和阿里云都开通了 CDN。大约在 7 月份,前后相差几天,分别收到了百度和阿里云的欠费提醒,随后 CDN 被停用。续费、购买流量包之后,第二天流量又很快消耗完了,更夸张的是阿里云甚至产生了几百元的欠费。要知道,公司几个官网平时一个 TB 的流量可以使用半年甚至更久,突然出现这种消耗速度,基本可以确定网站遭遇了异常流量攻击。

CDN流量消耗图

情况分析

分别进入百度和阿里云后台,可以看到异常流量主要集中在晚上 8 点到凌晨 3 点之间。大量针对图片、视频等资源的请求不断出现,一个小时就可能消耗掉十几个 GB 的流量。有些表现为同一个 IP 持续、高并发地请求,也有一些是大量不同 IP 请求相同资源。尝试在 CDN 后台进行常规配置后发现,这种强度的攻击已经很难仅靠简单的访问控制规则解决。网络上也有不少关于 CDN 流量被恶意刷取的讨论,对于这类攻击具体是如何产生的,有各种不同的说法。不过对我们来说,攻击来源并不是最重要的问题,如何在不影响正常访客的情况下,把异常流量控制在一个可接受的范围内才是更实际的问题。

为什么传统限速很难解决

当时首先考虑的是几种比较常见的办法。

上 WAF

例如阿里云 WAF、百度 WAF 等。这个方案当然有效,但成本比较高。以当时的价格计算,阿里云 WAF 一年大约 8000 元,百度 WAF 最低也需要每月数百元。

而我们网站的服务器一年也就几十到一百元左右。对于这种主要由静态资源被大量请求导致的流量攻击,这个成本显然不太适合长期使用。

全部请求限速

第二种办法是直接限制 IP、User-Agent 或者所有请求的带宽。问题也很明显:

限制得太低,正常访客访问图片和视频也会受到影响;

限制得太高,又无法有效控制攻击流量。

而且企业网站本身流量并不大,不能为了防攻击,把正常用户体验也一起牺牲掉。

挑战-应答

后来安全圈的一位朋友提醒,可以考虑采用类似 Cloudflare 的“挑战-应答”机制,对访问者进行验证。

这种方法确实能够有效过滤部分自动化请求,但对于企业官网来说也存在一些问题:

一方面,挑战过程需要消耗服务器或者边缘资源;

另一方面,过于严格的验证可能影响搜索引擎爬虫,也可能增加正常用户的访问时间。

最终也没有采用这种方案。

从攻击行为中寻找规律

既然传统方法都不理想,那就只能重新分析攻击本身。经过观察,我发现这类攻击存在几个比较明显的特点。

HTML很小,媒体资源很大

对于普通企业网站来说,HTML 页面通常并不大,很多页面甚至只有几十 KB 或者一两百 KB。但图片、视频等资源却完全不同,一张图片可能几 MB,一个视频甚至可能达到几百 MB。

因此,如果攻击者的目标是快速消耗 CDN 流量,那么持续请求图片、视频等大资源显然更加划算。

攻击者会进行一定程度的伪装

这些攻击并不是完全没有伪装。攻击者可能不断更换 IP,也可能修改 User-Agent、Referer 等请求信息。但攻击本身也是有成本的。伪装得越复杂,就意味着攻击程序需要处理更多逻辑,攻击成本也会提高。

正常浏览存在一定的访问顺序

一个正常用户访问网页时,一般会先请求 HTML 或 JSON,然后浏览器再根据页面内容请求 CSS、JavaScript、图片、视频等资源。

也就是说:

HTML / JSON

CSS / JS

图片 / 视频 / 其它资源

而很多简单的刷流量程序,则可能直接针对图片、视频等大资源发起请求。这给了我们一个非常重要的判断依据:不一定要判断“这个请求是不是攻击”,也可以判断“这个请求有没有经过正常的访问流程”。

最终方案

基于上面的分析,我设计了这样一套控制策略。

对于所有 HTML 和 JSON 之外的请求,默认设置一个很低的访问速度,例如 2KB/s,同时限制单 IP 的并发连接数。

这样,即使攻击者不断请求图片、视频等资源,其单位时间内可以消耗的流量也会被限制在一个很小的范围内。

对于 HTML 和 JSON 请求,则给一个正常浏览所需要的速度,例如 200KB/s,同时限制单 IP 并发。

当用户访问 HTML 或 JSON 时,如果没有安全 Cookie,就给客户端下发一个 Cookie。这个 Cookie 可以根据用户 IP 和服务器端的秘密字符串生成一个令牌。

接下来,当客户端请求图片、视频等资源时,就检查它是否携带这个 Cookie。

如果 Cookie 正确,就认为它已经完成了基本的“浏览行为”,给予较高的资源下载速度;

如果没有 Cookie,或者 Cookie 不正确,就继续按照很低的速度进行限制。

搜索引擎爬虫则单独识别,给予一个相对合理的访问速度。

整个过程可以简单理解为:

访问 HTML / JSON

获得安全 Cookie

请求图片 / 视频

检查 Cookie
↙ ↘
正确 不正确
↓ ↓
较高速率 低速率

最重要的是,这套逻辑直接放在 CDN 边缘脚本中执行,不经过网站服务器,因此不需要修改网站本身。

百度CDN实现

最终我在百度 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 contentType =
r.headersOut['Content-Type'];

if (!contentType) {
return null;
}

return contentType
.split(';')[0]
.trim();
};

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 &&
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);

这里的核心并不是具体的 Cookie 名称或者某一个限速值,而是:

根据请求是否经过正常页面访问流程,决定后续大文件资源的访问速度。

七、阿里云CDN实现

阿里云 CDN 的边缘脚本能力与百度 CDN 并不完全一致,因此需要做一些调整。

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
]
)
}

后面的资源类型判断和限速逻辑与百度 CDN 基本类似。

这里就不再逐行解释了,核心仍然是:

HTML / JSON

设置 Cookie

请求其它资源

检查 Cookie

决定限速

实际部署中遇到的问题

在调试过程中,也发现了一些问题。

百度CDN

百度 CDN 的扩展能力相对比较强,最终基本实现了上面的设计思路。

阿里云CDN

阿里云 CDN 则存在一些限制。

第一,最小限速比较高,当时最低只能设置到 50KB/s,无法做到百度 CDN 那样的 2KB/s。

第二,阿里云边缘脚本存在多个执行位置,但控制台能够直接选择的执行阶段有限。如果需要使用可以进行限速控制的阶段,需要提交工单由人工调整。

第三,这个执行位置无法直接获取响应的 Content-Type,只能通过请求 URL 的后缀判断资源类型,因此准确性不如直接判断 MIME Type。

虽然存在这些限制,但实际效果仍然比单纯依靠 IP 限速好很多。

最终效果

在 CDN 上部署上述脚本之后,目前整体流量消耗已经恢复平稳。

阿里云虽然没有完全实现最初的设计,但也抵挡住了大量异常请求。

这套方案当然并不完美,它只是利用了一个非常简单的规律:

正常用户会按照一定的顺序访问网站资源,而大量自动化刷流量请求往往会跳过这些步骤,直接请求最消耗流量的大文件。

因此,我们并不需要准确判断“这个请求是不是攻击”,只需要让没有经过正常访问流程的请求付出更高的带宽成本即可。

这套方法最大的优点是成本很低,而且直接运行在 CDN 边缘,不需要修改网站本身。

当然,它并不是万能的。

一旦攻击者了解网站的防护逻辑,就可以模拟正常浏览行为,重新获取 Cookie,甚至完整执行页面中的 JavaScript。因此,如果攻击规模进一步扩大,仍然需要结合 WAF、访问频率控制、IP/ASN 规则、Bot 管理等其它手段进行防护。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。