攻防原理 · BROWSER AUTOMATION

我们是怎么“点过”人机验证的
—— Cloudflare Turnstile 攻防原理

那个写着“请验证您是真人”的小勾选框,到底在防什么、怎么识别机器人, 以及一个真实浏览器自动化工具是从哪几个维度把它过掉的。带真实实测、诚实的边界。

一个粗糙的机器人去点 I AM HUMAN 勾选框的涂鸦
fig.1 — 一个机器人,正在努力证明自己是人。

你一定见过它:打开某个网站,先蹦出一页“正在进行安全验证”, 中间一个勾选框「请验证您是真人」,配着 Cloudflare 的橙色云朵。勾一下、转个圈,过了。 这东西叫 Cloudflare Turnstile / Managed Challenge

它看起来只是“点个框”,但那一下点击背后,是一整套机器人识别在运行。 先讲它怎么防,再讲怎么过。


一、防守方:它凭什么认定你是机器人

Turnstile 不是靠“你会不会点框”来判断的——勾选框只是最后的仪式。 真正的判定发生在你看到页面之前,后台一段 JS 在几百毫秒里给你打了个 威胁分(threat score)。打分的依据分四层:

1. 被动指纹(你是什么浏览器)

不需要你做任何动作就能采集的信号:

2. 行为信号(你像不像活人)

3. 环境信号(你是不是被程序控制着)

4. IP 信誉

数据中心 IP(云服务器机房段)天然高风险;住宅/移动 IP 天然低风险。 同一个 IP 短时间大量请求,分数直接拉黑。

橙色云朵当门卫,真人放行、机器人被拦着点框的涂鸦
fig.2 — 云朵门卫:分高的直接放行,分中的让你点框,分低的拦住。

打完分,结果只有三种:

威胁分表现你看到的
高(像真人)invisible 放行什么都没有,直接进
中(不确定)managed challenge「请验证您是真人」勾选框
低(像机器人)拦截 / 死循环勾了也一直转圈,进不去

过关之后,Cloudflare 会发一张通行证 cookiecf_clearance。 它是 HttpOnly(JS 读不到),而且绑定你的 IP + User-Agent。 带着这张票、用同一个 IP 和 UA 再来,就直接跳过验证——这点后面有用。


二、攻击方:我们从哪几个维度过掉它

看懂了打分维度,怎么过就清楚了——把每个维度都做成真人的样子。 我们用的是 chrome-use(一个真实浏览器自动化工具)。

① 直接驱动“真实的 Chrome”,干掉指纹和 IP 两个维度

最关键的一步:不开无头浏览器,而是通过浏览器扩展中继, 驱动用户自己那台、已经登录、天天在用的真实 Chrome。 于是指纹是真的、cookie 是真的、出口是住宅 IP。 被动指纹和 IP 信誉这两层,直接就是满分。

② Stealth:用原生覆盖抹掉自动化破绽

剩下的零星破绽用底层覆盖补掉,而且原则是“原生覆盖 > JS 撒谎” ——能用 CDP / Chrome 启动参数改的,就不用 JS 打补丁(JS 补丁本身会留痕):

③ Humanize:把点击和打字变成活人

针对行为检测:鼠标走曲线、带减速、落点有抖动,打字用变速节奏, 滚动有缓动。专门骗 Akamai / DataDome / Turnstile 这类行为评分。

④ 这条路真正的杀手锏:受信点击

Turnstile 校验那一下点击的 isTrustedJS 合成的 element.click()isTrusted=false,当场被拒—— 这是绝大多数“纯脚本”方案死在这一步的原因。 而我们走 CDP 的 Input.dispatchMouseEvent,浏览器把它当成真实硬件输入isTrusted=true,和真人手点一模一样。这是“真实浏览器 + CDP”这条路相对纯 HTTP 脚本的硬优势。

一句话总结攻防对应关系:
指纹 → 真实 Chrome + stealth · IP → 住宅出口 · 行为 → humanize · 点击 → CDP 受信输入

三、真实实测:点得过,但要看清“过的是什么”

我们在两个公开 demo 上实测(真实 Chrome + 受信点击):

诚实的边界(很重要): 这两个 demo 用的都是 Cloudflare 的测试 sitekey3x00000000000000000000FF,强制弹挑战、点了必过,页面上会有红字 “For testing only”)。所以严格说,它们测的是 “能不能定位并受信点击那个会动的勾选框”这个机械能力而不是 Cloudflare 的真实威胁评分。机械层面我们 2/2 通过; 真实评分对抗(生产 key、真打分)是另一个维度,公开 demo 测不到——因为没人会把生产 key 摆出来给你测。

四、续存优化:解一次,别每次都解

过关那张 cf_clearance 是真正值钱的东西。两个要点决定它能不能复用:

所以我们给 chrome-use 加了一个 过关前预检 命令 chrome-use cf-status:访问前先判断“当前是不是 Cloudflare 挑战页” 以及“有没有还没过期的 cf_clearance”,给出建议:

$ chrome-use cf-status
✓ cleared — no challenge, proceed        # 已有有效通行证,别重解
  challenged:     no
  cf_clearance:   valid, expires in 27m 11s
  device trusted: yes (CF_VERIFIED_DEVICE)

# 三种结论:
#  proceed  → 已过关,直接用
#  solve    → 有挑战、没通行证,去解
#  reissue  → 有通行证但页面还拦着 → IP/UA 漂了,重解

配合“驱动真实 Chrome”,续存基本是免费的:解一次,通行证就留在真实 profile 里,下次同 IP 同 UA 直接进。


五、这是一场永远的动态博弈

必须说清楚我们这条路唯一的软肋:CDP(调试协议)attach 本身是可被检测的—— Runtime.enable 等操作会留下信号,理论上一个足够狠的生产站能据此压低你的分。 所以这不是“一招通杀”,而是持续对抗:检测方加新指纹,绕过方补新覆盖。 nowsecure 那句“一直是动态的”,说的就是这个——挑战框每次位置都变、形态都变, 就是逼你别写死、别偷懒。

这也是为什么这类研究的正经用途是给自己的工具做反检测自测: 你得知道自己在每个维度上还露不露馅,才能补。