本教程或者说这次分享主要包括以下四点:
- 屏蔽部分恶意/扫描请求,质询部分可疑/风险请求。
- 优化缓存配置,提升抵御攻击的能力。
- 如遇无法抵挡的攻击,自动开盾。
- Invisible Turnstile的运用。
正文部分
一、屏蔽部分恶意/扫描请求,质询部分可疑/风险请求
以下配置不适用于仅API场景。以下部分内容可能和之前的帖子有所重叠。
1. User-Agent
正常用户访问网址是会携带UA的,而部分恶意/扫描请求User-Agent为空(嗯,没错,2025年了,部分请求仍然懒得携带UA,而且这类请求还不在少数)
对应WAF规则,策略Block,表达式如下
(http.user_agent eq "")
部分恶意/扫描请求可能会携带User-Agent,但是可能是另一种形式的“懒”,见下图。
(请求库默认的UA)
(老旧或不符合常规的UA)
所以我们可以对刚刚的那条规则进行修改。
对应WAF规则,策略Block或Challenge,表达式如下
(not starts_with(http.user_agent, "Mozilla/5.0 (")) or (http.user_agent eq "Mozilla/5.0") or (http.user_agent eq "Mozilla/5.0 (compatible)")
另外还有一些“礼貌”的扫描,比如Censys发起的请求会在UA里告诉你它来自 Censys。
Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)
这类请求有一个共同特征,即 UA 包含 http 或者 bot 关键词,所以我们可以对上面的WAF规则进行进一步修改,以遏制这类扫描。
对应WAF规则,策略Block或Challenge,表达式如下
注:应用这条WAF规则前需确保已经对搜索引擎等友好爬虫进行过白
(not starts_with(http.user_agent, "Mozilla/5.0 (")) or (http.user_agent eq "Mozilla/5.0") or (http.user_agent eq "Mozilla/5.0 (compatible)") or (http.user_agent contains "http") or (http.user_agent contains "bot")
2. Accept-Language 和 Accept-Encoding
正常的真人请求在请求头中必定携带Accept-Language 和 Accept-Encoding,如果缺失这两个请求头,则极大概率表明请求为异常来源。
对应WAF规则,策略Challenge,表达式如下
(not len(http.request.headers["accept-encoding"]) > 0) or (not len(http.request.headers["accept-language"]) > 0)
较为激进的策略:
假设访客多来自于中国大陆,则Accept-Language大概率包含zh,我们可以对Accept-Language不包含zh的请求进行质询(可能会有一定的误报率),表达式如下
(not any(http.request.headers["accept-language"][*] contains "zh"))
3. 一些陈旧、异常或者与浏览器不匹配的请求头
WAF规则策略Challenge,表达式如下,仅供参考,误报率趋近于0
(any(http.request.headers["accept-encoding"][*] eq "identity")) or (any(http.request.headers["x-requested-with"][*] eq "XMLHttpRequest")) or (len(http.request.headers["x-cache"]) > 0) or (len(http.request.headers["cdn-loopcount"]) > 0) or (len(http.request.headers["trailer"]) > 0 and not http.user_agent contains "Firefox") or (len(http.request.headers["x-frame-options"]) > 0) or (len(http.request.headers["accept-charset"]) > 0) or (http.user_agent contains "Firefox" and len(http.request.headers["sec-ch-ua"]) > 0)
部分攻击脚本为了绕过速率限制可能会伪造IP请求头,如果请求中包含这类请求头,则很大概率表明请求来源异常。
x-real-ip
x-forwarded-for
x-forwarded-host
cloudfront-viewer-address
ali-cdn-real-ip
WAF规则策略Challenge,表达式如下,可能存在一定的误报率(极少数浏览器插件会传递伪造的IP请求头)
(any(http.request.headers["accept-encoding"][*] eq "x-real-ip")) or (any(http.request.headers["accept-encoding"][*] eq "x-forwarded-for")) or (any(http.request.headers["accept-encoding"][*] eq "x-forwarded-host")) or (any(http.request.headers["accept-encoding"][*] eq "cloudfront-viewer-address")) or (any(http.request.headers["accept-encoding"][*] eq "ali-cdn-real-ip"))
4. 对部分高风险ASN、地区进行无差别质询(可选、有误报率)
- WAF规则策略Challenge,表达式如下(仅列举了部分高风险ASN)
激进版
(ip.src.asnum in {14061 213230 132203 60068 30058 396982 8075 24940 16276 16509 31898 44477 202561 47583 51167}) or (ip.src.country in {"BD" "BR" "CO" "IN" "ID" "PH" "RU" "TR" "UA" "VN" "NL"})
缓和版
(ip.src.asnum in {14061 213230 132203 60068 30058 396982 8075 24940 16276 16509 31898 44477 202561 47583 51167} and not any(http.request.headers["accept-language"][*] contains "zh")) or (ip.src.country in {"BD" "BR" "CO" "IN" "ID" "PH" "RU" "TR" "UA" "VN" "NL"} and not any(http.request.headers["accept-language"][*] contains "zh"))
- WAF规则策略Block,表达式如下(强烈推荐启用)
(ip.src.asnum in {200373 203020 64267 54252 397630}) or (ip.src.country in {"T1"})
5. 设置合理的速率限制
略
二. 优化缓存配置,提升抵御攻击的能力
如果网站允许,在理想状态下是尽可能多地缓存资源到CDN节点。默认情况下,Cloudflare会对常见的静态资源进行缓存。这里主要说三点
- Cloudflare默认不会对html类型文件缓存,如果需要缓存,则需要设置缓存规则。
- 如果可以,将Caching Level配置调整为下图所示。
- 启用Tiered Cache
默认为关闭,启用后可大幅提升缓存命中率以及降低源站压力。值得注意的一点是,如果源站是anycast ip或者通过cname记录解析现实的geodns则不推荐打开此选项。
三. 如遇无法抵挡的攻击,自动开盾
如遇无法抵挡的攻击,则可能需要Cloudflare五秒盾的介入。通过关键词”Cloudflare自动开盾”搜索,市面上已经有类似功能实现的脚本,这类脚本实现原理是通过检测源站CPU负荷,当负荷达到某个设置的阈值时自动调用CF api开启五秒盾,因而这类脚本大多运行在源站服务器上,而如果攻击过于猛烈可能会导致源站服务器直接宕机而无法执行相关检测脚本。
另一个可行的方案是将脚本运行在其他服务器或者serverless平台,通过调用Cloudflare接口( https://api.cloudflare.com/client/v4/graphql
)来实现获取每分钟回源请求数,如果请求数在一段时间内超过阈值,则自动开启五秒盾。
四. Invisible Turnstile的运用
Nodeseek上有一篇这样的求助帖,虽然已经发布于36天前,但评论区仍然没有符合要求的答案。
OP的诉求是图床接入了CF,但有恶意的自动化注册,OP想对注册页进行质询,以达到遏制自动化请求的目的。目前OP采取的方案是对
https://www.xpsav.com/register
链接开启了CF验证,但实际上有更友好的方案(整个注册过程中都无需向用户展示验证页)。
如何实现?
借助Turnstile的Invisible Mode。
创建一个Turnstile,配置如下。
将获取到的
<div class="cf-turnstile" data-sitekey="test-RG3Y"></div>
和
<script src="//challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
同时放入注册页的内。
同时创建一条CF WAF规则
这样,用户在输入注册信息时,CF Turnstile会自动对用户浏览器进行校验(用户无感知),通常自动验证会在几秒钟内完成,而用户输出信息的时候一般来说大于十秒,待用户填写好信息点击提交时,Turnstile已经生成所需的cf_clearance cookie,所以整个验证过程在大多情况下对用户来说是无感知的。
这次分享一个Lua版,可以直接部署在Nginx或者Openresty上。
前提,进行Cloudflare面板,Transform Rules创建两个请求头,分别是u-bot和u-auth,注意是Set dynamic
u-bot 为 to_string(cf.client.bot)
u-auth 为 concat(substring(http.user_agent, 0, 155), to_string(ip.src), "**MbGU79LYm2L5**")
务必把MbGU79LYm2L5替换为其他密钥
Lua代码如下,将代码贴到合适的位置即可
rewrite_by_lua_block {
local user_agent = ngx.var.http_user_agent
local u_bot = ngx.var.http_u_bot
local headers = ngx.req.get_headers()
local test = headers["u-auth"]
local cookie_test = ngx.var.cookie_test
-- 检查是否为爬虫,如果是则放行
if user_agent == "bot" or u_bot == "true" then
return
end
-- 计算 test 的 MD5 值
local test_md5 = ngx.md5(test)
-- 检查 cookie 是否与 预期值 相同
if cookie_test ~= test_md5 then
ngx.header["Set-Cookie"] = "test=" .. test_md5 .. "; path=/; HttpOnly"
local redirect_url = ngx.var.scheme .. "s://" .. ngx.var.host .. ngx.var.request_uri
return ngx.redirect(redirect_url, 302)
end
}
评论0