我写了一个只会最笨办法的机器人:逐列像素投影切分,再和内置字符位图做模板比对。

把它放到四种常见验证码前面跑 200 次,成绩差得很远。

字符扭曲型只过 4 次,点选文字型过 10 次,滑块缺口型和算术型 200 次全过。

通关率的差距不是 20% 和 30%,而是 2% 和 100%。

这篇把四种关卡做成了一个能直接玩的实验室:机器人先替你打一遍,你再自己打一遍,最后给一张人机对比的小结。

字符扭曲型关卡与机器人识别结果

一、为什么值得做这个对比

验证码是每天都要过的关,但哪种更难,很少有人给出可复现的数字。

网上讲原理的多,给实测的少;给结论的多,给算法的少。

我想确认一件很具体的事:如果攻击者只会最笨的模板比对,四种常见方案里哪一种还能挡得住。

检索词也很清楚:验证码、CAPTCHA、滑块验证码、验证码识别。搜的人多,愿意把过程写出来的少。

二、进入码道 Web 与准备环境

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。这篇用 WebUI 版演示。

浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwsf&sourcead=dmzntgwwbwz,登录后直接在对话框里写需求,不用装软件。

我给的需求是一句话版本:做一个验证码强度实验室的单文件网页应用,四种关卡可切换,每种带一个让机器人试一次的按钮和 50 次批量压测,再加一个人类挑战模式。

产出是一个纯前端单文件 index.html,零依赖、深色主题,双击就能跑。

三、四种关卡分别挡的是什么

先看设计,再看数据。四种关卡的定位完全不一样。

关卡防御目标判定规则
字符扭曲型自动 OCR / 字符识别脚本输入 4 位字符全对才过
点选文字型自动点击 / 简单 OCR按指定顺序点中 3 个汉字
滑块缺口型自动拖拽 / 缺口检测脚本滑块与缺口偏差不超过 4 像素
算术型暴力提交 / 批量注册两位数加减法算对

字符扭曲型是 4 位字母数字逐字旋转、错切、位移,再叠 3 条干扰线和 60 个噪点。

它贵在破坏字符的连通性,让先切分再识别这条流水线断在第一环。

点选文字型在噪点背景上摆 6 个汉字,题目只告诉你其中 3 个和它们的顺序。

难度被拆成三步:定位、识别、排序。任何一步错,整题就错。

滑块缺口型在纹理轨道右侧挖一个方形缺口,拖动拼图块对齐,偏差超过 4 像素判定失败。

它对人是直觉操作,对脚本是灰度跳变:缺口边缘的亮度差就是坐标。

算术型最简单,两位数加减法,结果非负。

它不依赖图像识别,而是要求读懂题目并计算,挡住了纯图像 OCR,却挡不住会读文本的脚本。

四、机器人是怎么解题的

页面里的机器人都是现算的,没有联网,没有模型调用,四种加起来不到 200 行。

  • 字符扭曲型:逐列像素投影,连通段切分,再与内置 30 个字符位图模板比对取最高置信度。
  • 点选文字型:栈式洪水填充找连通区域,过滤小噪点,每区域与目标字模板比对,按题目顺序点击。
  • 滑块缺口型:逐列平均灰度,相邻列差分,取两个最大峰值里较小的那个判为缺口左边界。
  • 算术型:直接读题目表达式,解析两个操作数与运算符,算完填回去。

滑块和算术的解法短到可以当场背下来,全程 1 毫秒以内。

字符型的解法最长,也最脆弱。模板比对的分数几乎全落在 0.5 左右,正确字符常常排不进前三。

五、50 次和 200 次的实测数字

界面上点开始压测会跑 50 次,我又把同一套算法单独跑了 200 次做复核。

关卡50 次压测200 次复核平均耗时
字符扭曲型0 / 50,0%4 / 200,2%9.32 ms
点选文字型3 / 50,6%10 / 200,5%1.83 ms
滑块缺口型50 / 50,100%200 / 200,100%0.18 ms
算术型50 / 50,100%200 / 200,100%0 ms

两组数字对得上,字符型从 0% 到 2% 只是样本变大以后偶尔蒙对一次。

结论很直接:滑块和算术在这个攻击模型下等于没有防护。

