ChatGPT Android 订阅逻辑漏洞深度分析

基于 RevenueCat 集成缺陷的业务逻辑绕过研究

⚠️ 免责声明
本文内容仅用于安全研究与技术交流,请勿将相关技术用于非法用途。任何未经授权的商业利用或攻击行为均可能违反《网络安全法》等相关法律法规。

🔍 核心摘要

本文深入剖析 ChatGPT Android 客户端在集成 Google Play Billing 与 RevenueCat 服务时存在的一处业务逻辑缺陷。由于服务端对客户端提交的订阅凭证缺乏二次校验机制,攻击者可通过两种技术路径实现订阅权益的异常获取。

💡 关键认知:这并非传统"破解",而是典型的服务端信任边界设计缺陷——过度依赖客户端传递的业务参数,未建立完整的凭证校验闭环。


📊 攻击方案对比总览

方案 攻击层面 技术门槛 设备要求 稳定性
方案 A:HTTP 响应篡改 网络层(MITM) ⭐⭐ 中等 无需 Root 依赖服务端策略
方案 B:内存令牌替换 运行时层(Frida Hook) ⭐⭐⭐⭐ 较高 需 Root + Magisk 相对更稳定

🛠️ 方案一:中间人劫持响应数据(免 Root 方案)

原理拆解

ChatGPT Android 应用在初始化时会请求用户账户状态接口:

1
GET https://android.chat.openai.com/backend-api/accounts/check/v4-2023-04-27

返回的 JSON 中包含 eligible_promo_campaigns 字段,用于标识当前账号可享受的促销权益。正常情况下,该字段应由服务端基于账号画像动态生成。

漏洞点:客户端未对响应数据进行完整性校验,攻击者可通过中间人代理工具修改响应包,注入非授权的优惠标识。

前置条件

✅ 基础环境:

  • 任意区域 Google 账号(建议美区)
  • 已绑定有效支付方式的账号(用于通过风控,实际不扣费)
  • Android 测试设备(模拟器或真机均可)

✅ 工具准备:

  • 抓包代理工具:HttpCanary / Charles / mitmproxy
  • Root 管理框架:Magisk(用于安装用户证书)
  • 目标应用:ChatGPT Android 官方客户端

可用促销标识参考

🔹 通用型优惠码(长期有效):

标识符 权益内容 备注
3-day-free-trial 3 天 Plus 试用 基础试用档位
plus-1-month-free-trial 1 个月 Plus 免费 核心利用点
1-month-10-dollars 首月半价($5) 折扣型优惠
pro-winback-1-month-free Pro 挽回试用 需特定账号状态

🔹 限时/定向优惠码(时效性较强):

标识符 权益内容 说明
2wqkodfx51z2x 6 个月免费试用 活动期可用
2ispxs5mtgz35 12 个月免费试用 高价值目标

📌 这些固定标识符的"可复用性",是外部平台持续提供低价订阅服务的底层原因。

响应包构造示例

以下为注入"1 个月免费试用"权益的典型响应结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"eligible_promo_campaigns": {
"plus": {
"id": "plus-1-month-free",
"metadata": {
"plan_name": "chatgptplusplan",
"title": "Promotional Offer",
"summary": "您已获得 1 个月 Plus 会员专属优惠",
"discount": {
"percentage": 100
},
"duration": {
"num_periods": 1,
"period": "month"
},
"no_auto_renewal_at_discount_end": true,
"promotion_type": "discount"
}
}
}
}

🔑 关键字段解析

  • discount.percentage: 100 → 表示 100% 折扣(即 0 元支付)
  • duration.num_periods → 优惠生效周期数
  • no_auto_renewal_at_discount_end → 优惠到期后是否自动续费

完成订阅与凭证提取

  1. 修改响应后重启应用,进入订阅页面即可看到"免费试用"选项
  2. 由于交易金额为 0,Google Play 风控策略相对宽松,仅需完成基础支付验证
  3. 订阅成功后,应用会向 RevenueCat 上报订阅凭证:
1
POST https://api.revenuecat.com/v1/receipts
  1. 拦截该请求,提取响应中的 fetch_token 字段:
1
2
3
4
{
"fetch_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"subscriber": { ... }
}

✨ 该 fetch_token 即为订阅凭证,可用于其他账号的权益激活(注:该行为可能违反服务条款)


🧪 方案二:运行时内存令牌替换(Root + Frida 方案)

技术原理

ChatGPT Android 在调用 Google Play Billing 时,会将当前商品档位的 offerToken 传递给支付组件。攻击者可通过 Frida 框架 Hook 相关内存对象,将付费档位的 token 动态替换为免费试用档位的 token,从而诱导支付系统按"0 元"处理交易。

🔐 本质:在客户端本地篡改计费参数,而非直接攻击服务端接口

环境搭建清单

组件 作用 版本建议
Root Android 设备 执行底层 Hook Magisk 26+
Windows/macOS 主机 运行 Frida 客户端 Python 3.8+
HLUDA Server 绕过 Frida 检测 最新版
ChatGPT APK 目标应用 官方最新版

四步攻击流程

① 枚举计费类实例
通过 Java.choose('c3q') 遍历内存中混淆后的计费类对象,利用反射访问私有字段 b(offerId)与 c(offerToken)。

