这封面图是在B站看到,不知出自哪位大师之手。
前言
这个事件我犹豫了一下,想要不要在本站发布,因为想必你们访问也不是想看八卦,我也不是想激起什么矛盾,但我最后发布的原因是,我觉得有必要从现实的层面上看待一个产品从设计到落地,有什么地方需要留意,我们该如何避免发生同类事故,如果真出事,会产生什么后果。
事故背景
原来抽卡是指抽别人的银行卡,这一点我是真没想到。
2026年1月22日,由鹰角网络开发的游戏《明日方舟:终末地》(以下简称《终末地》)正式全面公测,这次中国大陆和海外同步上线的,而在首发当天国际服发生了重大事故。
在当天游戏上线不久后,在 Reddit、Discord、X等社群平台纷纷出现询问和投诉的文章,内容都是指自己的 PayPal 帐户在未经自己授权的情况下无故扣款,并有人晒出自己的 PayPal 帐户扣款纪录,里面涉及多个不同币种,有美元、欧元、日元等。有受影响玩家涉及金额高达 1.5 万欧元。
在事发后不久,运营商 GRYPHLINE(鹰角子公司)把 Paypal 支付功能关闭,当日晚上发布了公告,指游戏中的 Paypal 支付功能出现问题,现已把支付功能关闭,并且官方承诺会对所有异常消费发起全额退款处理,所有退款预计4小时内完成。
所以为什么玩一个游戏会被无故扣钱呢?
技术分析(以下由 Gemini 说明,这方面叔自愧不如)
这次事故在技术上被称为「P0 级并发安全漏洞」,这是一个极为低级但致命的逻辑错误。它的核心就是「张冠李戴」,即把 A 的支付凭证,错给了 B 的购买请求。这不是骇客攻击,而是程式码本身对「数据隔离」的严重失败。
想像游戏伺服器是一个非常忙碌的「收银台」,为了处理数万人的同时充值,伺服器需要多个「服务员(线程)」同时工作。正常的流程应该是「一个玩家一个本子」,但鹰角却让他们混用了数据。我分析了三种最可能导致这种「串号」的技术原因:
可能性一:程式码内存污染(公共的便条纸)
这是最常见的错误,称为 「非线程安全」。开发者在程式码中,错误地将玩家的 PayPal 密钥(例如:self.token)定义成了所有线程共用的「公共变量」。当玩家 A 的请求正在处理时,玩家 B 的请求插队进来,就会把这个公共变量覆盖成 B 的密钥。当 A 的流程继续执行时,它读取了桌上 B 的密钥去扣款,造成直接盗刷。
可能性二:缓存的 Key 映射错误
为了加快读取速度,游戏伺服器会将玩家密钥短暂存储到一个高速缓存服务中。错误可能发生在:开发者在存储这个密钥时,使用了错误的「标签」(Key)。例如,本该使用 paypal_token:User_A 来存储,却错误地使用了 paypal_token:Current_User 这个固定的 Key。后一个用户的数据就直接覆盖了前面用户的数据,造成缓存数据的混乱,最终导致读取错误的密钥。
可能性三:资料库事务隔离失败
在极端情况下,错误也可能发生在数据库层面。如果程式在读取密钥时,使用了过低的事务隔离级别,有可能在玩家 A 的交易还没完全完成时,玩家 B 的请求就提前读取到了 A 正在操作的不一致的「脏数据」。尽管这种情况不常见,但其本质依然是:系统错误地让两个独立请求的数据发生了交叉污染。
无论是哪种技术细节,这次事故都指向同一个结论:支付系统没有在底层架构上实现数据的绝对隔离。在金融系统中,任何一个数据点的共享或错配,都会导致这种「零和博弈」的灾难——一个人得到了免费的抽卡,另一个人失去了自己的银行存款。
为什么只有 PayPal 出事?
这次事故更精准地暴露了问题:错误只在负责处理 PayPal 交易的程式码模组里。
原因是 PayPal 的「绑定协议」需要伺服器长期暂存一个「扣款密钥(Billing Agreement Token)」。
而像 Google Play 或 Apple Pay,因为它们不需伺服器长期持有密钥,支付环节是单独隔离的,所以没有犯这个错误,成功避开了这场灾难。
事故的连锁反应
最可怕的是这个错误被「无限抽卡」利用后带来的连锁灾难:
- 玩家 A 发现自己没扣钱但拿到了道具,开始疯狂点击购买。
- 每点击一次,伺服器就随机抓取一名受害者 B/C/D 的银行卡扣款。
- 这导致了受害者被在极短时间内连续盗刷,金额飙升到 1.5 万欧元这种天文数字。
- 被盗刷的玩家户口因为「高频异常交易」被银行风控冻结,误判为「洗黑钱」,生活陷入困境。
这是一次业界罕见的 P0 级(最高级别)技术事故,它不仅是金钱的损失,更是对软体工程基本原则的严重违背,应当作为所有后端开发者的反面教材。
系统修复与防范建议(以下依然由 Gemini 说明)
要彻底根除这类 P0 级漏洞,单纯修复变量定义是不够的。开发团队必须从「代码规范」到「测试流程」进行全方位的重构。针对上述的逻辑错误,我们提出了以下三种必须执行的技术改进方案,以确保用户资产的绝对安全。
改进方案一:严格的变量作用域控制(使用 ThreadLocal)
最根本的解决方式是从代码层面隔离数据。开发者应强制弃用任何形式的「全局变量」来存储用户状态。在 Java 或 Python 等后端语言中,应使用 ThreadLocal 或将变量限制在 Request Scope(请求级别作用域)内。确保变量与当前处理的线程生命周期绑定,请求结束即销毁,从物理上杜绝「A 用户读取 B 用户变量」的可能性。
改进方案二:高并发的压力测试(Race Condition 模拟)
常规的功能测试无法发现这类问题,必须引入「并发竞争测试」。测试团队需要编写脚本,模拟数千个线程在 同一毫秒 内发起支付请求(即模拟 Race Condition)。通过脚本验证:当线程 A 发起 PayPal 请求时,线程 B 是否能干扰其返回结果。只有在极限并发下数据依然保持一致,系统才算合格。
改进方案三:引入分布式锁与幂等性检查
为了防止缓存层面的错误,必须在支付接口引入「分布式锁(Distributed Lock)」。当玩家 A 的 ID 正在进行支付操作时,锁定该 ID 相关的所有缓存写入权限。同时,利用 UUID 为每一个支付请求生成唯一的「流水号」,数据库在扣款前必须校验该流水号是否匹配,防止同一笔订单被错误地关联到不同人的账户上。
技术修复只是第一步,建立对金融交易的敬畏之心才是关键。所有的支付代码上线前,都必须经过双人交叉审计(Code Review),并在与生产环境完全隔离的沙盒(Sandbox)中完成不少于 72 小时的稳定性测试。
最后的保险:自动熔断机制
人为的错误无法完全避免,因此系统需要最后一道防线——「自动熔断(Circuit Breaker)」。
监控系统应实时分析交易数据,一旦检测到以下异常指标,应立即自动切断支付服务:
- 单一时间窗口内,退款或报错率超过 1% 的阈值。
- 同一 PayPal Token 在极短时间内出现跨账户调用。
- 用户投诉关键词(如「盗刷」、「没买」)在客服后台激增。
这次事故的教训是惨痛的:在涉及真金白银的代码里,永远不要假设环境是安全的,必须假设每一个变量都可能被污染,并据此建立防御体系。
🍔 【白话文比喻】如何测试才不会「炸厨房」?
我们可以把测试过程想像成「餐厅出餐」的演练:
1. 错误的测试:慢条斯理的「试吃会」
原本的测试可能像这样:
- 测试员 A 扮演顾客,慢慢走进店里点了一份套餐。
- 厨师(伺服器) 慢慢切菜、摆盘、收钱。
- 结果: 一切正常,收钱正确,菜也好吃。
- 盲点: 这种测试环境太「优雅」了。现实世界不是只有一个客人,而是成千上万人同时冲进来。
2. 应该要做的测试:疯狂的「自助餐抢购演习」(并发测试)
为了抓出这种「张冠李戴」的错误,正确的测试应该要这样搞:
- 模拟场景: 找 1,000 个临演(虚拟机器人),在同一秒钟全部冲进餐厅大喊「我要结帐」!
- 观察重点:
- 厨师会不会因为太忙,左手拿著 A 的信用卡,右手却刷了 B 的帐单?
- 厨房的传票(数据)会不会满天飞,导致把桌号 5 的龙虾送到了桌号 3?
- 合格标准: 哪怕场面再混乱,A 的钱只能买 A 的饭。只要有一单搞错,这个系统就不能上线。
3. 最后的保险丝:装一个「防爆开关」(熔断机制)
除了测试,系统还需要一个紧急开关,就像家里的「保险丝」。
- 现实情况: 这次事故中,玩家被连续盗刷了几万欧元,系统却还在傻傻地扣款。
- 正确做法: 系统应该要有一个像「银行风控」一样的警报器。
- 设定规则: 「如果同一个人的 PayPal 在 1 分钟内被使用了 5 次以上」或者「如果不一样的帐号都在刷同一张卡」。
- 触发动作: 警报响起,直接「拉闸断电」!强制停止所有交易。
- 比喻: 这就像如果一个客人在 10 秒内吃了 50 个汉堡,餐厅经理应该立刻报警并停止上菜,而不是继续给他塞汉堡。
个人想法
《终末地》我是有玩的,是好玩的,但同时毛病也很多,这个不在本文章讨论范围。关于 Paypal 支付事件,我首次是在群看到,没多大留意,直到后来我上巴哈才发现事情很大条。
我一开始还只是把当瓜看,我看到有巴哈网友说「你的信用卡已进入卡池。」,我是笑了出来,我是没想到这么刺激,原来抽卡是这个意思,这脑洞......让我叹为观止(震惊)。
后来我再仔细看事情经过,我慢慢笑不出来,我不理解啊,这里简单自我介绍,叔的代码很拉,我现在水平顶多是写一个 hello world 程式,简单说就是无法独自编程,小破站是依赖 AI 帮忙重构完成,它占了很大功劳。
我为什么会笑不出来呢?
如果你不会编程,你也没玩这个游戏,你当乐子看是挺欢快的。但当我看到说拿别人的卡扣款,这.......简单说就是 A 抽卡,B 买单,我是不会手搓代码,但架构能力,逻辑还是有的,就...我很不理解,于是我去问 AI。
它和我说是开发方问题,然后它向我解释这个漏洞是如何发生的,程式码为什么没能将「这是 A 的 Token」和「这是 B 的请求」两者严格地绑定在一起。我看完后是觉得匪夷所思的,久久不能平静,我是真想不出来, 这是......这种感觉以前发生过许多回,我是越想越诧异。
由 Davidmack - 自己的作品, CC BY-SA 3.0, wiki 连结
这是一个典型的瑞士奶酪模型,从开发 -> 测试 -> 上线,每一层都存在漏洞,层层穿透,最终导致了这次事故。
这个 BUG 是隐蔽的,不容易测出来,但同时它也是一个低级错误,说明开发团队缺乏充足经验。
有没有可能是 Paypal 问题?
不可能,如果真是 PayPal 的问题,那么所有使用 PayPal 支付服务的平台都会出事,但事实上只有《终末地》出了问题,这说明问题出在游戏开发上。
有没有可能是编程语言的问题?
这是我在网上看到有人说是 Go 语言的 Bug 导致。回答是不可能,这不是语言问题,是编程逻辑错误,像是一个人说话不清,你不会归因到语种上。
后续影响
这起事故在国内国外没有引起大规模的关注,这对官方本身算是不幸中的大幸,但对玩家而言还是有不少影响。
玩家方面
关于退款方面,官方好像1个小时左右就立马停止支付通道,当天公告会发起全额退款,处理还是很及时的,因为受影响的玩家是一定拿到的,但他们损失不仅是钱,他们帐户有可能会被冻结,因为帐户短时间高频进行多次交易,而且涉及多国币种。
这种情况下银行会认为帐户有异常操作或洗钱嫌疑,然后冻结帐户,玩家就算拿到退款也没用,因为帐户被冻结了,无法提取。
鹰角网络方面
先提退款,PayPal 的规则通常是:当发生退款(Refund)时,PayPal 不会退还当初收取的那笔手续费(通常是 3%-4% + 固定费用),比如误扣100刀,Paypal 手续费为4刀 ,鹰角实收96刀,退款时鹰角是要吐回100刀给玩家。
其次,这个事故会也影响 Paypal 平台,有部份玩家可能会直接找银行申请 Chargeback 把钱讨回来,Paypal 是要先把钱垫付给玩家,然后回头再向鹰角讨回,这其中一定会产生赔偿纠纷。
还有部份玩家误会是支付平台的锅,人家平台后续还要不要和你鹰角合作,搞这么一出,不会明面上打官司,但私下会经常聊天喝咖啡。
欧盟法律(GDPR)可能会对 GRYPHLINE(鹰角的子公司)进行巨额罚款,GDPR 要求企业必须实施足够的技术和组织措施来确保数据的安全性,还有数据处理的公平性与目的限制。这次支付事件就是未经用户授权便进行消费动作,这已触犯了这两个核心条款。但这个机率较低,因为官方有及时处理。
其他方面
短期内所有国产游戏在海外发行都会受到审查收紧和信用污名化,因为此时事故非常恶劣,上面 AI 指这个事故是 P0 级是因为最高只是 P0,但实际程度是达到 P-1 / P-2。
鹰角网络会破产吗?
鹰角网络不会破产,子公司 GRYPHLINE 也短期内不会破产,鹰角会全力保住它,因为在海外宣发已经投了许多钱,沉没成本巨大,它们不会轻易放弃,然而未来几年的财报都不会太好看就是。
鹰角网络会赔20亿吗?
最近流行什么斩杀线理论,官方不会赔这么多钱的,但赔是一定的,不会20亿那么夸张,几百到几千万这个区间吧。上千万是比较坏的走向,但不是没可能,但几百万是跑不掉。
结语
我写这篇文章原因不是要黑游戏,我自己本身有玩过《明日方舟》,所以《终末地》公测时我也去体验,游戏......不差,甚至可以说精良,但也有许多毛病,有点像一坨华丽的......。
扯远了,写这篇文章原因是这两年发生许多事,有些明显是人祸,像之前香港大火,内地预制菜风波,到现在这个游戏支付事故,都有些共通点,都忽略隐性成本。
什么是隐性成本?公关、安全、维护这些都是,企业都不重视,甚至降本增笑,首先想到的都是这些部门,因为它们不能在财报上带来增益,觉得这个东西不赚钱,于是花很多钱投入营销宣发。
鹰角这回也是,这次事故他们还下了一步臭棋,在条款中新增要玩家自愿集体诉讼,前把充值的人得罪,后把其他潜在用户也得罪,这......我不理解。
信息安全方面,很多人,包括我以前也是,忽略了其重要性,以为不出事就没事,不出事是因为有人在默默做事,让它不出事,因为一旦出事就是灾难级的,所谓「善战者无赫赫之功」,你只顾著赚钱,却不顾口袋里的破洞。看现在,这些宣发还要不要做呢,宣发再多也收不来钱,一个游戏最赚钱的时间来这一出。
再唠叨一句,从商业层面,你的信用破产了是很难再谈合作的,这个信用值也是隐性成本,是一个隐形户口,所以这次事故是可以载入「屎册」。
留言 (0)
请先 登录 或 注册後才能發表評論。