关于码支付 伪造异步回调达到0元购的效果

本站资源均为网友、站长推荐收集整理而来,请勿商业运营,仅供学习和研究,请在下载后24小时内删除!!

请勿非法使用!仅供学术研究!

只要码支付使用了金额兜底功能,都可以通过伪造异步回调的方式进行0元购,市面上80%的瓶盖网或者知识付费、下单网址、商城都有这个逻辑BUG

从下面图片中可以看出:无实际支付成功案例,伪装异步回调成功等信息放行或进行重放即可达到此目的。

图片[1]-关于码支付 伪造异步回调达到0元购的效果-Bidwarp
图片[2]-关于码支付 伪造异步回调达到0元购的效果-Bidwarp

分析报告如下:

码支付异步回调验签漏洞分析报告

报告编号:SEC-2026-0801|日期:2026-08-01|分类:支付安全 / 异步回调|密级:内部

码支付异步回调验签漏洞分析报告

针对码支付系统异步通知回调接口(notify_url)签名验证缺失问题的漏洞原理分析与修复建议。

严重 · CVSS 9.1(概念评估)

目录

01 执行摘要

码支付是一类广泛部署于个人站长和中小电商的免签约聚合支付系统。其核心交易闭环依赖异步回调通知(notify_url)来确认支付结果并更新订单状态。分析发现,该系统的回调接口存在签名验证缺失或实现缺陷,导致攻击者可在不实际支付的情况下,通过构造伪造的回调请求将订单标记为”已支付”。

该漏洞的根本原因在于:码支付系统在接收到上游支付通道的异步通知时,未对回调报文进行有效的签名校验,或使用了可被绕过的弱签名算法。同时,系统缺少 IP 白名单、金额一致性校验和幂等性控制等多层防护,使得伪造回调的门槛极低。

核心风险

攻击者只需知道回调接口地址和参数格式,即可绕过全部真实支付流程,直接触发”支付成功”逻辑。在实际案例中,此类漏洞已被用于以 0.01 元购买高价值商品、批量刷取虚拟权益等场景,造成直接资金损失。

02 系统架构概述

码支付系统的典型架构基于 PHP + MySQL,采用”收银台 + 插件”模式对接多个支付通道。核心支付链路如下:

核心支付链路 商户系统发起支付 → 校验商户状态和签名 → 创建业务订单 → 创建支付单,按支付方式解析路由 → 轮询选择支付通道,加载支付插件 → 插件返回二维码 / 跳转链接 / JSAPI 参数 → 用户扫码或跳转完成支付 → 上游支付通道回调 notify_url → 系统更新订单状态为”已支付” → 创建商户通知任务,回调商户系统

关键文件结构

文件 / 接口类型职责安全关注点
yybpay_return.php同步返回用户支付后浏览器跳转回的页面,前端轮询订单状态仅展示用途,不更新订单状态
notify_url(回调处理)异步通知支付通道服务端主动调用,推送支付结果,更新订单状态★ 核心漏洞点
epay.config.php配置文件存放商户 PID、密钥、API 地址等密钥泄露风险
EpayCore.class.php核心类封装签名生成、验签、请求发送等逻辑验签实现是否正确
processNotify()业务函数处理通知通过后的订单状态变更、记账等幂等性、状态机校验

系统的 notify() 方法负责接收上游回调,调用 verifyNotify() 进行验签,验签通过后执行 processNotify() 更新订单。问题在于,不同实现版本的验签逻辑质量参差不齐。

03 回调机制解析

3.1 正常回调流程

一个标准的支付异步回调包含以下阶段: 支付异步回调正常流程 展示从支付通道到商户系统的正常异步回调流程,包括签名生成、传输、验签和订单更新四个阶段 ① 支付通道 用户完成付款 ② 生成签名 参数+密钥→MD5/HMAC ③ HTTP POST 发送至 notify_url ④ 验签 ★ 漏洞所在 报文内容 order_id, amount, status, sign 签名算法 MD5(sorted_params + key) 传输方式 POST / GET,可能无 HTTPS 缺失或可绕过 → 直接更新订单

3.2 正常回调报文结构

典型回调请求 POST /notify.php HTTP/1.1 Host: merchant.com Content-Type: application/x-www-form-urlencoded # 业务参数 out_trade_no=20260801100001 trade_no=202608011123456789 money=99.90 trade_status=TRADE_SUCCESS name=”会员充值”# 签名字段 sign=abc123def456… sign_type=MD5

