跳到正文

钱没到,货怎么会发出去?

2026年9月15日 3 分钟读完 Paymos Tech Paymos Tech
五条橙色虚线从左边弯进来,齐齐停在一面高白板前,后面那块小一点的白板一动没动——Paymos 收款欺诈插图

要点速览

加密货币收款欺诈做的是买家声称已付和订单发出去之间那段空当。七种手法住在这段 空当里,没有一种需要链上漏洞:不付任何账单的哈希、到了地址却关不掉账单的转账、 把结果或者价格交给浏览器、伪造的回调、重放的回调、瞄向新地址的退款,以及商户 账号本身。每一种都有挡住它的控制,每一道控制也都有它到头的地方:只验签名不看 时间戳,或者升级认证根本轮不到没有第二因素的用户。

针对加密货币付款的欺诈不可能是撤销。转账拿到终局性之后,没有卡组织可申诉,也没有发卡行去扣商户的账。剩下的是发货之前那段空当:从有人说「我付了」,到仓库、授权服务器或者客服真的动手。

七种手法住在这段空当里。下面一种一种过,先说挡它的那道控制,再说这道控制到哪儿就没用了。只讲控制不讲边界,结果就是有人把信任放到边界外面。多数手法和区块链无关:伪造的消息、错信的前端、改道的付款,只不过瞄的是收款,不是登录框。

一张截图,一个哈希,都对得上

买家发来转账截图和交易哈希,哈希一查,真的。

陷阱就在这儿。粘进区块浏览器,回来的是一笔真转账,旁边挂着金额和代币代码。三样都绑不住这张账单,代码最不可靠:按 EIP-20,namesymbol 都是可选的,标准明确告诉接入方「MUST NOT expect these values to be present」。代码是合约作者自己挑的一串字。

挡住它的是账单自己的状态。转账到了这张账单的地址,并且确认到这条网络、这笔金额对应的深度,付款才入账:以太坊上 100 美元以内是 2 个区块,1 万美元以上是 32 个,另有六条网络按网络终局性结算,不数区块。收银台上,检测到的每一笔转账都列给付款方看,没确认的也列。

确认深度是拿风险换速度,不是终局性的证明。万一链重组事后甩掉一笔已入账的转账,商户余额上那一笔不动,短掉的部分落在平台账户上。账单回答的是钱到没到,不回答订单该不该发。

钱进了余额,账单没关

因为转账可以到达地址,却不付这张账单。

有四种形状买家自己动手就能造:付款窗口关闭之后才发的转账;瞄着一张已经付过、过期或者取消的账单;换了资产,报价用 USDT,来的却是 USDC;还有落在没挂账单的地址上的。每一种都扣掉正常服务费后记进商户余额,账单一动不动。它不标已付,也没有哪个事件对得上这笔钱,只盯事件流的系统不会知道它来过。人这一侧倒是收得到,邮件和控制台铃铛各来一条。余额页上给它留了单独一块,原因、金额和服务费都写在那儿。

所以买家那条消息里有一句真话。你查一下余额,钱在那儿。 钱确实在。客服确认到账、放行订单,关掉的是一张账单上仍显示未付的单子。

反过来有一处对商户有利:同一种资产从另一条已接受的网络过来,按 1

付掉这张账单,前提是账单地址收得了那条链;不是每张都收得了。

退这一步不在通道里。钱已经落到商户余额上,要退回去一部分,就得有人授权一笔转出。付款方那一侧没有可点的按钮,等也等不来。

是谁告诉你的店铺「已支付」?

多半是浏览器,而浏览器和结果有利害关系。

同一个毛病有两个版本。第一个版本里,标记已付挂在返回 URL 上,或者挂在收银台弹层抛出的 JavaScript 事件上。低代码 SDK 抛五个这样的事件,全是 window 上的 CustomEvent,报的是弹层做了什么。拿来转个加载动画很好。它不是支付状态,开着控制台的买家自己就能抛。

第二个版本里,金额是从页面上取的。挂件按优先级从五个来源解析金额,安全程度差得远:price_id 是存在服务端的价格,请求里压根不带金额;amount_selector 读的是某个输入元素的 .value。在买家能改的页面上,这等于买家自己报价。MITRE 把这一类归到 CWE-602,服务端安全靠客户端执行:服务端「relies on the client to implement a mechanism that is intended to protect the server」。

把价格搬到服务端不等于收银台就安全,它只是把这个决定挪到买家够不着的地方。每单会变的购物车还是得自己的代码定价,已付状态还是得从买家写不进去的地方取。

回调地址在公网上,谁都能往里发

回调 URL 是公网上的地址,而且它收 JSON。

