返回知识中心
指纹

AWS WAF Bot Control:客户端实际看到什么

Common 与 Targeted 检查的信号不同,标签让规则串联,Block 与 Challenge 的响应也不同。一次 403 测量展示差异。

BotBrowser Team

文档中心

想直接进入 指纹 文档吗?

这篇文章属于知识中心的指南库。若你要步骤化配置、参考说明和持续更新,请直接进入对应 docs 分区。

托管规则组是决策引擎,有用的问题不是它是否触发,而是它收到了什么。BotBrowser 提供声明式配置文件,使页面可读取的各项表面彼此一致,不能更改规则组在边缘层做出的决定。读法应围绕结构而不是分数:两个保护级别检查不同内容,标签机制让一条规则为下一条提供输入,动作集合则让 Block 和 Challenge 产生明显不同的响应。

摘要

  • Bot Control 是一个托管规则组,内含 common 与 targeted 两个保护级别,只启用 common 级别会关闭行为检查。
  • common 级别用请求数据静态分析检测自我标识的机器人;targeted 级别包含 common,并为不自我标识的机器人增加检测。
  • 命中的规则可以附加标签,后续规则可以按该标签匹配,因此规则语言是可组合的,而不是单一分数。
  • Block 和 Challenge 是不同动作:前者结束请求,后者要求客户端运行脚本并重试。
  • 一次不带 user agent 的实测请求返回 403,HTML 正文 919 字节且没有挑战脚本,因此这是 Block,不是 Challenge。
  • 同一 endpoint 使用浏览器 user agent 时返回 200,所以该次测量的差异在请求,不在路径。
  • 标签是最容易被忽略的部分,因为显示“已阻止”的面板不会说明是哪个规则组或哪个标签产生了决定。
  • 页面侧配置文件可以保持自身表面一致,不能使边缘层决定成功。

目录

三个面板。第一个区分 Common 与 Targeted 两个托管规则组及各自的检查范围。第二个显示命中规则发出标签,后续规则读取该标签。第三个对比 Block 即正文很短的 403,与返回脚本供客户端执行的 Challenge。

一个规则组,两个保护级别

首先要明确,Bot Control 在一个托管规则组里有两个保护级别。common 级别默认运行;targeted 级别是选项,为不自我标识的客户端增加检测。只留下 common 级别并查看面板,只覆盖了问题的一部分。

common 级别用传统手法检测自我标识的机器人,例如对请求数据做静态分析;按文档的说法,它给自我标识的机器人打标签、验证搜索引擎等受欢迎的客户端,并检测高置信度特征。这类检查无需要求客户端做任何事即可计算,因此先运行,也最先拦住最粗心的自动化。

targeted 级别包含 common 的保护,并为不自我标识的高级机器人增加检测,通过限速、CAPTCHA 与后台浏览器挑战进行缓解,并使用浏览器询问、指纹与行为启发式检测。对数据任务而言,这是实际差异:满足 Common 的客户端仍可能在 Targeted 失败,而两种失败在状态码上看起来相同。

把两者放在一起读,会改变“已阻止”数量的含义。归因于 Common 的 Block 是对请求本身的判断;归因于 Targeted 的 Block 是对会话的判断,这意味着同一请求发送一次可能通过,重复发送则可能不通过。未记录规则组名称时,报告无法区分这两种情况,而应对方式不同。

标签就是规则语言

这是 AWS WAF 与基于分数的产品之间的结构差异,也是值得细读的部分。规则命中时,可以为请求附加标签。同一 web ACL 中的后续规则可以像读取输入一样按该标签匹配。

rule A matches   -> label aws:bot:category:http_library attached
                    (namespaced by the service, e.g. awswaf:managed:aws:bot-control:bot:category:http_library)
rule B reads     -> if label present, apply its own action
rule C reads     -> combine two labels into a narrower decision

这种组合让规则语言具备表达力。分数把所有观察压缩成一个数字,而数字无法说明哪两项检查同时成立。标签集合可以,因为每个标签保留自己的名称和来源,读取两个标签的规则表达的是合取,而不是阈值。