正常流程中,支付通道在用户付款后,将上述参数按字典序拼接、追加商户密钥后计算 MD5(或 HMAC-SHA256),生成 sign 字段。商户系统收到回调后,用本地存储的密钥重新计算签名,与传入的 sign 比对。一致则信任,不一致则拒绝。

04 漏洞核心:验签缺失

4.1 漏洞描述

码支付系统的异步回调接口(notify_url)存在签名验证缺失或可绕过的安全缺陷。具体表现为以下一种或多种情况:

缺陷类型表现根因严重程度
完全无验签notify_url 直接读取参数并更新订单,不校验 sign开发疏忽或测试代码上线未移除致命
验签逻辑可绕过verifyNotify() 返回值未正确判断,或验签失败仍继续执行代码逻辑错误,如 if 判断写反、异常未中断流程致命
弱签名算法使用 MD5 且密钥熵不足,可通过碰撞或暴力破解伪造MD5 已被证明存在碰撞漏洞,不适合安全签名
密钥泄露商户密钥硬编码在源码中或提交至公开仓库缺乏密钥管理意识
参数未校验不校验金额、订单状态、商户ID 等业务参数仅依赖 trade_status 判断,忽略一致性
无 IP 白名单任意 IP 均可向 notify_url 发送回调请求未配置支付通道 IP 段准入控制
无幂等控制同一订单的重复回调被多次处理缺少唯一键去重机制

4.2 漏洞代码示例(概念性)

以下是典型缺陷代码模式,用于说明漏洞原理:

