ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Yakit Webfuzzer自动化测试中验证码识别插件的接入与热加载脚本实现

Yakit Webfuzzer自动化测试中验证码识别插件的接入与热加载脚本实现 1. 自动化测试卡在验证码上的真实困境做过Web端自动化测试的人大概率都经历过这样一个场景接口逻辑跑通了参数构造没问题Webfuzzer的请求包也调好了结果一发包返回的全是“验证码错误”。尤其是登录、注册、短信发送、订单提交这类关键业务接口验证码几乎是标配。你手动测的时候看一眼图填四个数字就过了但一旦交给自动化工具批量跑整个流程就直接断在这里。Yakit的Webfuzzer是我平时用得比较多的一个模块做接口fuzz、参数爆破、批量重放都很顺手。但它本身并不内置验证码识别能力遇到带图形验证码的接口要么手动填要么就得想办法把识别环节接进去。手动填显然不现实批量跑一百个请求你不可能盯着一百张图看。所以核心问题就变成了怎么在Webfuzzer的自动化流程里自动完成验证码的获取、识别和回填。这篇内容就是围绕这个问题展开的。我会把整个方案的思路、验证码识别插件的接入方式、热加载脚本的编写、以及实际跑通之后遇到的各种坑完整地梳理一遍。适合已经用过Yakit基础功能、想进一步提升自动化测试覆盖率的同学参考。如果你还没接触过Yakit建议先把它的MITM和Webfuzzer基本操作过一遍不然有些操作会跟不上。整个方案的核心逻辑其实不复杂在Webfuzzer发包之前先请求验证码接口拿到图片把图片交给识别插件处理拿到识别结果后替换到请求包里对应的字段然后再发包。听起来就三步但每一步都有细节尤其是热加载脚本的编写和验证码接口的会话保持这两个地方最容易出问题。2. 整体方案设计与核心思路拆解2.1 为什么选择验证码识别插件而不是自己训练模型验证码识别这件事自己从头训练一个模型不是不行但成本太高。你需要收集大量样本、标注、训练、调参而且验证码的样式一旦变了模型还得重新训。对于自动化测试来说目标是跑通业务流程不是做一个通用的OCR产品。所以直接用现成的验证码识别插件是性价比最高的选择。Yakit本身支持插件机制你可以把验证码识别封装成一个插件在热加载脚本里调用。插件的好处是解耦识别逻辑独立于测试逻辑换一个识别服务只需要改插件不用动Webfuzzer的配置。而且插件可以复用今天用在登录接口明天用在注册接口不用重复写代码。选插件的时候有几个点要注意。第一是识别速度如果单张图识别要两三秒批量跑的时候整体耗时会非常夸张。第二是准确率尤其是那种带干扰线、扭曲变形的验证码识别率低的话会导致大量请求因为验证码错误而失败。第三是稳定性识别服务不能动不动就超时或者挂掉。这三点直接决定了自动化测试能不能真正跑起来。2.2 Webfuzzer热加载机制的关键作用Webfuzzer的热加载功能是整个方案能落地的关键。所谓热加载就是在请求发送之前和响应返回之后插入自定义的处理逻辑。Yakit支持用Yak语言写热加载脚本你可以在beforeRequest钩子里修改请求包在afterRequest钩子里处理响应。具体到验证码场景流程是这样的在beforeRequest里先判断当前请求是否需要验证码如果需要就发起一个额外的请求去获取验证码图片调用识别插件拿到结果然后把结果写入请求包的对应字段。这样Webfuzzer在真正发包的时候请求包里已经带上了正确的验证码。这里有一个容易忽略的点获取验证码的请求和提交表单的请求必须是同一个会话。也就是说服务端生成验证码时会把答案存在session里你提交的时候必须带着同一个session的cookie否则即使识别对了服务端也会认为验证码不匹配。所以热加载脚本里要处理好cookie的传递这一点后面会详细说。2.3 方案的整体数据流把整个流程串起来看数据流是这样的Webfuzzer准备发送原始请求包热加载脚本拦截请求判断是否需要验证码如果需要向验证码接口发起GET请求获取图片数据将图片数据传给验证码识别插件插件返回识别结果通常是字符串将识别结果替换到原始请求包的验证码字段Webfuzzer发送修改后的请求包服务端校验验证码通过则继续业务逻辑这个流程里第3步和第6步是最容易出问题的。第3步涉及到会话保持第6步涉及到字段定位。字段定位不准识别结果填错了位置等于白干。会话没保持住识别对了也过不了校验。3. 验证码识别插件的接入与配置细节3.1 插件的获取与安装Yakit的插件市场里有不少现成的插件验证码识别类的也有几个。你可以直接在插件商店里搜索“验证码识别”或者“captcha”找到评分高、更新频繁的插件安装。安装之后在插件列表里能看到插件的详细说明包括支持的验证码类型、调用方式、参数说明。如果插件市场里没有合适的也可以自己写一个。Yak语言写插件不难核心就是接收图片数据调用外部识别服务或者本地识别库返回识别结果。不过对于大多数场景直接用现成插件就够了没必要重复造轮子。安装完插件后建议先单独测试一下。在Yakit的插件执行页面上传一张验证码图片看看能不能正确识别。这一步很重要如果插件本身识别就有问题后面接进Webfuzzer也是白搭。测试的时候多试几张不同类型的验证码确认插件的适用范围。3.2 插件调用的参数与返回值处理调用验证码识别插件时通常需要传入图片数据。图片数据的来源有两种一种是验证码接口直接返回图片二进制流另一种是返回base64编码的图片字符串。两种情况的处理方式不同。如果是二进制流需要先读取响应体的原始字节然后传给插件。如果是base64需要先解码成二进制再传给插件。有些插件支持直接传base64字符串有些只接受二进制这个要看插件的具体说明。插件的返回值一般是识别出来的字符串比如“3A7B”这样的验证码答案。但也有一些插件会返回一个结构化的结果包含识别结果和置信度。如果置信度太低你可以选择重试或者放弃这次请求。这个逻辑可以在热加载脚本里加一个判断置信度低于阈值就重新获取验证码。注意不同插件的调用方式可能不一样有的用plugin.Call有的用plugin.Execute具体要看插件的文档。调用之前先把插件的接口签名看清楚参数类型和返回值类型都要确认。3.3 识别失败时的重试策略验证码识别不可能百分之百准确尤其是遇到复杂验证码的时候。所以重试策略是必须的。基本的思路是如果识别结果为空或者置信度低于阈值就重新请求验证码接口重新识别最多重试N次。重试的时候要注意每次重试都要重新获取验证码不能用上一次的图片。因为验证码是一次性的服务端生成新的验证码后旧的验证码就失效了。所以重试的逻辑应该是获取新验证码 - 识别 - 判断结果 - 如果失败则循环。重试次数不宜过多一般3到5次就够了。如果5次都识别失败说明这个验证码要么太复杂要么插件不支持继续重试也是浪费时间。这时候可以考虑换一个识别插件或者调整识别参数。4. 热加载脚本编写与核心环节实现4.1 beforeRequest钩子的基本结构热加载脚本的核心是beforeRequest钩子。这个钩子在每次请求发送之前执行你可以在里面修改请求包。基本结构大概是这样beforeRequest func(req) { // 判断是否需要验证码 if (!needCaptcha(req)) { return req } // 获取验证码 captcha : getCaptcha() // 识别验证码 code : recognizeCaptcha(captcha) // 替换请求包中的验证码字段 req replaceCaptchaField(req, code) return req }这个结构看起来简单但每个函数的实现都有讲究。needCaptcha要判断当前请求是不是需要验证码的接口getCaptcha要处理会话保持recognizeCaptcha要调用插件replaceCaptchaField要准确定位字段。4.2 获取验证码并保持会话获取验证码的请求和后续提交请求必须是同一个会话这是整个流程能跑通的前提。实现方式是在获取验证码时把响应中的Set-Cookie保存下来然后在提交请求时带上这个cookie。getCaptcha func() { rsp, err : poc.Get(https://target.com/captcha, poc.timeout(5)) if err ! nil { return nil } // 保存cookie sessionCookie rsp.GetCookie() // 返回图片数据 return rsp.GetBody() }这里的关键是sessionCookie要保存到一个全局变量里后续在replaceCaptchaField或者发包时使用。Yakit的热加载脚本支持全局变量但要注意作用域确保在同一个请求周期内cookie能正确传递。如果验证码接口返回的是JSON图片数据在某个字段里还需要先解析JSON再提取图片。这个要看具体接口的返回格式不能一概而论。4.3 调用识别插件并处理结果调用插件的方式取决于插件的类型。如果是Yakit内置的插件可以用plugin相关的API调用。如果是外部服务可能需要用http请求的方式调用。recognizeCaptcha func(imgData) { result, err : plugin.Call(captcha-recognizer, { image: imgData }) if err ! nil { return } return result.Data }调用插件时要注意参数格式。有些插件要求传base64有些要求传二进制有些要求传文件路径。传错了插件会报错或者返回空结果。建议先在插件测试页面确认参数格式再写到脚本里。识别结果拿到之后还要做一下清洗。有些插件返回的结果会带空格或者换行需要trim一下。有些返回的是JSON字符串需要解析后取字段。这些细节处理不好会导致替换到请求包里的验证码带上了多余字符服务端校验不通过。4.4 替换请求包中的验证码字段替换字段是最容易出错的一步。请求包里的验证码字段可能出现在URL参数、POST body、JSON body、Header里位置不同替换方式也不同。如果是POST表单验证码字段通常是captchaxxxx这样的形式可以用正则替换replaceCaptchaField func(req, code) { body : req.GetBody() newBody : re.ReplaceAllString(body, captcha[^]*, captchacode) req.SetBody(newBody) return req }如果是JSON body需要先解析JSON修改字段再序列化回去。如果是URL参数需要修改URL。不同位置的处理方式不同写脚本的时候要先把请求包的结构看清楚。提示替换之前先把原始请求包打印出来看看确认验证码字段的确切位置和格式。不要凭猜测写正则很容易匹配错。4.5 完整脚本的组装与调试把上面几个部分组装起来就是一个完整的验证码识别热加载脚本。组装完之后不要直接上批量任务先用单个请求测试。在Webfuzzer里发一个请求看看热加载脚本有没有正确执行验证码有没有被替换服务端返回是不是成功。调试的时候可以在脚本里加日志输出把获取到的验证码、识别结果、替换后的请求包都打印出来。Yakit的控制台能看到这些日志方便定位问题。确认单个请求能跑通之后再逐步增加并发和批量数量。5. 常见问题与排查技巧实录5.1 验证码识别正确但服务端仍然报错这是最常见的问题原因通常有三个会话没保持住、验证码字段替换位置不对、验证码被重复使用。会话问题最好排查把获取验证码请求和提交请求的cookie打印出来对比一下看看是不是同一个session。如果不是检查cookie的保存和传递逻辑。字段替换问题把替换后的请求包和服务端期望的格式对比一下。有时候是字段名写错了有时候是编码问题有时候是多了空格。验证码重复使用的问题比较隐蔽。有些服务端在验证码校验失败后不会立即失效验证码但有些会。如果你的重试逻辑里用了同一个验证码第二次肯定失败。所以每次重试都要重新获取验证码。5.2 识别速度慢导致整体测试效率低验证码识别本身需要时间如果单张图识别要两秒一百个请求就是两百秒这个耗时在批量测试里是不可接受的。优化方向有几个一是选择识别速度快的插件有些插件用的是轻量级模型识别速度快很多。二是减少不必要的识别比如有些接口在特定条件下不需要验证码可以在脚本里加判断跳过这些请求。三是并发处理如果Yakit支持并发发送请求可以把验证码识别也做成并发的。不过并发要注意验证码接口通常有频率限制并发太高会被封。所以并发数要控制不能无限制地开。5.3 插件调用报错或返回空结果插件调用报错的原因很多常见的有参数格式不对、插件未正确安装、插件依赖的服务不可用、图片数据为空。排查的时候先确认图片数据是不是真的拿到了。在脚本里把图片数据的大小打印出来如果是0或者很小说明获取验证码的请求本身就有问题。如果图片数据正常再检查插件调用的参数格式对照插件文档确认。如果插件依赖外部服务还要确认服务是不是正常运行。有些识别服务需要API keykey过期了也会导致调用失败。5.4 常见问题速查表问题现象可能原因排查方向解决方式识别正确但服务端报错会话不一致对比cookie确保获取和提交用同一session识别正确但服务端报错字段替换错误检查请求包确认字段位置和格式识别正确但服务端报错验证码重复使用检查重试逻辑每次重试重新获取验证码识别速度慢插件性能差测试单张耗时换轻量级插件或减少识别次数插件返回空参数格式错误对照插件文档确认参数类型和格式插件返回空图片数据为空打印数据大小检查获取验证码的请求插件调用报错服务不可用检查服务状态确认API key和服务运行状态5.5 几个实操中踩过的坑第一个坑是cookie的作用域。Yakit的热加载脚本里全局变量的作用域有时候和预期不一样。我在一个脚本里把cookie存到全局变量结果在另一个请求里读不到。后来改成用session对象来管理cookie问题才解决。第二个坑是验证码字段的编码。有些服务端要求验证码字段是URL编码的有些要求是原始字符串。替换的时候如果编码方式不对服务端解析出来的验证码就是错的。这个要在实际测试中确认不能想当然。第三个坑是插件的并发限制。有些识别插件不支持并发调用同时发多个请求会报错。如果要做并发测试要么换支持并发的插件要么在脚本里加锁保证同一时间只有一个识别请求。第四个坑是验证码的有效期。有些验证码生成后几分钟内有效如果获取验证码和提交请求之间间隔太长验证码就过期了。所以在脚本里要尽量缩短这两个操作之间的时间不要在中间做太多耗时的处理。6. 方案扩展与进阶优化方向6.1 支持多种验证码类型的自动切换实际测试中遇到的验证码不止一种有纯数字的、字母数字混合的、算数题的、滑块拼图的。如果只用一个插件可能只能处理其中一种。进阶的做法是在脚本里加一个判断逻辑根据验证码的类型自动选择对应的识别插件。判断验证码类型的方式可以是看图片的特征比如尺寸、颜色分布也可以是看接口返回的字段。有些接口会在返回里标明验证码类型直接读这个字段就行。如果没有标明就需要通过图片分析来判断这个实现起来复杂一些但也不是做不到。6.2 识别结果的缓存与复用如果同一个验证码在多个请求里使用可以考虑缓存识别结果避免重复识别。不过这种情况比较少因为验证码通常是一次性的。但在某些测试场景下比如同一个session下的多个接口都需要验证码且验证码是同一个这时候缓存就有用了。缓存的key可以用验证码图片的hash如果图片相同直接返回缓存的结果。这样可以减少识别次数提升效率。但要注意缓存的有效期验证码过期后缓存也要失效。6.3 与CI/CD流程的集成如果自动化测试是CI/CD流程的一部分验证码识别脚本也需要集成进去。集成的时候要注意几点识别服务的可用性要有保障不能因为识别服务挂了导致整个CI流程失败识别失败要有降级策略比如跳过需要验证码的用例或者标记为待人工验证日志要完整方便出问题的时候排查。集成方式取决于CI工具如果是Jenkins可以把Yakit的测试脚本封装成一个命令行任务在Jenkins里调用。如果是GitLab CI可以写一个.gitlab-ci.yml在script阶段执行Yakit命令。6.4 识别准确率的持续监控验证码识别准确率不是一成不变的服务端可能会更新验证码样式导致原来的插件识别率下降。所以需要持续监控识别准确率发现下降及时调整。监控的方式可以是在脚本里记录每次识别的结果和服务端的校验结果定期统计准确率。如果准确率低于阈值就触发告警提醒更换插件或调整参数。这个监控机制在长期运行的自动化测试里很有价值可以避免因为验证码识别问题导致测试结果不可靠。7. 一些个人体会这套方案我从开始折腾到真正跑通大概花了两个晚上。第一个晚上主要卡在会话保持上获取验证码和提交请求用的不是同一个session导致识别对了也过不了。后来把cookie的传递逻辑理清楚问题就解决了。第二个晚上在调识别插件的参数不同插件对图片格式的要求不一样试了好几个才找到合适的。实际跑起来之后效果还是比较满意的。原来手动填验证码一百个请求要花十几分钟现在自动化跑两三分钟就完成了。虽然识别不是百分之百准确但配合重试策略整体成功率能到95%以上对于自动化测试来说够用了。如果你也在做类似的自动化测试我的建议是先把单个请求跑通确认验证码能正确获取、识别、替换、提交再上批量。不要一上来就搞并发出了问题很难定位。另外识别插件的选择很重要多试几个找到适合你目标站点验证码类型的那一个。
返回列表