运维上的结果是,标签才是应记录的单元。只报告最终动作的面板丢失了原因,调试误报的团队只能修改客户端来找出哪条规则触发。记录被阻止请求上存在的标签可直接回答该问题,成本只是日志中的一个字段。

标签也解释了从外部看似乎不一致的行为。路径和请求头相同的两个请求可能收到不同动作,因为较早的规则匹配了第二个请求未携带的内容,差异在标签,而不在 endpoint 变更。

动作集合是客户端侧解读最常出错的地方,因为只记录成功或失败的日志会把两种结果都显示成“没有成功”。

Block      -> 403 by default; the client receives an error response
Challenge  -> 202; the client receives a script, runs it, retries with a token
CAPTCHA    -> 405; the client receives a page a person is expected to complete

只有中间动作要求客户端执行代码。把所有非 200 都当成 Block 的客户端会无限重试 Challenge,因为重试从不携带 Challenge 要求的令牌。把所有非 200 都当成 Challenge 的客户端会运行不存在的脚本,因为正文是错误页而不是 Challenge。

区别在解析之前就可见。Challenge 携带客户端应运行的脚本,Block 携带错误文档。查找脚本比匹配状态码成本更低,因为 Challenge 与 Block 的状态码可能相同。

一次实测的 Block 及其区分的动作

断言两者不同很容易,因此记录了一次请求。普通 HTTPS 客户端在完全不带 user agent 请求头的情况下请求一个文档页面,随后同一客户端使用浏览器 user agent 请求同一页面。

no_ua:        status=403 bytes=919  challenge_marker=false block_marker=true
              server=CloudFront content-type=text/html
browser_ua:   status=200 bytes=18804
              server=Server content-type=text/html

该输出中有三点值得命名。第一是状态,它是普通错误而不是 Challenge 握手。第二是正文长度,短到足以是错误文档,也远短于客户端应与之交互的页面。第三是标记扫描,响应中任何位置都没有发现挑战脚本。

第四点是比较。同一客户端、同一路径、同一天中的同一秒,在请求携带浏览器 user agent 后返回了完整页面。这不能证明某条规则触发,因为 endpoint 可能在做完全不同的事;它说明两个请求的差异在请求本身,且客户端可以改变其中一个而不改变另一个。

Block 也不会告诉客户端是哪条规则产生的。这是有意设计:响应不携带规则身份、标签集合或规则组。信息存在于厂商日志和控制台,因此诊断必须转到那里,而不能停留在客户端。

边界与所有边缘层决定相同,用该产品的术语表述更清楚。规则组读取到达的内容:请求行、请求头、来源、随时间变化的请求模式,以及边缘层可观察的连接参数。

它不读取文档。只在 JavaScript 运行后才存在的值,例如 canvas 哈希、WebGL 渲染器字符串或计算后的布局尺寸,不是响应发送前运行的规则的输入。这不是厂商选择的限制,而是操作顺序。边缘层在页面存在之前作出决定。

该顺序对数据任务有实际后果。生成完全一致文档的客户端仍须满足更早运行的检查,而那些检查针对请求而非渲染。反过来,满足所有请求级检查的客户端并未说明其文档是否一致,因为规则组从未询问。

因此两层不是带单一阈值的分数。它们是在不同时间提出的两个问题,合并两者的报告无法说明回答的是哪一个。把边缘层动作与页面侧结果并列记录,才能保持可分离,也才使后续调查成为可能。

第三次测量进一步区分 Block 与 Challenge,因为同一 endpoint 对三个形状不同的请求给出了三种不同响应:

clean_get:    status=200 bytes=18804 challenge_marker=false block_marker=false
no_ua:        status=403 bytes=919   challenge_marker=false block_marker=true
scripted_post: status=403 bytes=1053 challenge_marker=false block_marker=true

其中两个是 Block,一个是成功,两个 Block 的正文长度相差一百三十四字节。差异小到只记录状态码的读者会看到一个事件,而记录长度和标记扫描的读者会看到两个。两个 Block 都不携带挑战脚本,因此都是终止性的:客户端没有可运行的内容,重复同一请求会得到同一答案。