缺陷模式一:验签失败未中断流程function notify() { $epayNotify = new EpayCore($epay_config); $verify_result = $epayNotify->verifyNotify(); // 缺陷:验签失败时未 return / exit,流程继续执行if ($verify_result) { // 正常处理… } // else 分支缺失或为空,但后续代码仍然执行$out_trade_no = $_GET[‘out_trade_no’]; $money = $_GET[‘money’]; // 无论验签是否通过,订单状态都被更新 processNotify($out_trade_no, $money); }

缺陷模式二:完全跳过验签function notify() { // 直接从请求中读取参数,不调用任何验签方法$out_trade_no = $_GET[‘out_trade_no’]; $trade_status = $_GET[‘trade_status’]; $money = $_GET[‘money’]; if ($trade_status == ‘TRADE_SUCCESS’) { // 直接更新订单为已支付,无任何校验 processNotify($out_trade_no, $money); } return’success’; }

关键观察

在实际抓包分析中,yybpay_return.php 是前端轮询接口(同步返回),它本身不更新订单状态。真正更新订单状态的是服务端异步回调 notify_url。但该接口在 HAR 抓包中往往不可见——因为它是由支付通道服务器直接发起的 POST 请求,不经过用户浏览器。这使得漏洞更加隐蔽:开发者可能在不知情的情况下遗漏了验签逻辑。

05 攻击面分析

5.1 攻击前提条件

前提条件获取难度说明
notify_url 地址可通过下单流程抓包、查看源码或猜测路径(如 /pay/notify//notify.php)获取
参数格式标准 ePay 协议参数名固定:out_trade_notrade_statusmoneysign
商户密钥(如需伪造签名)密钥泄露时可获取;无验签时不需要
有效订单号正常下单即可获得未支付订单号

5.2 概念性攻击路径

说明

以下仅描述攻击原理和概念性路径,不包含可执行的攻击代码或具体利用工具。目的是帮助开发者和安全人员理解威胁模型,以便针对性修复。

路径一:无验签场景

  1. 攻击者在目标站点正常下单,获得订单号 out_trade_no
  2. notify_url 发送构造的 POST 请求,包含 out_trade_no=目标订单号trade_status=TRADE_SUCCESSmoney=原金额
  3. 由于系统不校验签名,直接将订单标记为已支付
  4. 攻击者无需实际付款即获得商品或服务

路径二:金额篡改 + 重放

  1. 攻击者正常支付一笔小额订单(如 0.01 元),抓取完整的回调请求(含有效签名)
  2. 修改 out_trade_no 为另一笔高价未支付订单的订单号
  3. 由于系统不校验金额一致性,或签名只覆盖部分参数,重放请求成功
  4. 高价订单被标记为已支付,实际仅支付了 0.01 元

路径三:密钥泄露后伪造签名

  1. 攻击者通过源码泄露或社工手段获取商户密钥
  2. 使用密钥为任意构造的回调参数计算有效签名
  3. 发送携带有效签名的伪造回调,系统验签通过,订单被更新

06 影响评估

直接资金损失

虚假支付

攻击者可对任意未支付订单伪造回调,无需实际付款即获取商品或服务。虚拟商品(会员、充值卡、激活码)损失即时发生。

业务欺诈

金额篡改

通过修改回调中的 money 参数,以极低金额购买高价值商品。无金额一致性校验时此攻击直接成功。

数据一致性破坏

重复入账

缺少幂等性控制时,同一回调可被重复发送,导致多次发货、多次发券、多次结算,对账时出现严重不平。

运营损失

批量刷单

攻击者可自动化批量伪造回调,短时间内造成大量虚假订单,直接冲击库存和运营成本。

信任损失

商户信誉

漏洞被利用后,商户面临用户投诉、资金纠纷和平台信任度下降,长期影响难以量化。

合规风险

监管处罚

支付安全漏洞可能违反《网络安全法》《数据安全法》相关规定,面临监管约谈或处罚。

CVSS 评估(概念性)

向量:AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 得分 9.1(严重)。攻击通过网络发起,无需认证和用户交互,可同时影响机密性、完整性和可用性。实际得分取决于具体系统的防护措施和业务场景。

07 修复建议

根据支付系统异步回调设计的行业最佳实践,建议按以下优先级实施修复:

  1. 强制签名验证(最高优先级) 所有回调请求必须经过签名校验。验签失败时立即终止流程并记录日志,不得继续执行任何业务逻辑。使用 HMAC-SHA256 替代 MD5,密钥长度不低于 256 位。验签逻辑应使用 hash_equals() 进行时间安全比较,防止计时攻击。
  2. 业务参数强校验 验签通过后,必须校验:①订单号是否为本系统订单且存在;②回调金额与订单原始金额是否一致(精确到分);③交易状态是否合法;④商户 ID 是否匹配。任一校验不通过即拒绝处理。
  3. 幂等性控制 以 out_trade_no 或渠道交易号为唯一幂等键。处理前先查询订单状态:若已是终态(成功/关闭/退款),直接返回 success 不重复处理。通过数据库唯一索引或分布式锁保证并发回调安全。
  4. 严格状态机 定义订单状态流转规则:待支付 → 已支付 → 已发货 → 已完成。禁止逆向回写:已支付的订单不再接受”关闭”或”失败”回调;已关闭的订单不再接受”成功”回调。
  5. IP 白名单准入 在 Nginx 或应用层配置支付通道 IP 白名单,仅允许支付宝/微信等官方 IP 段访问 notify_url。拒绝白名单外 IP 的所有请求。
  6. 原始报文落库 回调接口收到请求后,先将原始 POST body、请求头、来源 IP、时间戳完整落库,再执行验签和业务处理。确保每一笔回调有据可查,便于事后审计和问题排查。
  7. 异步化业务处理 回调接口只做:接收 → 落库 → 验签 → 发送消息队列 → 立即返回 success。耗时业务逻辑(结算、通知、营销等)由消费者异步处理,避免接口超时触发渠道重试。
  8. 主动补偿兜底 配置定时主动查询任务,对超过 N 分钟仍未收到回调的订单主动查询支付通道。日终运行对账任务,比对本地订单、支付流水和渠道账单三方数据,自动修复不一致。
  9. 密钥安全管理 商户密钥不得硬编码在源码中,应通过环境变量或密钥管理服务注入。定期轮换密钥,禁止将含密钥的代码提交至版本控制或公开仓库。
  10. 全链路 HTTPS notify_url 必须使用 HTTPS,并开启 HSTS。防止中间人监听、篡改或劫持回调请求。校验 TLS 证书有效性,防止降级攻击。

标准回调处理流程(修复后)

推荐实现(伪代码)function handleNotify($request) { // 1. 原始报文落库(先存后处理) logRawNotify($request); // 2. IP 白名单校验if (!isAllowedIP($request->remoteIP)) { logAndReject(‘IP not allowed’); return’fail’; } // 3. 签名验证(HMAC-SHA256,时间安全比较)$params = $request->params; $sign = $params[‘sign’]; unset($params[‘sign’], $params[‘sign_type’]); ksort($params); $expectedSign = hash_hmac(‘sha256’, http_build_query($params), $secretKey); if (!hash_equals($expectedSign, $sign)) { logAndReject(‘Signature mismatch’); return’fail’; } // 4. 业务参数校验$order = findOrder($params[‘out_trade_no’]); if (!$order) return’fail’; if ($order->status == ‘paid’) return’success’; if ($params[‘money’] != $order->amount) { triggerAlert(‘Amount mismatch’); return’fail’; } // 5. 发送异步消息,快速返回 enqueue(‘payment_success’, $order); return’success’; }

© 版权声明 All Rights Reserved
© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容