尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从验证码到设备指纹:注册接口防批量注册与恶意攻击的实战方案

发布时间:2026/9/16 10:19:17

资讯中心
01
ARTICLE

从验证码到设备指纹:注册接口防批量注册与恶意攻击的实战方案

从验证码到设备指纹:注册接口防批量注册与恶意攻击的实战方案
“FckSignups”这个项目名第一次看到的人多半会笑一下——它像是程序员半夜对着注册后台一堆假账号爆粗时随手起的名字。但真正用下来你就会明白这个名字把产品的怨气表达得很精准所有对外开放注册入口的产品都逃不过被垃圾注册、批量注册、刷短信通道、小额薅羊毛这些事反复折磨。FckSignups 本质上是一套注册防护策略的落地实现目标很单纯——在不劝退真实用户的前提下把机器注册和批量注册挡在门外。它不是传统意义上的验证码组件而是把前端埋点、蜜罐陷阱、频率控制、设备指纹和行为特征分析整合在一起的服务端防护模块。这篇文章我会把这套方案从设计思路、核心模块、接入流程到常见坑逐层拆开讲。不管你是后端开发、安全工程师还是独立开发者只要产品有注册接口都可以参考这套方案来搭一套自己的注册防护体系。1. 注册接口为什么总是被“薅”1.1 一个公开接口攻击成本低到离谱注册接口是产品里少数几个“不需要任何身份凭证就能调用”的公开接口。用户还没登录服务端就得先允许他提交账号、密码、手机号、邮箱这些信息。这个天然的开放性意味着任何人写一个几十行的脚本就能对着注册接口每秒发几十个请求批量创建账号。成本低到什么程度一个脚本、一个能收短信的号池、或者干脆用一次性邮箱域名就能源源不断地造号。很多产品在做用户增长时会推出新人红包、注册送积分、首单立减等活动结果活动还没跑完先被一批又一批的机器账号把预算薅光了。我自己见过一个典型的案例某社区产品开放邮箱注册后日常新增注册量在几百到一千之间结果某个周末突然冲到五千多后台一看全是同一个邮箱域名下的随机前缀账号。这些账号注册完不完善资料不发文不互动只挂机。等运营发现时整个用户库里已经有上万僵尸账号后续每次做推送都产生大量无效短信费用。1.2 垃圾注册的目的千奇百怪但危害都一样批量注册的动机主要分几类发广告和垃圾内容注册完马上在公开板块发推广帖、私信骚扰、刷评论区。刷数据和榜单给某些内容刷播放量、给商品刷好评差评、把活动排行榜刷到无法看。领福利和薅羊毛新用户红包、优惠券、体验金、邀请奖励机器账号批量领取。养号和后续作恶批量注册后把账号养起来为以后营销、灌水、恶意举报做准备。扫号和撞库尝试拿泄露的账号密码批量试探其他平台的注册接口。不管目的是哪一种最终都会反映成几个技术特征注册请求量激增、大量账号信息规律性强、账号活跃行为异常、短信或邮件发送量暴增。FckSignups 要做的就是在这几个特征刚冒头的时候把请求拦在注册逻辑之外。1.3 传统验证码为什么不够用很多人第一反应是加个图形验证码不就行了但实际接入过的人都知道验证码是防御手段里“性价比越来越低”的那一种。现在打码平台接个接口几秒钟就能识别普通图形验证码滑动验证码有专门的通杀脚本行为验证码如果只做前端校验抓包后直接绕过也不是什么难事。更关键的是验证码是“一刀切”正常用户也被迫每次都要做一遍转化率掉得很快。所以 FckSignups 的思路从一开始就不是“用一个验证码挡住所有人”而是把注册请求拆成多个维度的信号每个信号单独看都不致命合在一起打分低风险直接放行中风险弹一次验证高风险直接拒绝。这个思路的好处是真实用户几乎全程无感而脚本要同时绕过前端埋点、频率控制、设备指纹、行为分析这些关卡成本就高得多了。2. 整体设计思路四层防护先放行再校验先说结论FckSignups 的整体架构可以拆成四层每层负责一个维度的信号采集和判断层与层之间不耦合。请求前置检查IP 信誉、出口类型、基础黑名单。前端埋点与蜜罐判断请求是不是真实浏览器发出的。频率与行为特征统计同一个维度下的注册强度。兜底验证中高风险用户引导走验证码、邮件激活或人工审核。2.1 为什么用“打分制”而不是“一票否决”早期我做防护时也踩过“一票否决”的坑某个 IP 断定为机房 IP 就拒绝注册某个设备指纹可疑就拒绝注册结果真实用户被误杀得很惨。因为单一信号的可信度其实很低比如机房 IP 也可能是某个公司办公网络设备指纹在隐私模式下也可能缺失。打分制的逻辑是每个信号按严重程度加分比如“命中蜜罐字段”加 40 分“同一 IP 5 分钟内注册 3 次”加 20 分“IDC 机房 IP”加 15 分“行为特征缺失”加 5 分。最后总分小于 60 直接通过60 到 80 弹验证码大于 80 拒绝。这样一个看似“可疑”的请求如果行为特征正常、频率正常总分可能只有 20 分直接放行不会误伤。2.2 为什么采用“中间件模式”而不是改业务代码接入防护模块最怕的是侵入业务逻辑。很多团队一开始把验证逻辑写在注册函数里写了几百行之后业务一改防护逻辑就跟着崩。FckSignups 采用中间件模式在注册接口真正被执行之前统一经过一个防护网关。这样做有几个好处第一注册业务代码零改动接入和回滚都很快第二防护策略可以独立迭代不用发版业务代码第三同一个防护网关可以复用到其他接口比如找回密码、修改手机号只要把配置里的场景一换就行。请求进入注册接口 - FckSignups 前置检查 - 通过低分 - 进入正常注册逻辑 - 需要验证中分 - 返回验证码响应用户完成验证后带 token 再提交 - 拒绝高分 - 直接返回“注册过于频繁/请求异常”这个流程里最关键的是前置检查阶段不要阻塞太久。我一般要求整个检查链路在 100 毫秒内完成否则用户会明显感觉注册卡顿转化率也会受影响。3. 核心模块拆解每个环节到底在干什么3.1 蜜罐字段最便宜但最有效的反脚本手段蜜罐字段的原理很简单在注册表单里放几个真实用户永远看不到、也不会填写的隐藏输入框比如website、fax、company。正常人打开页面根本不会填但很多脚本是“拿到表单里所有字段就统统填一遍”尤其是用浏览器自动化框架时会把所有 input 都当成必填项处理。当服务端收到一个注册请求发现这些蜜罐字段里居然有值那基本可以断定这是脚本直接加分拦截。它不需要任何模型训练不需要外部服务部署成本几乎为零。但蜜罐字段有几个实操细节要注意字段名不要起得太像“蜜罐”像phone_confirm、email_again这种反而容易被脚本识别。用website、fax、company这类反常识字段更有效。样式上不要用display: none有些爬虫会过滤隐藏元素。用position: absolute; left: -9999px这种方式或者说用透明叠加层让肉眼看不到但不影响 DOM 结构。蜜罐字段不能是必填项否则正常用户提交的浏览器也不会填那才是灾难。最好同时加一个“页面渲染时间”标记脚本直接 POST 往往不会先加载页面这个标记缺失也能作为加分项。3.2 行为特征采集不用太复杂几个指标就够行为特征听起来高大上但落地时不需要上什么深度学习模型。我实际用的就是几个简单指标组合注册页停留时长从页面开始加载到提交表单正常用户至少需要几秒钟脚本可能 1 秒内就提交了。输入速度正常用户输入邮箱和密码有快有慢平均每个字符间隔不会均匀到毫秒级。焦点切换用户从一个输入框切到另一个输入框必然有 focus 和 blur 事件。脚本往往直接填值触发 submit。鼠标轨迹 / 触摸事件页面有鼠标移动或触摸记录说明确实有人在操作完全没有这类事件加分。前端把这些信息汇总成一个 JSON连同注册请求一起提交服务端解析后按规则打分。为了防止被抓包后重放我一般会对这个 JSON 做一次简单的签名用盐加时间戳生成 hash后端先验签名再取值。不过需要特别提醒行为特征采集涉及用户隐私做的时候要克制只采集上面这些“和防作弊直接相关”的元数据不要采集用户输入的具体内容、页面里的真实表单值这种敏感信息。在产品层面最好在隐私政策里说明会收集基本的操作行为数据否则审核和合规都容易出问题。3.3 频率控制滑动窗口比固定计数更稳频率控制是防批量注册的另一根柱子。它的目标很简单同一个维度下单位时间内的注册次数超过阈值就限制。这里的“维度”不只是 IP还应该包括设备指纹、邮箱域名、手机号段等。只按 IP 限流很容易误伤比如公司或校园里几十个人共用一个出口 IP稍微人多一点就把正常用户挡外面了。我用 Redis 做滑动窗口计数伪代码如下import time import uuid import redis r redis.Redis(host127.0.0.1, port6379, db0) def check_frequency(dimension_key): key ffcksignups:register:{dimension_key} now int(time.time()) window 3600 limit 10 pipe r.pipeline() pipe.zremrangebyscore(key, 0, now - window) pipe.zadd(key, {str(uuid.uuid4()): now}) pipe.zcard(key) pipe.expire(key, window) result pipe.execute() return result[-1] limit用 ZSET 的好处是可以精确统计一个滑动时间段内的请求次数而不是像固定窗口那样“这一分钟内过了 10 次就封下一分钟又全放行”。实际使用中我一般会配置多档位窗口窗口阈值触发动作5 分钟3 次返回验证码1 小时10 次返回验证码 延长激活时间24 小时30 次直接拒绝不同产品可以按注册场景调整。比如活动页做拉新时可以把阈值放宽否则用户群一拉起来就全被限制了但短信通道和邮件通道的请求频次要单独再压一档防止被刷爆。3.4 设备指纹识别“换了马甲”的同一批机器很多脚本不会只用一个 IP 批量注册而是会利用一些手段频繁切换网络出口这时候单纯按 IP 限流就失灵了。设备指纹要解决的是换了 IP但底层浏览器环境还是那一套怎么把它认出来。常见的设备指纹信号包括 User-Agent、Canvas 指纹、WebGL 渲染信息、字体列表、屏幕分辨率、时区、语言等。把这些信息做一次 hash就能得到一个设备 IDs。同一个设备换 IP 注册时设备 ID 大概率不会变服务端就可以把“同一设备 不同 IP 高频注册”这些条件组合起来判为高风险。我自己的实现是先在前端收集信息用 SHA-256 生成指纹再把指纹和服务端采集到的 User-Agent、Accept-Language 做一个加权核对。如果前端指纹和服务端看到的头部差异太大也直接加分。设备指纹在隐私合规上是个敏感点落地时我通常做两件事一是把原始信息留在前端只传 hash 值到服务端尽量不收集原始 Canvas 截图之类的数据二是给指纹加一个“会话内有效”的过期时间不长期保存用户设备信息减少合规压力。3.5 IP 信誉和出口类型识别“批量注册”的高发区这一层我的做法是把 IP 映射成几类出口然后对不同类型给不同权重。家庭宽带出口、移动网络出口正常用户多权重低IDC 机房 IP异常注册高发区权重高如果 IP 在已知的恶意 IP 黑名单里直接加高分。机房 IP 怎么识别最简单是维护一份 IP 段列表来源包括云服务商公开的 IP 范围、线下设备和安全社区共享的情报。更新频率不用太高一个月一次就够了。黑名单部分可以接免费或商业的情报源做增量同步。这里要特别谨慎仅仅因为一个 IP 是机房 IP 就拒绝注册误杀率会很高。因为很多小型企业、创业团队日常办公就是云上服务器或专线员工注册产品时也会被命中。所以机房 IP 在我这里权重只有 15 分左右真正决定拦截的还是综合总分。3.6 兜底验证给“疑似”用户一个证明自己的机会不管前置检查做得多好总会有边缘情况网络环境确实特殊、浏览器隐私模式导致指纹缺失、移动网络下行为特征不明显。如果把这些用户一刀切产品的新增转化率会非常难看。所以分数在“需要验证”区间的用户我会让他们走一次验证码。这里验证码不等于传统的图形验证码可以是滑块、点选文字、邮件激活链接、短信验证码。具体用哪种看产品类型低敏感产品社区、工具类用邮件激活即可成本低、体验相对可接受。中敏感产品电商、金融类用短信验证码。高敏感产品支付、交易类建议短信 人工审核双保险。邮件激活真的是被低估的防护手段。很多批量注册脚本为了省成本用的是临时邮箱临时邮箱往往不具备实时收信能力或者域名本身已经被标记。注册后必须激活才能使用这一道关卡就能滤掉相当一部分低水平脚本。4. 实操过程5 步把 FckSignups 接进你的服务4.1 第一步确定要防护的场景不要一上来就代码先盘场景。你的注册入口有几个普通注册、第三方登录绑定、活动页快捷注册、App 内注册是不是同一个接口我建议先给每个入口建一个独立的场景标识后续做限流、做拦截统计时才不会混。比如web_register、app_register、activity_register。同一个 IP 在 5 分钟内分别调用了三次网页注册和两次活动页注册那整体注册压力要高过单一入口所以频率计数也要按“用户维度”汇总一次。4.2 第二步配置阈值先松后紧FckSignups 支持一套 YAML 配置我直接把常用的配置示例写出来fcksignups: scenarios: web_register: honeypot_fields: [website, fax, company] behavior: require_render_mark: true min_stay_ms: 800 min_input_var_ms: 30 rate_limits: - window: 300 max_attempts: 3 action: verification - window: 3600 max_attempts: 10 action: verification scores: honeypot_filled: 40 no_render_mark: 10 fast_submit: 15 idc_ip: 15 high_freq: 20 known_ip: 30 thresholds: verification: 60 block: 80注意这里的阈值不是拍脑袋定的要结合你们产品的真实注册数据来调。接入初期我通常会把verification调成 80block调成 90先让绝大多数请求都通过只拦最明显的机器流量。跑一周看日志里拦截量和验证通过率再逐步收紧。4.3 第三步前端埋点前端需要在注册表单所在的页面加一段初始化脚本监听页面加载完成时间写入一个隐藏字段_render_ts。监听鼠标移动、键盘输入、focus/blur 事件汇总成行为轨迹摘要。计算整个表单填写耗时。把设备指纹 hash 写入提交数据。这里要处理 JSONP 跨域问题如果注册接口在另一域名下前端要用 fetch 带 CORS 方式提交或者把埋点数据放在自定义请求头里。4.4 第四步服务端中间件接入拿 Python Flask 举例接入方式类似下面这样from flask import Flask, request, jsonify from fcksignups import FckSignups app Flask(__name__) guard FckSignups(config.yaml) app.route(/api/register, methods[POST]) def register(): decision guard.check(web_register, request) if decision.action block: return jsonify({code: 429, msg: 请求过于频繁请稍后再试}), 429 if decision.action verification: return jsonify({ code: 403, msg: 请完成验证后继续, verify_token: decision.verify_token, }), 403 # ... 正常注册逻辑核心点在于decision.verify_token。用户完成验证码后服务端签发一个短时效 token下一次注册请求带这个 token就可以跳过验证环节。token 有效期我一般设 5 分钟用完即弃。4.5 第五步日志、监控和告警这一块很多人会忽略但我认为它和拦截功能同等重要。没有日志你就不知道拦截到底有没有效更不知道有没有误杀。每一条注册请求无论放行还是拦截都要记一条日志包含场景、IP、出口类型、设备指纹、各维度分数、总分、最终动作、耗时。每天跑一个统计任务看三件事放行率是否低到影响新增注册。拦截率是否有上升或异常波动。验证通过率被拉去验证的用户最终有多少完成了注册。再加上告警如果单小时拦截量超过日常均值 3 倍就推送通知到工作群。这往往是活动刚开始被刷或者有人写好了针对你注册接口的脚本的信号。5. 常见问题与排查技巧实录5.1 误杀正常用户动态出口 IP 一锅端现象办公室或学校网络下几十上百人共用同一个出口 IP频率限制一触发所有人都被要求验证码甚至直接拒绝。排查思路先看日志里被拦截的 IP 是不是集中在某个段再看这些请求的设备指纹是否各不相同。如果指纹差异大、行为特征正常就是 IP 维度权重太高了。解决方案把设备维度加入频率计数。同一设备在窗口期内超过次数才触发限制IP 维度的阈值放宽到设备维度阈值的 3 倍以上。同时可以加一个“同 IP 大量不同指纹”的异常信号这比单纯限制单一 IP 更准确因为批量脚本的特征恰恰是少数设备对应大量请求。5.2 短信通道被刷爆现象注册页接入短信验证码结果当天短信供应商打电话来说费用超了十倍。排查思路这类问题几乎都是因为验证码发送接口没有做独立限流。注册接口的限流做了但用户点击“获取验证码”这个动作对应的接口没接防护脚本可以直接循环调用发一条收一条。解决方案把“发送验证码”也纳入 FckSignups 防护。限流策略要更严格同一手机号 60 秒内只能发一次同一 IP 1 小时最多发 5 次设备维度 24 小时最多发 10 次。再者对异常请求返回的提示文案要和正常限流区分开避免让脚本判断出真实的频控规则。5.3 隐私模式 / 无痕模式下用户被识别为高风险现象用户开了浏览器的隐私模式前端采集行为数据时部分接口失效设备指纹也不完整导致分数偏高经常弹验证码。排查思路这种用户的行为数据特征往往是“没有鼠标轨迹记录”“canvas 指纹为空”之类的空值而不是“异常值”。空值不应当直接加分太多。解决方案把“数据缺失”和“数据异常”分开处理。指纹缺失可以加 5 分指纹被明确篡改过比如前端指纹和服务端 UA 明显不匹配才加 15 分以上。行为数据缺失时可以降到只按 IP 频率和设备维度兜底不要一棒子打死。5.4 拦截规则被摸索并绕过现象上线一周后拦截量突然下降但垃圾内容又开始增多说明脚本绕过了检测规则。排查思路攻击者和防御者本来就是一场猫鼠游戏。当对方发现某些字段不填就能通过时蜜罐字段和特征算法都会失效。关键是不要只依赖静态规则。解决方案线上防护从第一天起就要有“规则迭代”的机制。每周把拦截日志、放行后又被举报的账号拿出来复盘看它们当时的特征向量长什么样再补充新规则。另外验证码兜底是最后一道防线即使前面的规则被绕过中高风险的账号注册完成后也必须通过邮箱激活或验证码校验才能真正使用账号这给了你二次拦截的机会。5.5 从前端直接绕过整个防护网关现象有人直接构造 HTTP 请求绕过前端页面调用注册接口所有的前端埋点、行为特征全部缺失等于 FckSignups 的前半部分全白做。排查思路这是很多初接触防护的人最大的误解——前端采集的信号再丰富也防不住“不带任何前端参数”的裸请求。裸请求的特点是没有_render_ts字段、没有行为摘要、没有设备指纹 hash所有蜜罐字段也都是空的。解决方案对这类请求服务端要把它当成“非浏览器环境”来对待。常规浏览器请求一定会携带特定的 Accept、Accept-Encoding、Accept-Language、User-Agent 等请求头构造的脚本往往丢三落四。把这些请求头的完整度打成一个分数完全缺失的请求至少加 20 分然后强制走验证码。如果注册接口的业务允许最好在网关前加一层简单的 JS 挑战确保请求确实来自可执行 JS 的浏览器环境。5.6 阈值调优的正确节奏经验来说调阈值就像拧水龙头一开始拧太猛不是漏水就是爆管。我建议的节奏是第一周阈值调到最宽松只拦截蜜罐字段命中、已知恶意 IP 这类“必错”信号。目标是零误杀。第二周把频率限制加上观察被验证码拦截的用户里最终完成注册的比例。如果这个比例低于 20%说明阈值还是太严继续放。第三周逐步把行为特征、指纹、机房 IP 权重加上每加一条规则都观察两天数据确保没有明显的误杀波动。有个量化参考正常情况下被要求验证码的用户中应该有 50% 以上最终完成注册。如果低于这个数拦截策略大概率误伤了正常用户如果高于 80%说明验证码门槛过低对脚本的威慑力不够。6. 上线之后持续看数据不要“配置完就跑”把 FckSignups 接进去之后真正的工作才开始。因为垃圾注册不是一次性攻击它会随着你的活动节奏、产品曝光量、甚至竞品的注册门槛变化而波动。我习惯每周看一次核心指标注册转化率、验证码通过率、拦截量曲线、短信费用。如果产品举办拉新活动活动开始前要把频率阈值放宽活动结束后再收回来。很多活动运营不会提前通知技术所以我会在配置里写一个“活动模式”开关运营在后台一键切换不用临时改代码。另外FckSignups 的拦截结果可以反哺产品决策。比如你会发现某些渠道投放过来的用户注册转化率特别低拉日志一看全是标记为高风险或被验证码劝退的用户。这时候不是防护策略有问题而是这个渠道的流量质量本身堪忧。类似的如果某个地区用户被拦截比例明显高于其他地区也要慎重核对是不是出口 IP 覆盖的误伤有些情况是地区和 IDC 的 IP 分配重叠导致的。再分享一个小技巧把拦截规则定时同步到邮件和短信发送链路里。很多批量注册的目的不是注册本身而是注册后触发一封欢迎邮件或一条验证短信。如果注册接口已经拦住了 90% 的垃圾请求但剩下 10% 的漏网之鱼依然能触发大量邮件和短信成本问题就没真正解决。把 FckSignups 的“风险标记”传递给下游发送服务让高风险账号的触发类消息进入延迟队列或者人工审核整个体系的成本才算真正控住。这个内容后续还可以这样扩展如果你有多条产品线可以把 FckSignups 抽成独立服务通过 HTTP 接口或者消息队列对外提供注册风险评分。这样不同产品不用重复部署统一在一个管理后台里查看全局的注册风险和拦截趋势。我目前就在往这个方向调整把原来嵌在业务里的防护逻辑逐步拆出来后续可以单独写一篇介绍拆分过程中踩过的坑。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。