签名从哪来?小程序 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 逆向流程

  1. 解密小程序包
  2. 反编译解密后的包

4. 静态分析

4.1 关键词检索

  • 使用ripgrep搜索整个代码库
  • 发现签名相关代码集中在common/request.js文件中

4.2 关键字段分析

变量名 作用 参与签名 备注
Sign 最终签名值 输出 MD5摘要,大写
Ticks 时间戳 毫秒级时间戳
Nonce 随机数 生成逻辑有问题
AppId 应用标识 来自配置文件
AppSecret 应用密钥 硬编码在客户端
paramString 请求数据 包含所有请求参数

4.3 调用链还原

  1. 请求拦截器入口
  2. dealHeader函数处理
  3. 签名生成函数(被混淆为单字母变量名)
  4. MD5计算

5. 动态分析

5.1 断点设置

  1. 请求拦截器入口
  2. dealHeader函数开始
  3. 签名生成函数内部
  4. MD5计算前后

5.2 关键发现

  1. Nonce生成错误:使用1 - 1e8产生负数,导致随机数可能为负值
  2. 字符级排序:将拼接后的字符串逐字符排序,设计罕见
  3. 参数处理不一致:对象和数组类型只拼接键名,存在哈希碰撞风险

6. 签名生成算法复现

6.1 签名生成步骤

  1. 收集所有请求参数(paramString)
  2. 拼接以下字段:
    • AppId
    • AppSecret
    • Ticks
    • Nonce
    • paramString
  3. 对拼接后的字符串进行字符级排序
  4. 计算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 安全性问题

  1. 重放攻击风险:缺少服务端时间戳验证窗口
  2. 密钥泄露:AppSecret硬编码在客户端
  3. 算法弱点:使用MD5(已不被推荐用于安全场景)
  4. 实现缺陷:Nonce生成逻辑错误

8. 安全改进建议

8.1 密钥管理优化

  • 问题:AppSecret直接暴露在客户端代码中
  • 建议
    • 使用临时Token机制
    • 实现密钥轮换策略

8.2 重放防护机制

  • 建议措施
    • 服务端验证时间戳在±5分钟窗口内
    • 实现Nonce去重存储(Redis + TTL)
    • 添加请求频率限制

8.3 算法升级

  • 替换MD5为更安全的HMAC-SHA256
  • 规范化参数处理逻辑

9. 结论与启示

9.1 研究总结

  1. 算法识别:基于MD5的自定义签名算法
  2. 实现缺陷:Nonce生成、参数处理等多处问题
  3. 安全风险:密钥暴露、重放攻击、算法过时

9.2 开发者启示

  1. 安全设计:签名密钥不应暴露在客户端
  2. 算法选择:使用现代化的加密算法和标准实现
  3. 防护机制:实施完整的重放攻击防护
  4. 代码审查:定期检查关键安全逻辑的实现质量

附录:渗透测试利用方法

  1. 获取AppId和AppSecret(通过逆向获取)
  2. 设置较大的Ticks时间戳(可重复利用)
  3. 每次请求更换请求参数和Nonce值
  4. 配合Burp插件编写自动化脚本进行渗透测试

注:本文所有分析均基于授权样本,代码示例已脱敏处理。安全研究应遵守法律法规,仅在授权范围内进行测试。

小程序API请求签名逆向分析与验证技术文档 1. 背景与研究目标 1.1 问题发现 在分析某医疗小程序接口时发现所有请求都携带三个关键参数: Sign : 签名字段 Ticks : 时间戳(毫秒级) Nonce : 随机数 当这些参数不匹配时,服务端返回401签名验证失败错误 1.2 签名机制的意义 接口防刷:验证请求合法性 参数完整性:确保请求参数未被篡改 会话绑定:关联用户身份和请求上下文 2. 初步验证 尝试直接替换数据包中的业务参数(branchId, userId等) 结果:服务端持续返回401,表明签名与请求参数相关 3. 小程序逆向工程 3.1 逆向工具 使用以下工具进行小程序逆向: wxappUnpacker unveilr 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验证脚本 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插件编写自动化脚本进行渗透测试 注:本文所有分析均基于授权样本,代码示例已脱敏处理。安全研究应遵守法律法规,仅在授权范围内进行测试。