文章总结: 本文记录了一次支付金额篡改漏洞的完整挖掘过程,通过逆向分析发现小程序支付接口的签名在客户端本地生成且密钥可被恢复。关键步骤包括从加密配置中解密出appKey、还原HMAC-SHA256签名算法,最终实现将32元订单篡改为0.1元支付并验证成功。文章强调漏洞挖掘应优先验证签名边界和订单绑定机制,并提供了可复现的Python签名脚本示例。 综合评分: 89 文章分类: 漏洞分析,WEB安全,渗透测试,红队,实战经验
从客户端加密配置到伪造签名:一次支付金额篡改漏洞的挖掘实录
tangkaixing tangkaixing
开心网安
2026年5月15日 10:45 重庆
在小说阅读器读本章
去阅读
免责声明
由于传播、利用本公众号开心网安所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号开心网安及作者不为此承担任何责任,一旦造成后果请自行承担!如需要转载等,请标注文章来源。如有侵权烦请告知,我们会立即删除并致歉,谢谢!
概述
这次分析的目标,是一个聚合了洗车、券包等能力的小程序支付链路。最初的切入点并不是“直接看到金额可改”,而是顺着一个更基础的问题往下挖:发往核心支付接口的签名,到底是服务端私有能力,还是客户端本地就能复现?很多时候,业务接口表面上看似“有签名保护”,实战中真正决定漏洞上限的,恰恰是这个签名能力归谁所有。如果签名只能由服务端私钥生成,那么前端即使把金额字段明文提交出来,也不一定能构成真正可利用的逻辑漏洞;但如果签名本身就在客户端本地完成,而且密钥还能被恢复出来,那整个问题的性质就完全变了。这篇文章记录的,就是一次从客户端加密配置入手,逐步恢复本地配置、还原签名算法、伪造合法请求,最后验证到真实业务订单付款阶段可被改价的完整过程。
一、从可疑接口开始
在这个小程序里,多个业务最终都会落到统一的支付拉起接口:
POST /xxx/xxx/get-wx-payurl
从抓包结果看,这个接口的请求体里直接带了大量高价值字段:
{ "vaMchntNo": "...", "vaTermNo": "...", "totalAmount": 3200, "notifyUrl": "https://.../xxxxxStartWashCar", "mchntOrderId": "...", "subOpenId": "...", "subAppId": "...", "instMid": "xxx", "tradeType": "xxxxxxx", "msgType": "wx.unifiedOrder", "loginToken": "..."}
这类接口第一眼很容易让人怀疑“金额是不是客户端直传”,但这里不能急着下结论。因为只要 Authorization 无法伪造,就算看得到这些参数,也未必能成功构造有效请求。所以,真正的第一步不是直接改金额,而是回答下面这个问题:这个请求的签名能力,到底掌握在谁手里?
二、逆向入口:签名函数并不在服务端
对源码做静态分析后,支付请求最终走到一个统一请求封装模块。继续往里追,可以看到请求头中的 Authorization 并不是服务端返回,而是客户端本地调用签名函数生成的:
Authorization = getAuthorization(body, appId, appKey, "post")
这已经说明一个很关键的事实:
签名不是“服务器下发一次性签名”
也不是“客户端拿 token 去换临时签名”
而是客户端本地直接计算的
这时候再问两个问题:appId 从哪里来?appKey 从哪里来?
如果这两个值来自安全硬件、服务端临时下发、或运行时不可导出存储,那么问题还没完全成立;但如果这两个值只是本地配置,那么整个签名保护就会被直接击穿。
三、配置不是明文,依然在被加密源码中
源码里没有直接把配置明文摆出来,而是用了一个典型的“本地密文配置 + 本地固定密钥解密”的做法。
对应逻辑大致可以抽象成这样:
const encryptedConfig = CONFIG.dataconst aesKey = "固定字符串"const plain = AES_ECB_Decrypt(encryptedConfig, aesKey)const runtimeConfig = JSON.parse(plain)
这一步的意义很重要。它说明配置虽然不是明文写死,但依然满足两个条件:
密文在客户端本地
解密密钥也在客户端本地
因此只要拿到小程序包,就能离线恢复出运行时配置。
恢复后的配置中,至少包含了这些关键字段:appId/appKey/wxAppId/若干业务回调地址
出于安全和脱敏考虑,这里不在文章中展示完整值,只保留形态说明:
appId = 8a81...0990appKey = b8a3...30e5
到这里为止,签名能力已经从“理论可能”推进到了“可被客户端离线恢复”。
四、签名算法还原:不是障眼法,又是标准HMAC
继续分析签名函数后,可以把它抽象成下面这套流程:
- 将请求体序列化为紧凑 JSON
- 对请求体做
SHA256 - 拼接:
appId + timestamp + nonce + bodyHash
- 对拼接结果做:
HMAC-SHA256(appKey, raw)
- 最终再进行 Base64 编码
伪代码如下:
body_json = json.dumps(body, separators=(",", ":"))body_hash = sha256(body_json).hexdigest()raw = appId + timestamp + nonce + body_hashsignature = base64(hmac_sha256(appKey, raw))
这一点非常关键,因为它把漏洞链从“看见字段”变成了“能够稳定重签并发包”。也就是说,从这一刻开始,攻击者已经不再依赖官方小程序,也不再依赖服务端额外发放签名。
五、现在就可以直接开始复现
编写用脚本重签,再回填到 Repeater。
使用脚本:sign_get_wx_payurl.py
先把修改后的完整 JSON 保存成 body.json:
#部分代码参考def canonical_json(data: Dict[str, Any]) -> str: return json.dumps(data, ensure_ascii=False, separators=(",", ":"))def now_timestamp() -> str: return dt.datetime.now().strftime("%Y%m%d%H%M%S")def random_nonce() -> str: return str(random.randint(10**9, 10**10 - 1))def build_signature(body: Dict[str, Any], timestamp: str, nonce: str) -> str: body_json = canonical_json(body) body_hash = hashlib.sha256(body_json.encode("utf-8")).hexdigest() raw = f"{APP_ID}{timestamp}{nonce}{body_hash}".encode("utf-8") return base64.b64encode(hmac.new(APP_KEY.encode("utf-8"), raw, hashlib.sha256).digest()).decode("ascii")
python sign_get_wx_payurl.py --body-file body.json
脚本会输出:AuthorizationContent-Length标准化后的 Body
把这三项替换回 Burp Repeater 再发包即可
直接篡改金额:真实 32 元业务订单,付款阶段改成 1 角
第一步:先创建真实业务订单,选择低风险业务链路,重新调用下单接口生成一笔真实订单。这里以洗车订单为例。
第二步:在付款阶段改价
接着调用 get-wx-payurl,但不按正常逻辑提交 3200 分,而是手工改成:
totalAmount = 10结果服务端仍然返回:- `respCode = 0000`- `totalAmount = 10`- 新的 `miniPayRequest`
总结与反思
这次挖掘最值得记录的,不只是最后把金额改成了 1,而是整个问题是怎么一步一步被确认的。
一开始看到的是支付接口里有明文金额字段,但这还不能直接等价于可利用漏洞;真正把漏洞打穿的关键,在于先确认签名是客户端本地可复现的,再确认新订单号可以独立生成支付会话,最后再把验证推进到真实业务订单付款阶段。
这也是我一直以来的一个挖掘思路:不要看到“前端直传金额”就急着报漏洞,先看签名边界,再看订单绑定,再看金额绑定,最后再决定漏洞定性。
最后该文章已在先知社区首发【https://xz.aliyun.com/news/92096】
【完/感谢各位阅读】
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:开心网安 tangkaixing tangkaixing《从客户端加密配置到伪造签名:一次支付金额篡改漏洞的挖掘实录》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论