签名从哪来?小程序 API 请求签名的逆向与验证
字数 1869 2025-08-26 22:11:35
小程序API请求签名逆向分析与验证技术文档
1. 背景与研究目标
1.1 问题发现
- 在分析某医疗小程序接口时发现所有请求都携带三个关键参数:
Sign: 签名字段Ticks: 时间戳(毫秒级)Nonce: 随机数
- 当这些参数不匹配时,服务端返回401签名验证失败错误
1.2 签名机制的意义
- 接口防刷:验证请求合法性
- 参数完整性:确保请求参数未被篡改
- 会话绑定:关联用户身份和请求上下文
2. 初步验证
- 尝试直接替换数据包中的业务参数(branchId, userId等)
- 结果:服务端持续返回401,表明签名与请求参数相关
3. 小程序逆向工程
3.1 逆向工具
- 使用以下工具进行小程序逆向:
3.2 逆向流程
- 解密小程序包
- 反编译解密后的包
4. 静态分析
4.1 关键词检索
- 使用ripgrep搜索整个代码库
- 发现签名相关代码集中在
common/request.js文件中
4.2 关键字段分析
| 变量名 | 作用 | 参与签名 | 备注 |
|---|---|---|---|
| Sign | 最终签名值 | 输出 | MD5摘要,大写 |
| Ticks | 时间戳 | 是 | 毫秒级时间戳 |
| Nonce | 随机数 | 是 | 生成逻辑有问题 |
| AppId | 应用标识 | 是 | 来自配置文件 |
| AppSecret | 应用密钥 | 是 | 硬编码在客户端 |
| paramString | 请求数据 | 是 | 包含所有请求参数 |
4.3 调用链还原
- 请求拦截器入口
- dealHeader函数处理
- 签名生成函数(被混淆为单字母变量名)
- MD5计算
5. 动态分析
5.1 断点设置
- 请求拦截器入口
- dealHeader函数开始
- 签名生成函数内部
- MD5计算前后
5.2 关键发现
- Nonce生成错误:使用
1 - 1e8产生负数,导致随机数可能为负值 - 字符级排序:将拼接后的字符串逐字符排序,设计罕见
- 参数处理不一致:对象和数组类型只拼接键名,存在哈希碰撞风险
6. 签名生成算法复现
6.1 签名生成步骤
- 收集所有请求参数(paramString)
- 拼接以下字段:
- AppId
- AppSecret
- Ticks
- Nonce
- paramString
- 对拼接后的字符串进行字符级排序
- 计算MD5哈希(结果转为大写)
6.2 Python验证脚本
import hashlib
import json
from urllib.parse import quote
def generate_sign(app_id, app_secret, ticks, nonce, params):
# 1. 参数排序和拼接
param_str = json.dumps(params, separators=(',', ':'), ensure_ascii=False)
# 2. 拼接基础字段
raw_str = f"{app_id}{app_secret}{ticks}{nonce}{param_str}"
# 3. 字符级排序
sorted_str = ''.join(sorted(raw_str))
# 4. MD5计算
md5 = hashlib.md5()
md5.update(sorted_str.encode('utf-8'))
return md5.hexdigest().upper()
# 示例使用
app_id = "your_app_id"
app_secret = "your_app_secret"
ticks = "1629600000000"
nonce = "123456"
params = {"key1": "value1", "key2": "value2"}
sign = generate_sign(app_id, app_secret, ticks, nonce, params)
print(f"Generated Sign: {sign}")
7. 边界测试与安全性评估
7.1 测试用例
| 测试用例 | 变更内容 | 预期结果 | 实测结果 |
|---|---|---|---|
| T1 | 交换参数键顺序 | 签名相同 | ✅ 相同 |
| T2 | 修改参数值 | 签名不同 | ✅ 不同 |
| T3 | 添加null值参数 | 只影响键名 | ✅ 符合预期 |
| T4 | 时间戳固定 | 可重放攻击 | ⚠️ 存在风险 |
7.2 安全性问题
- 重放攻击风险:缺少服务端时间戳验证窗口
- 密钥泄露:AppSecret硬编码在客户端
- 算法弱点:使用MD5(已不被推荐用于安全场景)
- 实现缺陷:Nonce生成逻辑错误
8. 安全改进建议
8.1 密钥管理优化
- 问题:AppSecret直接暴露在客户端代码中
- 建议:
- 使用临时Token机制
- 实现密钥轮换策略
8.2 重放防护机制
- 建议措施:
- 服务端验证时间戳在±5分钟窗口内
- 实现Nonce去重存储(Redis + TTL)
- 添加请求频率限制
8.3 算法升级
- 替换MD5为更安全的HMAC-SHA256
- 规范化参数处理逻辑
9. 结论与启示
9.1 研究总结
- 算法识别:基于MD5的自定义签名算法
- 实现缺陷:Nonce生成、参数处理等多处问题
- 安全风险:密钥暴露、重放攻击、算法过时
9.2 开发者启示
- 安全设计:签名密钥不应暴露在客户端
- 算法选择:使用现代化的加密算法和标准实现
- 防护机制:实施完整的重放攻击防护
- 代码审查:定期检查关键安全逻辑的实现质量
附录:渗透测试利用方法
- 获取AppId和AppSecret(通过逆向获取)
- 设置较大的Ticks时间戳(可重复利用)
- 每次请求更换请求参数和Nonce值
- 配合Burp插件编写自动化脚本进行渗透测试
注:本文所有分析均基于授权样本,代码示例已脱敏处理。安全研究应遵守法律法规,仅在授权范围内进行测试。