☰
Yakit Webfuzzer自动化测试中验证码识别插件的接入与热加载脚本实现
2026/9/29 19:18:01 网站建设 项目流程

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 方案的整体数据流

把整个流程串起来看,数据流是这样的:

  1. Webfuzzer准备发送原始请求包
  2. 热加载脚本拦截请求,判断是否需要验证码
  3. 如果需要,向验证码接口发起GET请求,获取图片数据
  4. 将图片数据传给验证码识别插件
  5. 插件返回识别结果(通常是字符串)
  6. 将识别结果替换到原始请求包的验证码字段
  7. Webfuzzer发送修改后的请求包
  8. 服务端校验验证码,通过则继续业务逻辑

这个流程里,第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表单,验证码字段通常是captcha=xxxx这样的形式,可以用正则替换:

replaceCaptchaField = func(req, code) { body := req.GetBody() newBody := re.ReplaceAllString(body, "captcha=[^&]*", "captcha="+code) 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 key,key过期了也会导致调用失败。

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%以上,对于自动化测试来说够用了。

如果你也在做类似的自动化测试,我的建议是先把单个请求跑通,确认验证码能正确获取、识别、替换、提交,再上批量。不要一上来就搞并发,出了问题很难定位。另外,识别插件的选择很重要,多试几个,找到适合你目标站点验证码类型的那一个。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询