深度解析:滑块验证码前端安全研究:以极验 GT4(第四代)为例
免责声明:本文所有分析均基于公开可访问的前端JS代码及极验官网演示页(https://www.geetest.com/adaptive-captcha),仅用于安全研究、学习与了解验证码防护机制。文中所有敏感参数(RSA公钥模数、captc
免责声明:
本文所有分析均基于公开可访问的前端 JS 代码及极验官网演示页(https://www.geetest.com/adaptive-captcha),仅用于安全研究、学习与了解验证码防护机制。文中所有敏感参数(RSA 公钥模数、captcha_id 等)均来自极验官方公开演示环境,不涉及任何第三方业务系统。请勿将本文技术用于任何未授权的系统,违者后果自负。
一、背景介绍
极验 GT4(第四代行为验证)是国内应用最广泛的人机验证方案之一,支持滑动拼图、文字点选、一点即过、消消乐等多种验证形式。本文以
滑动拼图验证(slide)
为研究对象,分析其前端安全机制。
基本交互逻辑:
前端携带
captcha_id
+ 动态
challenge
请求
load
接口,获取背景图(bg)、滑块图(slice)、会话标识(lot_number)及工作量证明参数(pow_detail)
用计算机视觉识别滑块应移动到的缺口位置,得到
distance
前端将滑动距离、耗时、设备信息等核心字段组装为
w_data
,经
AES-CBC + RSA
双层加密后构造参数
w
携带
w
及会话参数提交
verify
接口,服务端返回
result: success/fail
研究核心难点:
识别缺口距离
(计算机视觉)
还原 w 参数的加密体系
(AES-CBC 对称加密 + RSA 非对称加密密钥)
工作量证明(PoW)参数计算
(动态 bits/hashfunc 适配)
二、整体流程图
本地生成 challenge(UUID v4)+ callback(时间戳)
↓
load 接口(GET /load)
↓ 返回 lot_number、bg、slice、pow_detail、payload、process_token
下载背景图 + 滑块图
↓
ddddocr 识别缺口距离 distance
↓
计算工作量证明 pow_msg / pow_sign
↓
组装 w_data(distance、passtime、userresponse 等字段)
↓
AES-CBC 加密 w_data → aes_data
RSA 加密 AES 密钥 → rsa_data
w = aes_data + rsa_data
↓
verify 接口(GET /verify)→ result: success/fail
三、抓包分析
3.1 load 接口
请求:
GET https://gcaptcha4.geetest.com/load
关键请求参数:
参数
来源
说明
callback
本地生成
'geetest_' + Date.now()
,用于 JSONP 回调
captcha_id
业务方配置
绑定某个具体业务,从前端 JS 中提取,固定值
challenge
本地生成
UUID v4 格式,每次请求动态生成(源码:
config.challenge || uuid()
)
client_type
固定
web
risk_type
固定
slide
表示滑动拼图,不同验证形式对应不同值
lang
固定
zh
challenge
在 GT4 的
gt4.js
源码中明确写为
config.challenge || uuid()
,即业务方不传时自动生成 UUID,因此可本地用
uuid.uuid4()
替代。
响应示例(JSONP 格式):
geetest_xxxxxxxxxx
(
{
"status"
:
"success"
,
"data"
:
{
"lot_number"
:
"614c7e56806748cfad5d2eeca241293f"
,
"captcha_type"
:
"slide"
,
"bg"
:
"captcha_v4/xxx/bg/xxx.jpg"
,
"slice"
:
"captcha_v4/xxx/slice/xxx.png"
,
"ypos"
:
14
,
"pow_detail"
:
{
"version"
:
"1"
,
"bits"
:
0
,
"datetime"
:
"2026-04-15T21:20:34.369357+08:00"
,
"hashfunc"
:
"md5"
}
,
"payload"
:
"AgFD8g..."
,
"process_token"
:
"ee684e49..."
,
"payload_protocol"
:
1
}
}
)
响应为 JSONP 格式,需动态定位括号位置解析,不能硬编码偏移量(callback 名长度随时间戳变化)。
3.2 verify 接口
请求:
GET https://gcaptcha4.geetest.com/verify
关键请求参数:
参数
来源
说明
captcha_id
同 load
业务方固定值
lot_number
load 响应
本次会话唯一标识
risk_type
固定
slide
payload
load 响应
服务端加密载荷,原样透传
process_token
load 响应
会话凭证,原样透传
payload_protocol
load 响应
协议版本,原样透传
pt
load 响应
原样透传
w
本地加密生成
核心参数,见第四节
四、JS 逆向——w 参数加密体系
w
是 verify 接口的核心参数,其生成逻辑可直接在极验 CDN 分发的
gt4.js
(未混淆)中阅读。
4.1 w 参数结构
w = AES_CBC_Encrypt(JSON.stringify(w_data), aes_key) [hex]
+ RSA_Encrypt(aes_key) [hex]
即:
AES 加密后的数据 hex 字符串
拼接
RSA 加密密钥 hex 字符串
,服务端先用私钥解出 AES 密钥,再解密出
w_data
。
4.2 w_data 核心字段
字段
含义
说明
setLeft
滑块移动像素
ddddocr 识别结果
passtime
滑动总耗时(ms)
随机范围 1300~2000
userresponse
实际响应距离
distance / 1.0059466... + 2
,固定换算公式
lot_number
会话标识
来自 load 响应
pow_msg
PoW 消息
见第五节
pow_sign
PoW 签名
见第五节
gee_guard
环境检测结果
固定结构,包含多项 bot 检测标志
em
环境检测补充
固定结构
4.3 AES 加密
算法:AES-128-CBC,PKCS7 填充,输出 hex
密钥处理规则(来自
gt4.js
逆向):
key_str
长度 < 16:取其 MD5 digest(16 字节)作为密钥
key_str
长度 ≥ 16:截取前 16 字节
IV 处理规则相同,默认 IV 为
"0000000000000000"
def
AES_Encrypt
(
word
:
str
,
key_str
:
str
,
iv_str
:
str
=
"0000000000000000"
)
-
>
str
:
"""AES-128-CBC 加密,PKCS7 填充,输出 hex"""
key_bytes
=
(
hashlib
.
md5
(
key_str
.
encode
(
)
)
.
digest
(
)
if
len
(
key_str
)
<
16
else
key_str
.
encode
(
)
[
:
16
]
)
iv_bytes
=
(
hashlib
.
md5
(
iv_str
.
encode
(
)
)
.
digest
(
)
if
len
(
iv_str
)
<
16
else
iv_str
.
encode
(
)
[
:
16
]
)
cipher
=
AES
.
new
(
key_bytes
,
AES
.
MODE_CBC
,
iv_bytes
)
return
cipher
.
encrypt
(
pad
(
word
.
encode
(
)
,
AES
.
block_size
)
)
.
hex
(
)
4.4 RSA 加密
算法:RSA PKCS1 v1.5,公钥通过模数(hex)+ 固定指数(65537)构造,输出 hex
RSA 公钥的模数(
modulus
)虽以 hex 字符串形式存在于
gt4.js
中,但实际使用时需通过调试断点或 Hook 加密函数的方式动态确认其与当前 SDK 版本的对应关系,不建议直接静态读取硬编码。加密的内容是随机生成的 AES 密钥字符串,服务端持有私钥还原后用于解密
w_data
。
def
RSA_Encrypt
(
plaintext
:
str
)
-
>
str
:
"""RSA PKCS1 v1.5 加密,modulus 需通过调试从 gt4.js 中获取,输出 hex"""
modulus
=
int
(
"<调试获取的 modulus hex 字符串>"
,
16
)
key
=
RSA
.
construct
(
(
modulus
,
65537
)
)
return
PKCS1_v1_5
.
new
(
key
)
.
encrypt
(
plaintext
.
encode
(
)
)
.
hex
(
)
4.5 AES 密钥生成
每次请求随机生成 16 字节(4 × 4 位随机 hex),保证每次
w
不重复:
def
get_random_key
(
)
-
>
str
:
"""生成 16 字节随机 hex 字符串,作为 AES 会话密钥"""
return
''
.
join
(
format
(
random
.
getrandbits
(
16
)
,
'04x'
)
for
_
in
range
(
4
)
)
五、工作量证明(PoW)机制
GT4 在
load
响应中下发
pow_detail
,要求客户端提交满足条件的哈希值,用于防刷。
5.1 pow_detail 结构
字段
说明
version
协议版本,固定
"1"
bits
要求哈希结果前导零 bit 数;
bits=0
表示无要求,任意值即可
hashfunc
哈希算法,
md5
或
sha256
datetime
服务端当前时间戳,计入消息体防重放
5.2 pow_msg 格式
pow_msg = f"1|{bits}|{hashfunc}|{datetime}|{captcha_id}|{lot_number}||{random_key}"
pow_sign = hashfunc(pow_msg)
当
bits > 0
时,需循环碰撞直到
pow_sign
满足前导零条件:
def
get_pow_info
(
pow_detail
:
dict
,
captcha_id
:
str
,
lot_number
:
str
)
:
"""
动态计算 PoW,适配 bits=0(直接返回)和 bits>0(碰撞循环)两种场景。
"""
bits
=
pow_detail
.
get
(
'bits'
,
0
)
datetime
=
pow_detail
.
get
(
'datetime'
,
''
)
hashfunc
=
pow_detail
.
get
(
'hashfunc'
,
'md5'
)
hash_fn
=
hashlib
.
sha256
if
hashfunc
==
'sha256'
else
hashlib
.
md5
prefix
=
'0'
*
(
bits
//
4
)
# 需要多少个前导零 hex 字符
while
True
:
key
=
get_random_key
(
)
pow_msg
=
f'1|
{
bits
}
|
{
hashfunc
}
|
{
datetime
}
|
{
captcha_id
}
|
{
lot_number
}
||
{
key
}
'
pow_sign
=
hash_fn
(
pow_msg
.
encode
(
)
)
.
hexdigest
(
)
if
bits
==
0
or
pow_sign
.
startswith
(
prefix
)
:
return
pow_msg
,
pow_sign
六、图像识别——计算缺口距离
使用开源库
ddddocr
进行滑块缺口匹配,支持直接传入图片二进制,无需写入磁盘:
from
ddddocr
import
DdddOcr
def
ddddocr_get_distance
(
bg
:
bytes
,
tp
:
bytes
)
-
>
int
:
"""
利用 ddddocr 识别滑块缺口位置,返回滑动距离(像素)。
:param bg: 背景图二进制(带缺口的完整背景)
:param tp: 滑块图二进制(带透明通道的拼图块)
:return: 缺口 x 坐标(像素)
"""
det
=
DdddOcr
(
det
=
False
,
ocr
=
False
,
show_ad
=
False
)
res
=
det
.
slide_match
(
tp
,
bg
,
simple_target
=
True
)
return
int
(
res
[
'target'
]
[
0
]
)
res['target']
返回
[left, top, right, bottom]
,
left
即缺口的 x 坐标。
极验 GT4 图片为标准分辨率(非 Retina 2x),无需额外
/2
校正,与数美等 SDK 不同。
备选方案:OpenCV 模板匹配
若 ddddocr 识别不准,可改用 Canny 边缘检测 + 模板匹配:
import
cv2
def
opencv_get_distance
(
bg_path
:
str
,
slice_path
:
str
)
-
>
int
:
"""OpenCV Canny 边缘检测 + 模板匹配识别缺口距离"""
bg
=
cv2
.
imread
(
bg_path
)
tp
=
cv2
.
imread
(
slice_path
,
cv2
.
IMREAD_UNCHANGED
)
# 统一灰度化
bg_gray
=
cv2
.
cvtColor
(
bg
,
cv2
.
COLOR_BGR2GRAY
)
tp_gray
=
(
cv2
.
cvtColor
(
tp
,
cv2
.
COLOR_BGRA2GRAY
)
if
tp
.
shape
[
2
]
==
4
else
cv2
.
cvtColor
(
tp
,
cv2
.
COLOR_BGR2GRAY
)
)
# 边缘检测后模板匹配
result
=
cv2
.
matchTemplate
(
cv2
.
Canny
(
bg_gray
,
50
,
150
)
,
cv2
.
Canny
(
tp_gray
,
50
,
150
)
,
cv2
.
TM_CCOEFF_NORMED
)
return
cv2
.
minMaxLoc
(
result
)
[
3
]
[
0
]
# max_loc[0] 即 x
七、JSONP 响应解析
GT4 的 load/verify 接口均返回 JSONP 格式,回调函数名包含毫秒级时间戳,长度不固定,
不能用固定偏移量(如
[22:-1]
)截取
,应动态定位括号:
def
parse_jsonp
(
text
:
str
)
-
>
dict
:
"""动态解析 JSONP 响应,提取 JSON 部分"""
json_str
=
text
[
text
.
index
(
'('
)
+
1
:
text
.
rindex
(
')'
)
]
return
json
.
loads
(
json_str
)
八、完整请求流程概述
步骤
动作
说明
1
生成会话标识
本地生成
challenge
(
uuid.uuid4()
)和
callback
(
geetest_
+ 时间戳)
2
调用 load 接口
获取
lot_number
、背景图/滑块图路径、
pow_detail
、
payload
、
process_token
3
下载图片
从
https://static.geetest.com/
下载 bg 和 slice
4
识别缺口距离
ddddocr
slide_match
得到
distance
5
计算 PoW
根据
pow_detail.bits
和
hashfunc
动态碰撞,得到
pow_msg
/
pow_sign
6
组装 w_data
填入 distance、passtime、userresponse、pow 等字段
7
加密构造 w
AES_CBC(w_data, aes_key)
+
RSA(aes_key)
拼接
8
调用 verify 接口
携带 w 及会话参数,解析返回
result: success/fail
九、关键知识点总结
知识点
详情
challenge 来源
本地
uuid.uuid4()
生成,GT4 源码明确支持(
config.challenge || uuid()
)
captcha_id
业务方配置的固定值,从前端 JS 提取,不会每次变化
图像识别库
ddddocr
滑块匹配,GT4 无 Retina 缩放,无需
/2
AES 加密
AES-128-CBC,PKCS7 填充,输出 hex;密钥短于 16 字节时取 MD5
RSA 加密
PKCS1 v1.5,公钥参数可从 gt4.js 明文读取,输出 hex
w 结构
AES(w_data) hex
+
RSA(aes_key) hex
直接拼接
PoW 机制
动态适配 bits/hashfunc,bits=0 时直接返回,bits>0 时循环碰撞
JSONP 解析
动态定位
(
)
位置,不能硬编码偏移量
Session 管理
使用
requests.Session
自动携带服务端下发的 Cookie(如
captcha_v4_user
)
十、与数美 SDK(protocol=206)的横向对比
对比维度
极验 GT4
数美 SDK(新版)
加密算法
AES-CBC + RSA,输出 hex
DES-ECB,输出 Base64
加密参数数量
合并为 1 个 w 参数
分散为 12 个独立字段
密钥来源
本地随机生成 AES Key + 固定公钥
硬编码 Key 逆向 + 动态派生
__key
工作量证明
有 PoW 机制(pow_detail 动态下发)
无 PoW
图片分辨率
标准分辨率,无缩放
Retina 2x,需
/2
校正
序列化
标准
json.dumps
自定义
smStringify
(差异处理 undefined)
JSONP 解析
需动态解析
需动态解析
JS 混淆程度
gt4.js 基本可读
obfuscator.io 重度混淆,需 webcrack 反混淆
十一、依赖安装
pip
install
requests pycryptodome ddddocr opencv-python
本文技术仅供安全研究与学习,切勿用于任何未授权系统,违者后果自负。