滑块验证码攻防全拆解:从缺口识别到拟人轨迹与风控对抗
上周朋友找我调一个UI自动化脚本卡在南航登录环节的滑块验证上回放录屏一看每次都是拖到一半被判定异常然后提示重新验证。索性把这一套滑块验证从前端交互到服务端校验逻辑完整拆了一遍。南航这种滑块验证方案和市面主流的人机验证产品在架构上同根同源都是前端采集轨迹 服务端行为判定的模式。把它当作样本分析缺口识别、轨迹构造、风控检测这些核心算法也就一并理清了。这篇文章把整个拆解过程整理出来从图像处理到行为模拟再到服务端风控视角的对抗逻辑适合做自动化测试、前端风控、安全评估的同行参考。我尽量把原理讲透代码能直接复用的也会标注清楚。1. 滑块验证的整体架构与南航场景拆解1.1 一次完整滑块验证的请求链路滑块验证看起来只是拖动小图到缺口一个动作背后其实是三段式链路配合第一步前端向后端发起验证码初始化请求服务端返回两张图一张带缺口的背景图一张滑块小图同时下发一个唯一标识这次会话的 token。这张背景图里的缺口位置是服务端随机生成的理论上只有服务端知道精确坐标。第二步用户在浏览器里拖动滑块前端 JavaScript 实时监听鼠标或触屏的移动事件记录一系列轨迹点每个点包含 x 坐标、y 坐标和时间戳。松手后前端把这些轨迹数据连同设备环境信息一起加密作为验证请求提交。第三步服务端拿到轨迹数据后用一套风控模型来判断这是真人还是程序。判断通过则下发通过凭证前端用这个凭证继续业务请求判断不通过则重新出题。这个链路最关键的一点是服务端根本不关心你到底拖得准不准它关心的是你拖的这个过程像不像人。缺口位置识别得越准轨迹模拟得越拟真通过率才越高。这也决定了滑块算法分析的核心工作集中在两块图像缺口定位和行为轨迹构造。1.2 南航场景的特殊性为什么这块滑块值得单独分析南航的滑块验证有几个特点让它在同类方案里很有代表性也让它比其他网站的滑块更值得拆。第一个特点是图片质量高、背景复杂。南航背景图通常选用飞机、机场、风景插画这类素材颜色丰富、纹理细节多这和很多网站用纯色渐变背景不一样。纹理复杂直接导致图像缺口识别难度上升边缘误检率比纯色背景高出一截。第二个特点是交互控件是移动端优先设计。南航的滑块既支持手机触屏滑动也支持 PC 鼠标拖动两种输入方式的行为特征差异很大——触屏拖动没有鼠标悬停抖动PC 拖动有更明显的小幅修正。做轨迹模拟时必须区分设别类型生成不同特征的轨迹。第三个特点是业务场景触发频率高、环境要求严。登录、购票、查询会员信息都有可能触发验证而且南航对 IP 和账号环境比较敏感。同一个 IP 短时间内频繁触发验证风控系统会直接提高验证难度表现为背景图干扰加重、可拖拽时间缩短。分析这类滑块验证不能只盯着一套固定的缺口识别和轨迹生成方案还得考虑不同终端、不同网络环境、不同触发频率下的动态变化。这也是我把这三次核心算法分开讲的原因——每一块都能独立优化也都有自己独立的坑。2. 核心算法模块一缺口识别与图像处理2.1 缺口识别的三种技术路线对比想拖动滑块先得知道往哪拖所以缺口识别是整套流程里的第一个技术关卡。目前主流方案有三类第一类是传统模板匹配。把滑块小图当作模板在背景大图上搜索最相似的区域。这种方案实现简单、不需要训练数据几分钟就能跑通。缺点是遇到复杂背景容易误匹配而且要求滑块小图的轮廓和背景里的缺口形状高度一致。南航这种插画风格的背景纯模板匹配经常会匹配到云朵、机身弧线这些相似纹理上。第二类是边缘检测 轮廓匹配。先用 Canny 或 Sobel 算子提取背景图和滑块图的边缘信息再做模板匹配或轮廓比对。因为边缘信息去掉了颜色干扰只保留结构轮廓对复杂背景的抗干扰能力明显强于直接像素匹配。这也是我实际项目里用的主力方案。第三类是深度学习目标检测。用目标检测或图像分割模型直接回归缺口框的位置常见做法是 YOLO 系列加少量标注数据训练或者用 U-Net 做像素级分割。深度学习方案泛化能力最强遇到已见过的背景风格几乎能做到像素级精准但缺点是需要收集样本、标注数据、训练模型工程成本比前两类高一个量级。我个人在分析南航这类验证码时的建议是先走边缘检测 模板匹配的路线把流程跑通看识别精度瓶颈在哪再决定要不要上深度学习。大部分场景下传统视觉方案调好了精度完全够用没必要一上来就上模型。2.2 边缘检测与模板匹配的工程实现下面是一套可运行的缺口定位实现用 Python OpenCV 写核心步骤就四步预处理、边缘提取、模板匹配、坐标换算。import cv2 import numpy as np def locate_gap(bg_path, fg_path): # 背景图预处理转灰度、高斯模糊降噪 bg cv2.imread(bg_path) bg_gray cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY) bg_blur cv2.GaussianBlur(bg_gray, (5, 5), 0) # 滑块图处理优先用alpha通道轮廓更干净 fg cv2.imread(fg_path, cv2.IMREAD_UNCHANGED) if fg.shape[2] 4: alpha fg[:, :, 3] fg_blur cv2.GaussianBlur(alpha, (3, 3), 0) _, fg_mask cv2.threshold(fg_blur, 127, 255, cv2.THRESH_BINARY) else: fg_gray cv2.cvtColor(fg, cv2.COLOR_BGR2GRAY) _, fg_mask cv2.threshold(fg_gray, 127, 255, cv2.THRESH_BINARY) # Canny边缘检测 bg_edge cv2.Canny(bg_blur, 100, 200) fg_edge cv2.Canny(fg_mask, 100, 200) # 模板匹配用滑动窗口计算相似度 result cv2.matchTemplate(bg_edge, fg_edge, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(result) # max_loc是滑块图左上角在背景图中的坐标 return max_loc[0], max_val这里有三个细节值得展开。第一为什么先做高斯模糊。背景图经过服务端压缩会有 JPEG 压缩噪声如果不做模糊直接提边缘会出现大量细碎的假边缘。但模糊核也不能太大否则边缘位置会偏移。我实测背景图是 680 像素宽时高斯核用 5x5 比较合适再大会把缺口边界磨掉一两个像素。第二宁可多算两步把滑块图的轮廓做干净。滑块小图通常带透明通道直接用 RGB 像素做模板匹配会把透明区域的黑色底也算进去导致匹配结果严重偏差。用 alpha 通道提取不透明区域轮廓是处理滑块模板的关键技巧很多第一次写识别脚本的人都会漏掉这一步。第三模板匹配返回的是像素级坐标。不同网站的滑块容器布局差异很大小图左侧可能有一段透明留白实际滑动距离不等于模板匹配的 x 坐标。做坐标换算时要看前端代码里滑块初始位置和背景图左边界的关系通常需要减去一个固定偏移量。分析南航的页面时我花了些时间在控制台里核对滑块小图的 CSS 位置最后确认需要减去初始 left 偏移量才能得到真实拖动距离。2.3 识别误差分析与精度调优缺口识别不是拖过去就行误差太大会直接触发风控的位置校验试几次就会被拦截。风控系统会计算最终落点与真实缺口的距离真人拖动通常有 1 到 3 像素的随机误差误差为 0 反而像机器。所以识别模块的两个优化方向一是提高识别基准值的准确度二是把识别值加上随机偏移再交给轨迹生成模块。工程调优时我总结了几条实用经验多尺度匹配背景图在不同屏幕分辨率下会被 CSS 缩放宽度可能不是原始像素宽度。先读前端代码里背景图的实际渲染宽高对模板做等比缩放否则误差可能达到几十像素。双阈值 Canny 的自适应固定用 100 和 200 在某种背景下效果好换到另一种背景效果可能很差。可以先用 Otsu 二值化找梯度分布再动态设置 Canny 阈值这类自适应调整能明显降低复杂插画背景的误检率。置信度判断matchTemplate 返回的 max_val 可以当作置信度。如果低于 0.5说明当前模板匹配结果不可靠直接重新加载验证码不要硬识别。这个放弃重试策略比强行调参省时间得多。3. 核心算法模块二拟人轨迹的构造原理3.1 机器轨迹和人类轨迹的本质差异缺口定位解决的是往哪拖轨迹生成解决的是怎么拖。服务端风控对轨迹的检测本质上是一个二分类问题把轨迹特征输入模型判断是真人操作还是机器生成的。真人和机器生成的轨迹差异体现在三个层面。第一层是速度曲线。真人拖动不是匀速的而是慢启动—加速—减速—微调的过程。手指或鼠标从静止到运动加速度是渐变的不会瞬间爆发到高速。而最简单的机器轨迹是匀速直线速度曲线是一条水平线这在风控模型里属于一眼假的特征。第二层是时间序列的规律性。真人拖动的时间间隔不是完全均匀的两次采样之间可能有 10ms 到 50ms 的波动。机器的定时器触发则非常均匀间隔几乎恒定频域分析下会出现明显的周期峰。第三层是噪声和抖动。真人手部控制有天然的生理抖动哪怕意图保持直线轨迹也不会是完美直线会有几个像素的横向偏移和方向修正。机器轨迹往往要么过于干净平滑要么随机噪声幅度过大、频率过高这两种极端都容易被识别。理解了这三层差异轨迹生成算法的思路就清晰了构造一条速度曲线符合物理规律、时间序列有自然波动、空间位置带适度噪声的轨迹。3.2 轨迹生成器的设计与实现实际工程里我倾向于用三次缓出曲线 随机噪声的组合方案。缓出曲线模拟了开始慢、中间快、结束慢的物理过程随机噪声模拟了手部抖动和修正行为。import numpy as np import random def generate_track(distance): # 人类观察缺口到完成拖动通常需要0.8到1.6秒 duration random.uniform(0.8, 1.6) # 以20ms为采样间隔 steps int(duration * 50) # 三次缓出曲线1 - (1-t)^3 t np.linspace(0, 1, steps) eased 1 - np.power(1 - t, 3) # 横向位移 缓出曲线 * 目标距离 正态抖动 track_x eased * distance # 手部生理抖动幅度0.4个像素左右频率较高 jitter np.random.normal(0, 0.4, steps) track_x track_x jitter # 最后的对齐阶段真正拖动结束时会有小幅修正 track_x[-1] distance track_x[-2] distance random.uniform(-1.2, 0) # 纵向位移拖动不是完美水平线加入小幅度漂移 track_y np.random.normal(0, 1.0, steps) track_y[-1] 0 track [] for i in range(steps): track.append({ x: round(float(track_x[i]), 2), y: round(float(track_y[i]), 2), t: i * 20 }) return track这套生成器有几个关键的随机项我逐个解释。时间总长设定在 0.8 到 1.6 秒之间这是真人完成一次滑块拖动的常见区间。低于 0.5 秒太快超过 2.5 秒又显得犹豫都会被模型标记。三次缓出曲线的物理含义是后半段的位移增量逐渐减少对应手指接近目标时减速。这个模型比一次函数匀速拖动要真实得多也比二、四次曲线的生硬加速更接近手指运动。正态抖动幅度是关键参数。我试过 0.2 到 1.5 像素的不同幅度最后落在 0.4 最好——太小轨迹过于平滑太大则显得手在剧烈颤抖。幅度应结合滑块宽度调节南航这类 300 像素宽的滑块0.4 到 0.8 都算合理区间。终点修正模拟了第一下拖过头或没到位再微调一下的动作。真人对准缺口很少一步到位通常误差 1、2 个像素甚至会有往回拖几像素的修正。机械生成的轨迹为了准确往往落点误差为 0这反而成了破绽。3.3 轨迹质量的自检方法生成轨迹之后别急着提交验证先看一眼轨迹曲线是否符合人类特征。我平时会做三个快速检查一是画位移-时间图曲线应该是平滑的 S 形而不是直线或锯齿。如果某一段出现了平台期说明轨迹疑似中途停滞真人拖动不会完全静止再突变。二是算速度曲线速度应该先升后降峰值出现在拖动中段。如果速度曲线有多个峰值且幅度大说明轨迹充满反复修正这种轨迹在真人的快速拖动场景里不常见。三是检查落点误差最终落点应该和缺口位置有 1 到 3 像素的随机误差。每次提交都精确命中缺口中心的轨迹风控模型会给出很高的人为标记优先级。另外一个容易被忽略的质量指标是轨迹点数量。真人拖动每秒采样的点数受限于硬件上报频率鼠标和触屏常见是 60 到 120Hz对应每 20ms 一个点左右。如果生成的轨迹点密集到每 5ms 一个点那就太不正常了。所以我生成轨迹时固定用 20ms 间隔这也和多数浏览器事件监听的实际频率对上。4. 核心算法模块三服务端风控究竟在检测什么4.1 轨迹时序特征的校验维度很多人模拟轨迹时只看轮廓像不像忽略了一个关键问题服务端风控不是看一眼轨迹曲线就放行的它会从时序数据里提取几十个特征做综合打分。我在分析南航验证交互时重点梳理了服务端通常会关注的几个维度。第一个维度是速度的连续性和平滑度。真人拖动时人体肌肉受惯性约束速度变化是平滑过渡的不会出现从 0 瞬间跳到最大速度的突变。对速度序列做一阶差分人机差异非常显著。第二个维度是加速度分布特征。真人在拖动开始阶段加速度较大接近目标时减速并且加速度为负整个过程加速度曲线有一个明显的正负交替。而很多简单轨迹生成器只控制速度机械累加位移加速度分布和真人差异很大。第三个维度是轨迹的贝塞尔拟合误差。这是比较高级的特征风控系统会把轨迹点序列拟合成贝塞尔曲线然后计算原始点与拟合曲线的偏差。真人手部操作有随机波动偏差分布比较自然机器生成的轨迹往往过于贴合某种数学曲线拟合误差过小反而暴露。这些维度告诉我们的核心结论是模拟轨迹不能只追求位移准确更要关注时序特征的统计分布。这也是为什么数据驱动的轨迹方案比手工调参的方案效果更好——先从真实用户操作中采集大量轨迹样本统计特征分布再按分布规律生成新轨迹这种方案被识别出来的概率会显著下降。4.2 环境指纹与行为上下文轨迹本身只是风控判定的一个维度真正被标记的高危信号往往来自环境指纹和行为上下文这是做滑块算法分析时容易忽视的一层。环境指纹包括浏览器指纹、canvas 指纹、WebGL 渲染指纹、字体指纹、时间区、屏幕分辨率、语言设置等。自动化工具如果用了默认配置这些指纹之间会存在逻辑矛盾比如浏览器语言是中文但时区是 UTC这种组合在真人环境里几乎不会出现。风控系统通过对比指纹一致性能快速识别自动化环境。行为上下文指的是这次滑块验证之前的用户行为序列。真人登录南航 App通常会从打开网页或 App 开始经过页面浏览、输入账号密码等一系列行为才会触发滑块验证。而自动化脚本往往直接请求验证接口之前没有任何页面行为历史。这种无上下文行为本身就是极高危信号。所以在做自动化测试或安全评估时要意识到一个事实即使轨迹模拟得完美环境指纹和行为上下文仍然可能暴露自动化身份。这也是很多脚本通过率上不去的深层原因。4.3 理解风控的最终目的防御视角分析到这里有必要把视角转回防御端理解这些检测算法不是为了绕过这套体系而是反过来帮助我们设计更稳固的风控策略。我在做安全评估时会拿滑块验证做攻防演练按三个层次检验验证码强度第一层看缺口识别难度如果图像处理几步就能精准定位缺口说明图片干扰设计不够第二层看轨迹模型识别率如果随机生成的轨迹能拿到高通过率说明行为模型需要强化第三层看整体链路校验如果绕过轨迹直接构造请求也能通过说明服务端缺少轨迹一致性校验。这种攻防视角对整个系统安全是有价值的。对风控团队来说只有站在攻击者角度了解算法是怎么拆解验证码的才能针对性地加固比如增加干扰线、动态调整缺口位置、增加轨迹特征维度。对自动化测试团队来说理解这些机制能帮助搭建更稳定的测试方案避免因验证码误判导致测试随机失败。5. 工程化落地与常见问题实录5.1 自动化测试中的落地姿势把滑块算法分析应用到 UI 自动化测试通常要串起一整条链路不只是调用一个缺口识别函数。我搭的自动化框架里处理滑块验证的流程是等待验证码 iframe 出现确认验证码类型获取背景图和滑块图地址下载到本地按第 2 节的算法定位缺口坐标生成拟人轨迹按轨迹模拟鼠标拖动等待验证结果成功则继续业务操作失败则刷新重试。这里有一个工程化细节值得注意必须处理验证码 iframe 的切换。南航的滑块验证码嵌在 iframe 里Selenium 或 Playwright 操作前要先切换上下文否则元素定位会一直失败。很多同学在环境上浪费大量时间最后发现是 iframe 切换的问题。另一个细节是等待策略。滑块图片是异步加载的如果在图片资源还没加载完成时就开始识别拿到的图片可能是空白或半加载状态。我通常用显式等待等到 canvas 元素出现且图片高度大于某个阈值再进入识别流程。5.2 高频问题排查表实操过程中遇到的坑我整理成了一张排查表遇到问题可以直接对照。症状可能原因排查与解决方向缺口定位明显偏左或偏右滑块图有透明留白坐标换算没做偏移修正核对前端代码中滑块容器和背景图的定位关系修正偏移常量识别结果不稳定成功率忽高忽低背景图纹理复杂Canny 阈值固定导致误检换成动态阈值对识别结果做多帧验证置信度低时重试轨迹看起来很像但提交总是失败落点误差为 0或轨迹时间总长不合理给终点加随机误差检查时长是否落在 0.8 到 1.6 秒区间验证码频繁刷新同 IP 或同一环境触发频率过高被风控标记控制并发和重试频率必要时换干净的环境验证鼠标拖动事件没有触发自动化工具没有正确切换到 iframe检查上下文切换确认 iframe 加载完成后再操作这个表还可以继续扩展但大部分问题归根结底是三类坐标换算不准、轨迹不拟真、环境被标记。按这三个方向排查能解决九成的失败问题。5.3 几个容易被忽视的坑最后分享三个我在实战里踩过、但很少被人提起的坑。第一个是截图分辨率与显示缩放。Windows 系统如果设置 125% 或 150% 的显示缩放自动化工具拿到的页面元素坐标和实际截图像素坐标会不一致导致识别出的缺口位置映射到鼠标移动坐标时整体偏移。解决方法是先统一坐标系要么全部在截图坐标系里计算再按缩放比例换算成屏幕坐标要么直接用 CDP 的 DeviceMetricsOverride 覆盖缩放。第二个是滑块拖动时不能只移动 x 轴。我之前写过一个简化版本轨迹生成完美但 y 轴完全不动是条纯水平线。真人的手在水平拖动时y 轴方向必然有 4 到 6 个像素的自然浮动。纯水平线在风控模型里是极度可疑的特征。第三个是重试会累积风险。很多脚本的默认策略是失败就立刻重试连续失败几十次也在短时间内完成。这在风控系统看来是典型的自动化特征。更合理的做法是失败后随机等待 3 到 8 秒再重试连续失败超过 5 次就停止等待一段时间或换环境后再继续。6. 写在最后的实操体会整套滑块算法分析做下来我最深的体会是滑块验证的本质是一场行为特征的概率博弈而不是一场图像识别的精确对抗。缺口识别哪怕误差两三个像素轨迹模拟得当照样能过反过来缺口识别再完美轨迹和环境的任何一个细节暴露自动化特征照样被拦截。如果让我给刚开始接触这块的同行一个建议那就是别上来就钻图像算法的牛角尖。先把整条链路跑通把缺口识别精度控制在 5 到 10 像素内把轨迹模拟做到速度曲线符合物理直觉、落点有自然误差、时间总长在合理范围这时候大概率已经能解决日常自动化测试的需求了。之后再根据实际被拦截的特征反馈回头优化对应模块这样走一圈后体验会扎实很多。再分享一个小技巧调试轨迹模拟时可以先在浏览器控制台里手动拖几次滑块把 console 里打印的轨迹点记录下来和自己的生成结果做对比。多对比几组真实轨迹和生成轨迹的差异比看十篇理论分析都管用。真实数据永远是最标准的参照物。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →