不用 CAPTCHA 识别机器人:什么才真正有效
为什么验证不再是一道防线、哪些信号取代了它,以及一套诚实的无感识别方案在实践中是什么样子。
验证在被弃用之前,就已经失效了
CAPTCHA 是一种测试,它假定解题对机器昂贵、对人便宜。这个假设的两半都已经垮掉了。
如今对机器来说解题很便宜。商业打码服务通过 API 接收一道验证并返回令牌,按千次计价,而真正盯上你站点的人早就把这一项列进了预算。机器视觉在完全没有人工介入的情况下就能处理大多数图片九宫格。
而对人来说解题不再便宜。九宫格之所以变难,恰恰是因为机器变强了,于是成本落到了你的客户身上,而不是攻击者身上。在结账环节,这个成本可以用被放弃的订单来衡量。
所以验证如今成了一项针对真实用户的税,而一个有决心的机器人只需花几分之一美分就能绕过。这就是另寻他法的全部理由。
取而代之的是:信号,而非测试
无感识别不再要求访客证明什么,转而读取会话本就在告诉你的东西。三个层面承载了绝大部分信号。
行为层。会话与页面的交互方式——指针移动的形态、事件之间的节奏、焦点在表单里移动的方式。自动化产生的分布与人类不同,而且它越是努力装得像人,差异反而越明显。
传输层。TLS 握手和 HTTP/2 设置描述了客户端技术栈。一个 JA3 或 JA3N 指纹会告诉你是哪个库发起了这次连接;即便是驱动真实 TLS 栈的无头浏览器,宣告出来的组合仍然不是 Windows 上真正的 Chrome 会产生的。这比 user-agent 字符串难伪造得多,因为它要求复刻一个真实客户端的加密栈,而不是改一个请求头。
信誉层。这个地址此前做过什么——在你的站点上,以及(可选地)在别人的站点上。判断一个地址,不只看眼前这一次会话。
没有哪一层是够用的。对于一落地就立即提交的会话,行为信号很稀薄;传输层指纹被所有使用同一个库的人共享,包括合法的那些;而一个地址第一次出现时,信誉是空白的。判定来自三者的结合。
值得了解的失效模式
任何诚实的无感识别介绍,都会写清它会在哪里出错——因为这些失误比 CAPTCHA 的更安静,也因此更容易被漏掉。
- 注重隐私的真实用户看起来像自动化。有人用高度加固的浏览器经 VPN 访问,会产生稀薄的行为信号和不寻常的指纹。你的放行名单正是为此存在的,而且必须真的去维护它。
- 共享出口地址。企业 NAT、移动运营商的 CGNAT 和 VPN 出口,会把成千上万互不相干的人放在同一个地址后面。这类地址上的信誉必须区别加权,否则你会因为某人网络里的一个人跑了爬虫,而封掉整家公司。
- 看起来很坏的好机器人。你的支付服务商的 webhook 重试、你的可用性监控、合作方的对接——全是自动化的,也全是必需的。它们该在拦截开启之前进入放行名单,而不是等到有人报故障之后。
- 第一次出现。一个全新的地址没有历史。这正是共享封禁名单的用武之地,也正是为什么一个只做信誉、不做会话分析的系统在第一天很脆弱。
- 无声的漂移。CAPTCHA 坏了是很吵的。而一个已经悄悄开始放过某种新机器人模式的检测器,看起来和一个安静的星期没有区别。请盯住拦截率与流量的比值,而不只是仪表盘上的可用性。
一套站得住脚的方案是什么样
按每一步对结果的决定性程度排序。
从观察模式开始。在关闭拦截的状态下运行足够久,看到一个完整的周周期,包括你最安静的夜晚和最忙碌的一次促销。你要找的是那些与你已确知为真的事实相矛盾的判定。
在开启拦截之前填好放行名单,而不是之后。支付 webhook、监控、合作方对接、你自己的办公网段,以及你希望收录你的爬虫。这是人们会跳过、然后在凌晨三点发现的一步。
在网关上执行,而不是在页面里。在 JavaScript 中施加的决定,是攻击者可以跳过的决定。拦截该发生在请求被响应的地方。
对平台失效开放,对你自己的规则失效关闭。如果判定服务不可达,流量应当放行——一次把你站点一起拖垮的故障,比一个漏网的机器人更糟。你自己的拒绝名单则不同:那是你的明确指令,应当继续生效。
让判定保持可解释。当有人对一次拦截提出异议时,你需要看到是哪些信号导致了它。一个只返回数字的系统,会让你在客户面前无话可说。
Karma 处在什么位置
Karma 就是把这套设计做成了产品:一个异步代码片段,行为与传输层信号经 TLS 送往采集端,判定基于你自己的租户信誉库做出,并且——如果你选择加入——还有一份由其他租户所见汇聚而成的共享封禁名单。
拦截发生在你的网关上,决定会在本地缓存,因此采集端的一次故障不会把你的站点一起拖垮。你的放行与拒绝名单始终高于平台判定。
免费的 Detect 套餐就是观察模式:每月 25,000 次判定,永久有效,无需绑卡,也不执行拦截。它正是上面描述的那套方案,而且无论你最终选择什么,这都是正确的起步方式。