② 捕获目标 Token
持续监控,当发现 offerId 匹配免费试用档位且 offerTokenAdumm 为前缀时,缓存该 token 备用。

③ Hook 系统级参数传递
对以下三个 Android 框架方法进行插桩:

  • Intent.putExtra(String, String)
  • Bundle.putString(String, String)
  • Bundle.putStringArrayList(String, ArrayList)

在应用发起购买请求时,识别并替换原始付费 token 为缓存的免费 token。

④ 完成"0 元"支付
篡改后的请求被 Google Play 接受,返回成功回执,应用层据此开通相应权益。

核心脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
Java.perform(function () {
const TARGET_ID = "plus-1-month-free-trial";
let capturedToken = null;

// 扫描并捕获目标 offerToken
function scanTokens() {
Java.choose('c3q', {
onMatch: function (obj) {
const bField = obj.getClass().getDeclaredField('b');
const cField = obj.getClass().getDeclaredField('c');
bField.setAccessible(true);
cField.setAccessible(true);

const offerId = bField.get(obj);
const offerToken = cField.get(obj);

if (offerToken?.toString()?.startsWith('Adumm') &&
offerId?.toString() === TARGET_ID) {
capturedToken = offerToken.toString();
console.log(`[+] Captured: ${TARGET_ID}`);
}
},
onComplete: function () {}
});
}

// 定时扫描
setInterval(() => !capturedToken && Java.perform(scanTokens), 3000);

// Hook 参数传递方法
const Intent = Java.use('android.content.Intent');
const Bundle = Java.use('android.os.Bundle');

function swapToken(val, ctx) {
if (!val?.toString?.()?.startsWith?.('Adumm')) return val;
if (!capturedToken) Java.perform(scanTokens);
if (capturedToken) {
console.log(`[🔄 ${ctx}] Token swapped`);
return capturedToken;
}
return val;
}

Intent.putExtra.overload('java.lang.String', 'java.lang.String').implementation =
function(k, v) { return this.putExtra(k, swapToken(v, 'Intent')); };

Bundle.putString.implementation =
function(k, v) { return this.putString(k, swapToken(v, 'Bundle')); };
});

操作指引

  1. 设备刷入 Magisk 并启用 Root
  2. 推送 frida-server(HLUDA 版)至设备并后台运行
  3. 启动 ChatGPT,进入"升级 Plus"页面(先不要点击购买
  4. 主机执行:frida -U -f com.openai.chatgpt -l exploit.js
  5. 观察控制台输出 Token swapped 提示后,再点击购买
  6. Google Play 弹窗显示金额为 $0,完成验证即可

⚠️ 风险提示

  • 脚本兼容性依赖具体应用版本,可能触发闪退
  • 建议在订阅页面加载完成后再注入脚本
  • HLUDA 可绕过部分检测,但无法保证 100% 隐蔽

🧩 漏洞根因深度剖析

🔸 方案一:响应注入类缺陷

根本原因:服务端仅校验优惠码格式有效性,未验证其获取路径与账号资格的匹配性。

修复进展:部分优惠码已绑定账号画像,但通用型标识符仍存在利用空间。

🔸 方案二:Billing 令牌信任问题

根本原因:Google Play Billing 接口设计默认信任客户端传递的 offerToken,缺乏服务端侧的二次资格校验。

修复进展:属平台级设计约束,需谷歌官方推进改进。

🔸 历史方案:凭证批量复用(已修复)

曾存在一种通过 RevenueCat 回调接口批量生成 fetch_token 的方案:

  1. 正价开通 1 个 Pro 账号
  2. 客户端触发"降级"操作,请求新凭证
  3. 未消费的 fetch_token 可重复用于其他账号

修复措施:当前凭证已绑定设备指纹 + 账号 ID + 时间戳,实现"一证一用"。


🛡️ 安全加固建议

面向开发者的防御策略

层面 建议措施
服务端校验 所有订阅状态变更必须与 Google Play / RevenueCat 进行双向确认
凭证管理 fetch_token/offerToken 应绑定设备指纹、用户 ID、时间窗口
优惠逻辑 建立优惠码使用审计日志,检测异常高频/跨账号使用行为
通信安全 移动端强制启用 SSL Pinning,防止中间人劫持
运行时防护 集成 Root 检测、Hook 框架识别、调试器感知等反篡改机制

面向普通用户的风险提示

  • 🔒 利用此类漏洞可能导致账号被永久封禁
  • 💳 绑定的支付方式存在信息泄露风险,建议使用虚拟卡/一次性卡
  • 🔄 漏洞修复具有时效性,不建议作为长期方案依赖

⚖️ 法律与伦理声明

本文内容仅限于安全研究、学术交流与技术防御目的。任何未经授权的商业利用、批量攻击或恶意牟利行为,均可能触犯:

  • 🇨🇳 《中华人民共和国网络安全法》第 27 条
  • 🇨🇳 《计算机信息网络国际联网安全保护管理办法》
  • 🇺🇸 Computer Fraud and Abuse Act (CFAA)

🙏 请遵守"负责任披露"原则,发现漏洞请优先联系厂商修复。


📚 延伸阅读资源