伪造一条 invoice.paid 不需要任何权限:形状对、账单 ID 对、处理器肯读 body 就够。MITRE 给这一类也留了条目,CWE-345,说的是产品「does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data」。

每次投递都用 HMAC-SHA256 签名,X-Webhook-Signature 这个头是组合的:一个时间戳,加一个或多个 v1 值。密钥轮换期间它带两个,持续 24 小时,任一匹配都算通过,接收端因此要把 v1 当列表解析,不当字段。哈希什么、各语言怎么做恒定时间比较,在验签页

再就是重放,这一条最出人意料。每次投递都盖 X-Webhook-Timestamp,出站这一侧不设时间窗,容忍多大偏差归验签的那一方。文档要求超过 300 秒就拒绝,官方 SDK 默认五分钟,建在 SDK 上的集成天然带着这道检查。只照 HMAC 手写的验签器,一周之后照样收下一条录来的投递,而它上面的签名是真的。

一条事件到两次,货就发两次

多数重复不是攻击,正因为如此,处理器特别容易写错。

一次尝试撞上 10 秒超时就重试;一个周期 11 次尝试,间隔从 1 分钟拉到 8 小时,走完要约 16 小时,失败的事件还能手动重放。扇出再加一层:同一类目挂两个端点,一件业务事实就有两次投递。攻击方只要把录来的那一次再发一遍。

一次请求里走着两个 ID,控制就是给它们分工。X-Webhook-Id 里那个 evt_… 是投递身份,去重用它。data 里那个 inv_… 是业务身份,跨端点、跨状态变化都不变,订单记在它上面。换过来两头都错:键建在投递 ID 上,两个端点把一笔付款记两遍;键建在账单 ID 上,跟在 confirming 后面的 paid 就扔掉了。重复投递怎么做幂等把处理器从头走了一遍。

去重挡住的是第二次运行,不是第二次到达。周期持续多久,投递就来多久;周期耗尽,标记为失败的是事件,不是端点。在梯子上待了几小时的 confirming 还可能落在 paid 之后,按「最后到的为准」写的处理器,会把一张已付订单倒着走回去。

退款打到谁给的地址上?

打到经手这件事的人粘上去的那个地址。

这一手对攻击方零成本,完全不碰你的系统。一封退款请求过来,自带目的地:可能来自别人正在读的客户邮箱,也可能来自被说动的同事。

挡住它的是转出白名单。转出只能指名单上已有的地址,名单为空则转出完全不可用。条目按地址加网络族建键,不按单条网络:一个 EVM 地址登记一次,这一行就答应了所有 EVM 转出网络。移除是吊销不是删除,行留着,审计链条不断。加地址本身也是一次特权操作,落进同一条链条。

它在两个地方到头。一处是改名单时那道升级认证,它并不是人人都要过。没登记过 TOTP 或者通行密钥的商户用户直接跳过去,二次验证总得先有第一次才谈得上加强。这道验证不是开户就带的,它从有人登记第二因素那天起才存在。

另一处是一次批准的体量。一次请求最多能加 1000 个地址,共用一次升级认证,CSV 导入正好用上这一点:整份文件只发一条通知,不是每个地址一条。方便和爆炸半径是同一个数字,所以要读的正是这份名单本身。名单里也没有哪一行说明对面的钱包是谁的:商户完全可以有意把客户地址登记进去。谁有资格退、期限多长、谁来批,属于退款政策

攻击者已经登录进来了

上面每一道控制,都听配置它的那个账号的。

从控制台里面看,回调 URL 可改,webhook 密钥可轮换,白名单是一张表单。会话一旦是别人的,上面六种手法一种都用不上。

账号上没有密码可以钓。登录方式是邮箱魔法链接、Google、启用的地方还有 Telegram OIDC,以及通行密钥,第二因素是 TOTP。对这一种攻击,通行密钥是四者里最硬的:WebAuthn 把凭证绑死在注册它的依赖方上,认证器「ensures that all operations are scoped to a particular origin, and cannot be replayed against a different origin, by incorporating the origin in its responses」。仿得再像的域名,收上去也用不了。

升级认证站在三个操作前面:关掉第二因素、改白名单、改 API 密钥的 IP 白名单。每个作用域各是一张票,为一个拿到的验证码授权不了别的。转出凭证还多一条要求,付款凭证连选都选不了:IP 白名单为空的转出密钥在认证这一步就被拒,不当作不限制。

没有密码不等于没有会话。魔法链接落进邮箱,而邮箱恰好最先被接管;第二因素关的正是这条路,默认没有人登记它。事后商户能读的是自己的操作记录和登录历史,完整的平台日志只有运营方看得到。