字符扭曲型挡掉了 98% 的尝试,是四种里唯一还有效的。

点选文字型卡在中间,挡掉 95%,但它的弱点也明显:汉字不重叠时,连通区域一割就开。

批量压测完成:50 次成功率 0%

六、我在现场:几次真实的失败

把机器人放到字符型上,它的输出是这样的:

机器人失败 · 耗时 11.1 ms · 字符识别置信度过低,识别为 MYUM,正确答案是 MY6S

4 个字符里错 1 个,就是 0 分。

点选型的失败更像一道逻辑题:

机器人失败 · 耗时 5.1 ms · 识别顺序为 雨→花→天,正确顺序为 雨→地→花

位置找准了,字也认对了,只是顺序排错。

滑块型我特意手动拖到一个错误位置,页面给的提示是:

滑块偏差 242 像素,未对准缺口。

点选文字型:机器人识别顺序失败

滑块缺口型:误差 242 像素的失败提示

三种失败路径的提示都是中文原话,你在本地也能照着复现。

七、验证与踩坑

我在本地把 index.html 用静态服务器跑了一遍,命令是 python -m http.server 8080,再打开 http://127.0.0.1:8080/index.html。

这次一共做了 4 项测试:每种关卡各 50 次界面压测,外加一轮 200 次复核。四组数字都对得上。

所有数字都是这里跑出来的,界面上的压测点一次就有,200 次复核是在页面控制台里循环调同一套函数。

第一个坑在人机对比小结里。

我让人类在算术关卡答 5 次,答对 4 次、答错 1 次,成功率 80%。

机器人在同一关卡的成功率是 100%,但小结的结论写的是人类表现更好、此验证码对机器人有一定阻挡效果。

原因是那一行用了字符串比较:

const mRate = (mOk / mTotal * 100).toFixed(0);        // "100"
const humanRate = (st.c / humanTotal * 100).toFixed(0); // "80"
mRate > humanRate ? 机器人更强 : 人类更强;

toFixed(0) 把百分比变成了字符串,"100" > "80" 按字典序是 false,结论就反了。

正确写法是先 parseFloat 比大小,或者用数字算完再格式化。

第二个坑是失败提示和题目刷新的一致性。

人类答错之后,页面会立刻换一道新题,红字提示写的却是上一题的正确答案。

我在算术关卡看到算术回答错误、正确答案是 92,而屏幕上的题目已经换成另一道减法了。

判断逻辑没错,只是提示和画面差了一拍。写这篇文章时我把它当成设计取舍记下来,没有去改。

第三个坑更像常识:滑块偏差的阈值是 4 像素,人手拖动很难稳定落在这个区间。

机器人 0.18 毫秒就能算出缺口左边界,人和机器的差距不在眼力,在这条流水线上。

八、使用码道的体会

先说结果:一句话需求就能出可运行的整页应用,四种关卡、机器人、压测和对比小结都在里面。

结构是我给的,实现细节是它补的。比如缺口左边界取两个最大峰值里较小的那个,这种策略我写需求时并没有想那么细。

也有需要盯的地方。机器人识别率这种指标没有客观验收标准,它在字符型识别率上反复迭代了很久,最后仍然只有 2%。

这个数字不好看,但它是对的:扭曲强度摆在那里,简单模板比对就是过不了。

所以我留了 2% 没去动,把它当成实验结论的一部分。

另外提醒一句:页面里的机器人在自己生成的验证码上跑,不针对任何真实站点,演示代码只用于理解方案的强弱边界。

九、直接下载试玩

这个 Demo 已经完整开源(源代码、本文档、全部截图),托管在公开仓库,可直接下载来玩:

打开上面的链接,进入 codearts-captcha-lab 文件夹,把 index.html 下载到本地,用浏览器打开即可运行,无需安装任何依赖。

十、小结

四种验证码在这个实验里的排序很干净:字符扭曲 2%、点选文字 5%、滑块 100%、算术 100%。

带行为检测的滑块和带风控的算术不在这个攻击模型里,纯缺口的滑块和纯文本的算术确实是裸奔的。

下一步我想加的是随机扰动加重试惩罚这类行为特征,看看能不能把滑块的通关率也压下来。那是另一个实验了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