请求头集合提供第二种读法。成功响应报告 server=Server,两个 Block 都报告 server=CloudFront。这不是规则身份,也没有给出规则组名称,但它告诉读者哪一层作出响应,并把应用前面生成的 Block 与应用产生的响应区分开。

读者不应由此得出 user agent 是决定性输入的结论。它是第一次比较中改变的变量,足以让一个 endpoint 从错误变为完整页面。它在别处是否足够取决于该 endpoint 的配置,唯一诚实的回答方式是在相关站点记录同样三个字段,而不是从这一次推广。

数字里还有第二层教训。919 字节与 18804 字节的响应相差二十倍,只存状态的流水线会把它们当作同一类事件,而一个是错误文档,另一个是页面。存储长度几乎不增加成本,却能立即区分两者,因此它应与状态一起进入记录,而不是留到单独的调试过程。

规则组还有单次请求无法显示的时间维度。行为检查跨请求观察,因此发送一次请求并读取答案的客户端只测量了系统中最便宜的部分。发送数千次的任务才会触及累积部分,基于单次请求的报告无法描述它。这不是厂商文档的缺口,而是一次观察所能支撑的范围的缺口。

规则组看不到什么,以及实践结论

先读动作,再改客户端。正文很短的 403 是 Block,需要控制台解释;携带挑战脚本的响应是 Challenge,需要能运行它的客户端。在记录中写明规则组,因为 Common 与 Targeted 失败原因不同,而同一状态码覆盖两者。保留标签,因为它是唯一说明哪条规则与哪条规则同时成立的字段,基于分数的摘要会丢弃它。对于组合方式不同的厂商,可阅读 Cloudflare 与 DataDome:采集端看到什么;关于哪些规则需要永不抵达页面的值,可阅读 页面脚本无法计算的边缘层规则。

声明式配置文件可以保持页面暴露的各项值彼此一致,不决定托管规则组返回什么。决定属于 endpoint,客户端侧有价值的工作是让记录足够精确,以便事后解释该决定。

标签机制值得再写一段,因为它改变了对报告证明力的要求。单一分数是摘要:丢弃个别观察,只保留一个数字,不同意该数字的读者没有可继续检查的内容。标签集合保留观察,读者可以询问当时存在哪些标签、每条标签由哪条规则发出,以及最终动作需要全部还是只需要一个。这是结论与记录的区别,也是基于标签的规则语言可审计而单一数字不可审计的原因。

这种表达力有成本,值得说明以便团队规划。包含许多互相读取标签的规则的 web ACL 比单一分数更难推理,因为求值顺序重要,一条路径上存在的标签在另一条路径上可能缺失。读取两个标签的一条规则与各自读取一个标签的两条规则行为不同,而只报告最终动作的面板看不到该差异。把标签列表保留在日志中,才能使该差异事后可恢复,它也是一个月后回答误报问题的同一字段。

关于范围需要说明,因为这类主题稍有越界就会让整份记录失去价值。这里没有断言某个站点运行 Bot Control、某个状态码来自托管规则组,或某天通过的请求次日仍会通过。测量描述的是对一个文档 endpoint 的三次请求以及产品公开记录的结构;它们不描述任何受保护应用,读者若要判断真实目标,需要在该目标上记录同样三个字段。

同样的谨慎适用于标签词汇。示例使用类别形式的名称来展示组合方式,真实部署会看到自己规则发出的名称,包括服务添加的命名空间前缀。把教程中的标签字符串复制到读取它的规则里,是构建永不相符规则的最快方式,解决办法是从真实请求读取标签,而不是相信示例。

最后,撰写报告时应记住动作集合的不对称。Block 是事后可从控制台解释的决定,Challenge 是产生了第二个请求的决定。只统计第一个请求会把 Challenge 路径少算一半,因为携带令牌的重试是独立条目,可能成功且在失败计数中不留痕迹。想描述实际发生情况的报告,必须先确定统计的是哪一种,再打印数字。

来源

#AWS WAF#Bot Control#规则标签#Challenge#诊断

本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界

在 BotBrowser 里跑一遍同样的检查

加载 profile,让刚读到的这些信号在浏览器内部保持一致,并在正式上线前先在自己的机器上确认结果。