验证码挡不住专业爬虫,却常把真实用户挡在门外。本文用 10 个问答讲清验证码怎么选:五类方案的抗 CC 效果、破解成本、误伤来源、分层下发配置与验收。
Q:验证码到底能挡多少 CC?先说清它管什么
- 验证码解决的是单次请求成本太低这件事,不是所有 CC 攻击。
- 它能挡住三类:脚本刷登录、短信、搜索框;代理池肉鸡跑通用工具;低水平压测类请求。
- 它挡不住三类:真人众包打码、直接打静态资源与带宽、四层洪水。这三类根本不经过页面。
- 所以别把验证码当拦截器。它只是把一次请求的成本从接近零抬到几毛钱,真正决定效果的是挑战下发给谁。
Q:市面上的验证码怎么分类?
- 按交互分三类:挑战式(图形字符、滑块、点选语义、短信或邮件 OTP)、无感式(行为打分、JS 环境检测、设备指纹)、组合式(平时无感,分数低才弹挑战)。
- 按依赖分两类:依赖浏览器 JS 的,和不依赖的(OTP、签名)。这一条直接决定 APP、小程序、开放接口能不能用。
- 按成本分两类:用户时间成本(OTP 最高,要等短信)和攻击者破解成本(图形字符最低)。选型本质上是在这两者之间找平衡点。
Q:五种方案抗 CC 效果怎么排?
- 对纯脚本:无感打分 > 点选语义 > 滑块 > 图形字符 > OTP。
- 图形字符现在最弱:OCR 与视觉模型对四位字符的识别率早已超过 90%,攻击流量还能顺手做训练集。
- 滑块的轨迹可以录一次重放,市面上有现成的破解 SDK,成本比大多数人想得低。
- 点选语义需要语义模型,破解贵一些,但题库有限,被标注几十次之后准确率会明显下降。
- 无感打分的优势是用户无感;劣势是依赖数据积累,小站冷启动阶段判断准确率一般,容易两头不讨好。
- OTP 不是抗 CC 方案:它防的是账号盗用。用它抗 CC,先崩的往往是自己短信通道和真实用户。
Q:破解成本怎么算?贵的方案值不值得上
- 打码平台的公开报价量级:图形字符千次几元,滑块与点选千次十几元到几十元。
- 判断公式很直接:单次成功收益 × 成功次数 大于 打码成本,攻击者就会破。
- 优惠券、票务、注册拉新、限量抢购属于值得破的场景,再贵的验证码都拦不住,要转向账号维度限频与风控。
- 资讯站、企业官网、内容站不值得破。这类站用无感打分加接口限频就够,直接上最贵的方案纯属浪费预算。
Q:误伤率从哪来?为什么一上线投诉就不断
- 五类高频误伤:弱网与低端机(JS 挑战超时被判失败)、中老年用户(滑块拖不到位)、海外与代理用户(IP 信誉分天然偏低)、APP 内嵌 WebView(拿不到完整环境)、合法机器(监控、SEO 工具、合作方)。
- 可用的基线:挑战触发率 3% 到 8%,挑战通过率应在 95% 以上。通过率掉到 90% 以下,说明规则或题库出了问题,不是攻击太猛。
- 别把误伤当小数:日订单 2000 的站误伤 2%,一天就是 40 单,而且没人报错,只看到转化率慢慢往下走。
Q:Nginx 上怎么分层下发验证码?
- 顺序必须是:先限频,再判可疑,只对可疑请求下发挑战。全站无差别挑战等于自己打自己。
- 可疑判定用 map 组合:无 Referer、UA 非常见浏览器、单位时间请求数超阈值、命中历史恶意 IP 库,任一命中即视为可疑。
- 挑战通过后发一枚短时通行证,绑定 UA 摘要加 IP 网段,有效期 10 到 30 分钟。别绑完整 IP,移动网络换基站就掉。
- 通行证只对接口路径生效,绝不覆盖静态资源和大图,否则通过一次就长期免检。
# 1) UA 白名单:常见浏览器不算可疑
map $http_user_agent $ua_suspect {
default 1;
"~*Mozilla/5.0 (Windows NT" 0;
"~*Mozilla/5.0 (iPhone" 0;
"~*Mozilla/5.0 (Linux; Android" 0;
}
# 2) 已持通行证的不再挑战
map $cookie_cc_pass $has_pass {
default 0;
"~^[a-f0-9]{32}$" 1;
}
# 3) 组合判定:只有"可疑且无通行证"才挑战
map "$ua_suspect:$has_pass" $need_challenge {
"0:0" 0;
"0:1" 0;
"1:1" 0;
default 1;
}
# 4) 只挑战接口,静态资源永远不挑战
location ^~ /api/ {
if ($need_challenge = 1) {
return 302 /__challenge?back=$request_uri;
}
limit_req zone=api_per_ip burst=20 nodelay;
proxy_pass http://backend;
}
Q:APP 和小程序上没有浏览器,验证码怎么用
- JS 挑战、Canvas、滑块在 APP 与小程序里都不可靠,硬塞进去的结果是体验崩,而且能被逆向。
- 换成三件套:签名请求(HMAC 加时间戳加 nonce,服务端校验 5 分钟时间窗与事件幂等)、设备指纹(安装 ID、系统版本、机型哈希)、账号与设备维度限频。
- 限频的 key 必须从 IP 换成「账号加设备」。移动网络下成千上万人共享出口 IP,按 IP 限就是大规模误伤。
# 移动端按设备维度限流,IP 维度只做兜底
limit_req_zone $http_x_device_id zone=app_per_dev:20m rate=2r/s;
limit_req_zone $binary_remote_addr zone=app_per_ip:10m rate=50r/s;
location ^~ /api/v1/ {
limit_req zone=app_per_dev burst=5 nodelay;
limit_req zone=app_per_ip burst=100 nodelay;
proxy_pass http://backend;
}
Q:哪些场景不该用验证码?
- 首页、商品详情、文章页:转化关键路径,加验证码等于自己砍流量。
- 搜索引擎抓取路径:蜘蛛过不了挑战,加验证码就是主动放弃收录。
- 静态资源与下载:攻击者打的是带宽不是计算,这里该用限速、防盗链、缓存,验证码完全无用。
- 支付回调与开放接口:对方是机器,用验签和密钥配额,不要挑战。
- 反过来说,登录、注册、短信发送、搜索、评论提交这五个接口,才是验证码值得投入的地方。
Q:怎么验证选型到底有没有用?
- 看五个数就够:挑战触发率、挑战通过率、误伤工单数、被挑战 IP 的请求速率变化、核心接口转化率。
- 上线方式用灰度:同一接口先放 5% 流量开挑战,跑两天再扩到全量。直接全量上线,出问题没有对照组。
- 两条回退红线:通过率低于 95%,或转化率下降超过 2%,立即关掉。
- 攻击侧验证更直接:攻击量没降,说明对手在换 IP 而不是被挡住,这时该补的是 IP 信誉库不是题库。
Q:还有什么补充建议?
- 验证码是最后一道不是第一道。前面该有限频、UA 判定、IP 信誉,挑战只处理漏网的。
- 别追求一次挡住。目标是把攻击者单次成本抬到他觉得不划算,不是 100% 拦截。
- 题库要轮换。同一套点选题库用三个月,标注数据足够训练出模型,效果会断崖式下降。
- 反常识的一条:验证码调得越严,越要留一条无挑战的申诉通道。用户被误伤却找不到入口,丢的不是一次请求,是这个人。