这七道里,哪几道要你自己写

七道控制不在 API 的同一侧。

三道不管谁做什么都成立:账单状态、转出前面的白名单,以及不拿没付账单的转账去关账单。另外四道只有你的代码做了才存在——验签并检查时间戳、按投递 ID 去重、价格放服务端、登记那个把升级认证打开的第二因素。每条都是一个下午的活。

有一种情况在这张单子外面。签名说明某把密钥授权了这笔转账,从不说明它的主人本意如此,而商户这一侧分不出被盗钱包和好客户。那是客服流程和发货政策,不是一道控制,任何一张安全页都关不上它。

发货之前的七种手法,各自被什么挡住,又在哪儿到头(2026年9月)
手法挡住它的它还留着的口子
买家自己提供的付款证明账单自己的状态,按设定深度入账这张订单该不该发,仍然是商业判断
转账到了地址,却不付账单账单不动,入账连原因一起落在余额页不发 webhook,只盯事件流的集成听不见
履约或者定价交给浏览器决定已付状态从服务端读,价格放服务端每单会变的购物车,还得自己的代码定价
伪造的回调HMAC-SHA256 签名,恒定时间比较,v1 当列表读重放,时间窗归接收端自己定
同一条事件投递了不止一次按 X-Webhook-Id 去重,按 data 里的 ID 记订单投递照样来,挡住的只是第二次运行
退款打向别人给的地址转出只发白名单地址,名单为空就停掉转出一次升级认证可以批准一千个地址
攻击者已经登录进来没有密码可钓;通行密钥、TOTP、按作用域的升级认证第二因素要自己开,没开就没有升级认证

常见问题

加密货币收款欺诈指的是什么?

想让货、额度或者转出为一笔从没对上订单的钱放出去。区块链这条通道上撤销不存在, 所以这类尝试必须赶在发货之前,而不是几周之后。

交易哈希能证明账单已付吗?

不能。哈希证明链上存在一笔转账。它有没有到这张账单的地址、是不是接受的资产、 有没有赶在窗口关闭之前、确认到没到要求的深度,这四件事由账单状态回答。

钱到了,账单为什么没标成已付?

因为它到了地址却没付这张账单:窗口关闭之后才到、账单已经付过或取消,或者换了 一种资产。钱扣掉服务费后进余额,账单原样不动,这类入账不发 webhook。

别人能伪造一条付款 webhook 吗?

谁都能往回调 URL 发 POST,所以每次投递都对原始 body 带一个 HMAC-SHA256 签名。 验签要用恒定时间比较,把请求头里的 v1 当列表读,并且检查时间戳,光靠签名挡不住 重放。

加转出地址之前 Paymos 会要求双重验证吗?

只有这个商户用户已经登记了 TOTP 或者通行密钥才会。升级认证加强的是已经存在的 因素,没有第二因素的用户不会被问。白名单本身一直生效,名单为空时转出直接停掉。

怎么防止重复的 webhook 把订单发两次?

两个标识各管一头。投递去重认 X-Webhook-Id 请求头里的 evt_ 值,订单认 data 里的 账单 ID。反过来建键,同一笔付款到了两个端点就会记成两笔。

什么时候不该用自建一套风控打分

  • 如果钱是换了资产、或者窗口关闭之后才到的,那是对账的活,不是欺诈案。余额页上已经 写着原因,给一个自己能直接读到的事实打分,什么也不多。
  • 如果卖的是记名客户、走账期,身份就是你手上已有的控制。在它前面再加一道筛,多半只 是给本来就要发的订单排了个队。
  • 如果价格锁在服务端、履约等的是核验过的账单终态,这七种手法已经关上了。给一扇关着 的门打分,买到的是一个误报率。
  • 如果担心的是别人拿被盗钱包来付款,你这一侧什么都看不见。链上记下的只有一件事, 某把密钥点了头;钱包归谁、当时愿不愿意,这里没有那一栏。

参考来源

  1. 1. EIP-20 — Token Standard (accessed 2026-09-15)
  2. 2. CWE-602 — Client-Side Enforcement of Server-Side Security (accessed 2026-09-15)
  3. 3. CWE-345 — Insufficient Verification of Data Authenticity (accessed 2026-09-15)
  4. 4. W3C — Web Authentication: An API for accessing Public Key Credentials Level 2 (accessed 2026-09-15)
  5. 5. Paymos 文档 — 验证 webhook 签名 (accessed 2026-09-15)
  6. 6. Paymos 文档 — 安全 (accessed 2026-09-15)

最近复核:2026年9月15日

#欺诈#webhook#安全#支付风险
分享