点击蓝字 关注我们
日期:2025年04月01日
作者:YuKong
介绍:配合yakit热加载实现对图形验证码的识别绕过。
0x00 前言
之前使用Burpsuite
一直在用算命瞎子写的图形验证码识别工具。后来不小心把Burpsuite
删了,也懒得折腾索性改用了国产工具yakit
。接下里的内容就是基于算命瞎子写的图形验证码识别脚本的二次开发以及yakit
热加载的使用。
0x01 前置知识
1.1 yakit热加载
热加载中存在两个特殊的魔术方法即beforeRequest
和afterRequest
,顾名思义就是在请求之前或请求之后调用该函数。我们这里的思路就是在登录请求发出之前,调用beforeRequest
函数请求获得图形验证码并且发送到验证码识别工具,获取识别后的结果。最后将识别后的结果加载到登录请求中,从而实现图形验证码的绕过。
https://www.yaklang.io/products/Web%20Fuzzer/fuzz-hotpatch/
1.2 图形验证码识别
算命瞎子写的脚本:
xp_CAPTCHA 白嫖版:
https://github.com/smxiazi/NEW_xp_CAPTCHA
xp_CAPTCHA API版:
https://github.com/smxiazi/xp_CAPTCHA
0x02 工具开发
2.1 图形验证码识别
主要代码如下:
(1)选择POST
传入两个参数如果是模式1
或者模式2
则使用python
的第三方库ddddocr
进行识别。如果是模式3
,则使用API
接口进行识别,我这里用的是图鉴(http://www.ttshitu.com/)。
模式1
:
try:
if self.path != '/recognize':
self.send_error(404, "Page not Found!")
return
img_name = f"temp/{time.time()}.png"
req_datas = self.rfile.read(int(self.headers['content-length'])).decode()
json_req_datas = {k: v[0] for k, v in parse_qs(req_datas).items()}
mode = json_req_datas.get("mode", "1")
image_url = json_req_datas.get("image_url", "")
image_base64 = json_req_datas.get("image_base64", "")
image_base64 = image_base64.replace(" ", "+")
# print(image_base64)
if mode == "1": # 本地 ddddocr 识别 - 通过 URL
response = requests.get(image_url, timeout=5, verify=False)
if response.status_code != 200:
self.send_error(400, "无法下载验证码")
return
with open(img_name, 'wb') as f:
f.write(response.content)
text = ocr.classification(response.content)
模式2
:
elif mode == "2": # 本地 ddddocr 识别 - 通过 Base64
image_data = base64.b64decode(image_base64)
with open(img_name, 'wb') as f:
f.write(image_data)
text = ocr.classification(image_data)
模式3
:
def recognize_captcha_api(image_url, image_path):
""" 按照 `xp_CAPTCHA.py` 处理模式 3,调用 `http://api.ttshitu.com/predict` 进行识别 """
try:
# 下载验证码图片
response = requests.get(image_url, timeout=5, verify=False)
if response.status_code != 200:
print(f"[ERROR] 无法下载验证码图片: {image_url}")
return "0000"
# 保存到本地
with open(image_path, 'wb') as f:
f.write(response.content)
# 转换为 Base64
image_base64 = base64.b64encode(response.content).decode("utf-8")
# 调用 API 识别,需要填入自己注册的账号密码
payload = {
"username": TTSHITU_USERNAME,
"password": TTSHITU_PASSWORD,
"typeid": "3",
"image": image_base64
}
api_response = requests.post(TTSHITU_API_URL, json=payload)
result = api_response.json()
if result.get("success"):
return result["data"]["result"]
else:
print(f"[ERROR] 识别失败: {result.get('message', '未知错误')}")
return "0000"
except Exception as e:
print(f"[ERROR] 请求验证码识别接口失败: {e}")
return "0000"
elif mode == "3": # 远程 API 识别 - 通过 URL
text = recognize_captcha_api(image_url, img_name)
(2)同时使用前端页面记录验证码识别日志:
class Resquest(BaseHTTPRequestHandler):
""" 处理 HTTP 请求 """
def do_GET(self):
""" 提供前端网页 UI """
if self.path != '/':
self.send_error(404, "Page not Found!")
return
with open('temp/log.txt', 'r') as f:
content = f.read()
html_content = f"""
<html>
<head>
<title>验证码识别</title>
<style>
body {{ text-align: center; font-family: Arial, sans-serif; }}
table {{ border-collapse: collapse; width: 80%; margin: auto; }}
th, td {{ border: 1px solid black; padding: 10px; text-align: center; }}
th {{ background-color: #f2f2f2; }}
input, button {{ padding: 10px; margin: 5px; }}
</style>
<script>
function sendCaptchaRequest(mode) {{
var requestData = {{}};
if (mode === 1 || mode === 3) {{
requestData = {{
mode: mode.toString(),
image_url: document.getElementById("image_url").value
}};
}} else if (mode === 2) {{
requestData = {{
mode: "2",
image_base64: document.getElementById("image_base64").value
}};
}}
fetch("/recognize", {{
method: "POST",
headers: {{
"Content-Type": "application/x-www-form-urlencoded"
}},
body: new URLSearchParams(requestData).toString()
}})
.then(response => response.text())
.then(data => {{
document.getElementById("result").innerHTML = "识别结果: " + data;
}})
.catch(error => {{
console.error("请求错误:", error);
}});
}}
</script>
</head>
<body>
<h1>验证码识别服务</h1>
<h2>模式 1 - 本地识别 (ddddocr, 图片 URL)</h2>
<inputtype="text"id="image_url"placeholder="输入验证码图片 URL">
<buttononclick="sendCaptchaRequest(1)">提交</button>
<h2>模式 2 - 本地识别 (ddddocr, Base64)</h2>
<textareaid="image_base64"rows="5"cols="50"placeholder="输入图片 Base64"></textarea>
<buttononclick="sendCaptchaRequest(2)">提交</button>
<h2>模式 3 - 远程 API 识别 (API, 图片 URL)</h2>
<buttononclick="sendCaptchaRequest(3)">调用 API 识别</button>
<pid="result"></p>
<h2>识别记录</h2>
<table>
<tr>
<th>验证码</th><th>识别结果</th><th>时间</th><th>模式</th>
</tr>
{content}
</table>
</body>
</html>
"""
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=UTF-8')
self.end_headers()
self.wfile.write(html_content.encode())
经过长时间使用下来发现还是花钱的准确率更高啊。实现效果如下:
2.2 yakit热加载
因为热加载的局限性(我太菜了),所以选择使用两种热加载脚本分别传输模式1
,2
和模式3
。
传输模式1
、2
代码:
beforeRequest = func(req) {
// 发送 HTTP 请求并接收响应
header=`GET /xxx/getImage?_=1741665974000 HTTP/1.1
Host: xxx.xxx.xxx`
imgPacketRsp, err = poc.HTTP(header, poc.https(true))~
// ====== 声明变量 ======
b64Img = "" // 用于存储验证码的 Base64 编码
mode = 2 // 用于指示识别模式
// 获取响应头中的 Content-Type
contentType = poc.GetHTTPPacketHeader(imgPacketRsp, "Content-Type")
// 判断响应类型
if re.Find(contentType, "^[a-z]+")=="image" {
// 响应为图片类型
imgData = poc.GetHTTPPacketBody(imgPacketRsp)
b64Img = codec.EncodeBase64(imgData)
mode = 2 // 模式 1
} else {
// 响应为 JSON,包含 Base64 编码的图片数据
b64Img = poc.GetHTTPPacketBody(imgPacketRsp)
if b64Img == "" {
print("未找到 img_data")
// return req
}
mode = 2 // 模式 2
}
// ====== 构造 OCR 请求 ======
ocrReq = f"mode="+mode + "&image_base64="+b64Img
ocrPacket = f"POST /recognize HTTP/1.1rn" +
f"Host: 127.0.0.1:8899rn" +
"Content-Type: application/x-www-form-urlencodedrn" +
f"Content-Length: {len(ocrReq)}rnrn" +
ocrReq
// ====== 调用 OCR 服务 ======
ocrRsp, err = poc.HTTP(ocrPacket)~
ocrResult = string(poc.GetHTTPPacketBody(ocrRsp))
// ====== 替换占位符 ======
req = re.ReplaceAll(req, "__ocr__", ocrResult)
return req
}
传输模式3
代码:
beforeRequest = func(req) {
// ====== 构造 OCR 请求 ======
ocrReq = f"mode=3&image_url=https://xxx.xxx.xxx/getImage"
ocrPacket = f"POST /recognize HTTP/1.1rn" +
f"Host: 127.0.0.1:8899rn" +
"Content-Type: application/x-www-form-urlencodedrn" +
f"Content-Length: {len(ocrReq)}rnrn" +
ocrReq
// ====== 调用 OCR 服务 ======
ocrRsp, err = poc.HTTP(ocrPacket)~
ocrResult = string(poc.GetHTTPPacketBody(ocrRsp))
// ====== 替换占位符 ======
req = re.ReplaceAll(req, "__ocr02__", ocrResult)
return req
}
0x03 使用效果
(1)登录功能处抓包:
(2)点击右上角热加载,将对应代码放入并点击保存:
(3)将需要传入验证码的参数处填入标识符号,我这里是__ocr__
。
(4)发包查看结果,可以看到成功率还是挺高的。
0x03 总结
原始工具是将xp_CAPTCHA
白嫖版和xp_CAPTCHA API
版分了两个脚本来写。但是我这里后面可能会在不同的场景用不同的方式来进行识别(懂的都懂),所以整合到一个脚本里方便后续使用。
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
点此亲启
ABOUT US
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程研究中心”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(黑白之道):『渗透测试』前端图形验证码绕过
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论