白帽故事 · 2025年7月28日

从游戏到钱包:ORM注入攻击如何窃取加密货币?

概述

本月早些时候,Sam Curry 和原文作者发现了他们在实战中遇到的首批可利用的 ORM 注入漏洞,并利用该漏洞从一个在线游戏中实现了加密货币窃取。

事件背景

该游戏是一款即将上线的“付费生成(pay-to-spawn)”射击类游戏,属于大逃杀类型,玩家需要支付一定数量的加密货币才能生成,最后胜者通吃。

遗憾的是,目前这款游戏尚未正式发布,处于封闭测试阶段,没有公开邀请方式,也找不到游戏的二进制文件,但另一位朋友 Justin Rhinehart 在 VirusTotal 找到了上传的游戏文件。

研究过程

取得游戏二进制文件后,启动时由于服务器尚未运行,屏幕几乎空白,于是他们将注意力转向驱动游戏运行的网站。

file

  • 账号注册功能可用
  • 创建设备账户并在网站上探索后取得部分进展
  • 通过修改客户端逻辑,将“isAdmin”标记从“false”替换为“true”,在网站仪表盘中出现了一个隐藏的管理员面板菜单。

file

虽然替换标记使管理员功能可见,但由于权限限制,无法进行数据访问,相关的 API 调用均返回 401 未授权错误,无法在主站点获得权限升级。

进一步探索

发现一个子域名“dev.”,其内容与主站点完全相同,但启用了 Django debug 模式。

API 请求失败时返回详细错误信息,这帮助他们进一步了解网站内部架构,虽然机密密码和关键配置被 Django 屏蔽,但调试堆栈中仍有部分源代码片段可抓取。

关键漏洞实验

API 请求如下,暴露了对后台 ORM 查询的过滤器控制:

POST /api/race/queue HTTP/2
Host: dev.xxx.com
Content-Type: text/plain;charset=UTF-8

{"queue":"default","action":"remove_users","query":{"start_filters":{"id":"123"},"filters":{},"order":[]}}

尝试使用带有 password__contains 过滤器泄露密码哈希:

POST /api/race/queue HTTP/2
Host: dev.xxx.com
Content-Type: text/plain;charset=UTF-8

{"queue":"default","action":"remove_users","query":{"start_filters":{"password__contains":"a"},"filters":{},"order":[]}}

返回如下响应,表示有 140 名用户的密码哈希中包含字母“a”:

{"queue": "default", "deleted": 140}

数据库信息和权限发现

  • Django 默认使用 PBKDF2 方式存储密码,破译密码难度大
  • 未发现会话 cookie、JWT 或密码重置令牌等其它敏感数据
  • 模型包含 is_superuseris_staff 字段,可用于识别管理员账户
  • 利用复合过滤条件,结合 __startswith 查询,暴力猜测管理员邮箱地址

编写脚本自动化邮件地址暴力猜测:

file

进一步利用邮件日志

发现 mail_log 模型,包含 subjectmessage 字段,类似发送给用户的邮件记录。

请求密码重置后,通过 ORM 注入查询 mail_log.message 字段,检索重置链接字符串,成功泄露密码重置链接。

file

file

权限提升与攻击成功

结合管理员邮箱和重置链接,重置管理员密码后成功登录后台。

获得完全管理员权限后,尝试将游戏的钱包中的少量 USDC 转账至自己账户,测试成功。

file

结论与善后

获得了对游戏管理员面板的完全访问权限,包括控制加密货币钱包,转移资金等。

所有发现均已负责任披露,相关公司已发布补丁修复漏洞。

原文:https://blog.p1.gs/writeup/2025/07/06/Hacking-a-crypto-game/