视频加载失败

iOS ChatGPT Pro 20x 订阅链路拆解:商品标识替换与权益绑定

2858 字
14 分钟
iOS ChatGPT Pro 20x 订阅链路拆解:商品标识替换与权益绑定
47工作室微信二维码

47 工作室

这篇文章整理的是一条完整的 iOS 订阅链路:客户端先向 App Store 提交商品购买请求,StoreKit 在支付成功后生成签名交易数据,应用再把交易凭证交给 RevenueCat,由 app_user_id 决定权益最终归属。

真正值得分析的不是某一个可替换字段,而是三个系统之间的信任边界:

  1. App Store 决定用户实际购买了哪个商品。
  2. Apple 签名交易凭证,证明一笔真实交易存在。
  3. RevenueCat 和应用后端把这笔交易映射到具体 ChatGPT 账号。

只看其中一层,很容易把“商品选择”“付款成功”和“账号获得权益”误认为同一件事。下面按网络链路把它们拆开。

一、先看全链路#

一次正常的 iOS 订阅大致经历四段:

ChatGPT iOS 客户端
│
│ ① 提交商品标识、价格与订阅组信息
▼
App Store buyProduct
│
│ ② 完成支付并生成 Apple 签名交易数据
▼
StoreKit Transaction / JWS
│
│ ③ 客户端上传 fetch_token 与 app_user_id
▼
RevenueCat receipts
│
│ ④ 返回 CustomerInfo / entitlement
▼
ChatGPT 后端校验并展示订阅状态

Apple 的职责是确认交易,RevenueCat 的职责是管理订阅状态,ChatGPT 账号系统则负责识别用户。问题通常出现在跨系统映射,而不是密码学签名本身。

二、研究环境#

设备与工具#

项目用途
已越狱 iPhone安装网络调试插件并观察 App 流量
Sileo管理越狱软件包
SSL Kill Switch 3在测试设备上处理证书锁定
Reqable抓取、断点和重写 HTTP 请求
上游网络代理保证测试网络能够正常访问目标服务
Windows 或 macOS 电脑运行 Reqable,与手机处于同一局域网

代理链路#

电脑端让 Reqable 监听 9000,再把它的二级代理指向本机上游代理端口。手机 Wi-Fi 的手动代理填写电脑局域网地址和 9000 端口。

iPhone
│ Wi-Fi 手动代理 :9000
▼
Reqable
│ 二级 HTTP 代理 127.0.0.1:7890
▼
上游网络代理
│
▼
App Store / RevenueCat / ChatGPT

这个结构有两个好处:Reqable 可以完整看到应用流量,上游代理也不需要直接安装到手机。若电脑局域网地址为 192.168.1.20,手机代理服务器就填写该地址,而不是 127.0.0.1。

三、第一条链:App Store 商品标识替换#

1. 定位购买请求#

在 ChatGPT App 中进入:

设置 → 订阅 → 查看所有方案

选择一个可见方案并进入系统确认页,但先不要完成确认。此时在 Reqable 中定位 App Store 的购买请求:

POST https://p44-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct

请求体是 Apple Plist 数据。分析时最重要的字段如下:

字段作用
appAdamIdApp Store 中的应用 ID
bid应用 Bundle ID,本次样本为 com.openai.chat
offerName订阅商品的内部方案名
salableAdamIdApp Store 可售商品 ID
mtSubscriptionAdamId订阅组 ID
price交易价格的 milliunit 表示
buySubscription是否按订阅商品处理

2. 本次样本中的商品映射#

下面是 2026 年 9 月流量样本中出现的映射。它们属于服务端可变数据,不应写死到长期运行的自动化程序中。

方案offerNamesalableAdamIdmtSubscriptionAdamIdprice
Go 月付 8 美元oai_chatgpt_go_1000_1m6749460546无8000
Plus 月付 19.99 美元oai_chatgpt_plus_1999_1m6448311597674946054619990
Plus 年付 200 美元oai_chatgpt_plus_20000_1y67454162896749460546200000
Pro 5x 月付 100 美元oai_chatgpt_pro_10000_1m67598174416749460546100000
Pro 20x 月付 200 美元oai_chatgpt_pro_20000_1m66579544056749460546200000

这里最容易误读的是 price。200000 不是 200000 美分,而是以千分之一货币单位表示的 200 美元。Apple 的 StoreKit 文档也明确区分了 API 中的货币单位与 JWS 载荷中的 milliunit。

3. 断点重写逻辑#

以“Plus 年付请求切换到 Pro 20x 月付”这一观察样本为例,需要保持应用和订阅组上下文不变,只调整商品映射:

offerName = oai_chatgpt_plus_20000_1y
salableAdamId = 6745416289
offerName = oai_chatgpt_pro_20000_1m
salableAdamId = 6657954405
price = 200000

放行请求后,最终判断依据不是 Reqable 中“请求已发送”,而是 iOS 原生购买确认页展示的方案名称、计费周期和价格是否同时一致。三项只要有一项不匹配,就说明商品映射并未形成有效闭环。

这一步只改变 App Store 正在处理的商品选择。它不会自动决定订阅最终归属哪个 ChatGPT 账号。

四、第二条链:交易凭证与账号权益绑定#

1. 为什么要单独观察回调#

App Store 支付完成后,ChatGPT App 会把 Apple 交易数据提交给订阅管理层。样本中涉及两个关键端点:

https://api.revenuecat.com/v1/receipts
https://ios.chat.openai.com/backend-api/payments/rc/ios/verify/v4-2023-04-27

如果目的是研究“交易凭证如何映射到账号”,需要在付款前为这两个端点设置断点或临时阻断规则,同时保留完整请求记录。否则客户端会立即完成上传,当前登录账号会先进入正常绑定流程。

2. RevenueCat 请求中的核心字段#

POST /v1/receipts 的请求通常同时携带交易证明和用户标识。下面只保留结构,所有具体令牌均使用占位符:

{
"fetch_token": "<Apple 签名交易数据>",
"app_user_id": "<ChatGPT Account ID>",
"product_id": "oai_chatgpt_pro_20000_1m",
"price": 200,
"store_country": "USA",
"transaction_id": "<App Store Transaction ID>"
}

对应请求头的结构类似:

Authorization: Bearer <RevenueCat public SDK key>
X-Client-Bundle-ID: com.openai.chat
X-Platform: iOS
X-StoreKit2-Enabled: true

这些字段可以分成两类:

类型字段含义
交易证据fetch_token、transaction_id、product_id证明买了什么,以及交易是否真实
归属标识app_user_idRevenueCat 用来定位 Customer 与权益归属的用户 ID

RevenueCat 官方文档把 App User ID 定义为客户识别的核心标识。同一个 App User ID 可以在不同设备上恢复同一份订阅状态,匿名 ID 与自定义 ID 也可能通过登录或恢复购买形成 alias。

3. 观察账号迁移行为#

在流量样本中,保持 Apple 签名数据和交易字段不变,仅替换 app_user_id,可以直接观察订阅系统是否把同一笔有效交易映射到另一名 Customer:

fetch_token = <保持原值>
app_user_id = <账号 A 的 Account ID>
app_user_id = <账号 B 的 Account ID>
product_id = <保持原值>
transaction_id = <保持原值>

判断是否发生归属变化,应同时检查三处:

  1. RevenueCat 响应中的 subscriber.original_app_user_id 与 aliases。
  2. entitlements 中的产品、购买时间和过期时间。
  3. ChatGPT 后端校验完成后的实际账号状态。

仅看到 HTTP 200 不等于权益已经稳定落到目标账号。响应、后端校验与客户端展示三者一致,才算完整闭环。

五、成功响应应该怎么看#

经过裁剪的响应结构如下:

{
"request_date": "2026-09-17T07:47:20Z",
"subscriber": {
"entitlements": {
"chatgpt_pro": {
"product_identifier": "oai_chatgpt_pro_20000_1m",
"purchase_date": "2026-09-17T07:38:26Z",
"expires_date": "2026-10-17T07:38:26Z"
}
},
"original_app_user_id": "<目标账号 ID>",
"subscriptions": {
"oai_chatgpt_pro_20000_1m": {
"price": {
"amount": 200,
"currency": "USD"
},
"store": "app_store",
"period_type": "normal",
"ownership_type": "PURCHASED",
"expires_date": "2026-10-17T07:38:26Z"
}
}
}
}

重点字段有四组:

  • entitlements:当前 Customer 获得的可用权益。
  • product_identifier:权益对应的真实商品。
  • original_app_user_id:RevenueCat 记录的最初用户标识。
  • subscriptions:交易来源、价格、周期和到期时间。

若 original_app_user_id 没变化,但 aliases 中出现了新账号,说明系统可能执行了身份合并,而不是简单覆盖。后续查询任何一个 alias 都可能返回同一份 CustomerInfo,这一点在复盘时必须区分。

六、常见失败点#

抓不到 HTTPS 明文#

通常是证书没有正确安装、证书锁定仍然生效,或者请求走了未纳入代理的网络栈。先用普通 HTTPS 页面确认手机到 Reqable 的基础代理链路,再检查 SSL Kill Switch 3 的启用状态。

App Store 请求出现,但确认页方案没变#

优先核对 offerName 与 salableAdamId 是否属于同一个商品。只改显示名称或只改价格,不能替代 App Store 对商品 ID 的识别。

支付完成后抓不到 receipts#

检查阻断规则是否直接丢弃了请求。如果要分析请求体,应使用断点或保留请求副本,而不是无日志地拒绝流量。

RevenueCat 返回成功,ChatGPT 仍未显示权益#

这通常说明订阅管理层与应用后端的状态尚未一致。继续检查 verify 请求、账号 ID、产品 entitlement 名称,以及服务端是否进行了交易 ID 去重或二次验证。

同一凭证再次提交失败#

Apple 交易 ID、RevenueCat Customer 和应用后端都可能执行幂等或重放校验。一次有效响应不能推出交易凭证可以无限复用。

七、漏洞根因不是“价格能改”#

从安全设计角度看,真正的风险不是客户端能否改动 Plist。客户端请求天然不可信,关键在于服务端是否把以下关系绑定成一个不可拆分的授权决策:

Apple 签名交易
+ Bundle ID
+ Product ID
+ Subscription Group
+ Original Transaction ID
+ ChatGPT Account ID
+ RevenueCat Customer / aliases
= 唯一可接受的权益记录

只验证 Apple 签名,最多能证明“这笔交易是真的”;只相信 app_user_id,最多能知道“客户端希望把权益给谁”。只有服务端验证两者之间的既定绑定关系,才能证明“这笔真实交易就应该给这个账号”。

八、服务端加固建议#

1. 服务端验证 Apple JWS#

不要把客户端上传的解析结果直接当成交易事实。服务端应验证 JWS 签名、证书链、Bundle ID、环境、商品 ID、交易 ID 和签名时间,并使用 App Store Server API 做必要复核。

2. 建立不可变交易账本#

以 originalTransactionId 和 transactionId 建立唯一索引,首次绑定时同时写入内部账号 ID。后续恢复购买可以走明确的账号迁移流程,但不能由客户端任意指定新归属。

3. 校验商品组合#

服务端维护允许销售的 product_id、订阅组、价格与地区映射。客户端传来的 offerName、price 或 salableAdamId 只能作为提示,不能作为结算依据。

4. 正确处理 RevenueCat aliases#

Webhook 对账时同时检查 original_app_user_id 与 aliases,避免一次身份合并让多个业务账号共享同一份权益。用户切换账号时,应显式定义“迁移”“恢复”与“拒绝”的策略。

5. 把成功拆成多个状态#

至少区分:

purchase_created
apple_transaction_verified
revenuecat_customer_updated
backend_entitlement_granted
client_state_refreshed

这样即使第三方回调延迟或失败,也不会用一个模糊的“支付成功”掩盖权益尚未完成落账的事实。

九、结论#

iOS 订阅并不是一个请求,而是一组跨系统状态转换:

  1. buyProduct 决定送往 App Store 的商品选择。
  2. StoreKit 生成带 Apple 签名的交易事实。
  3. fetch_token 把交易事实交给订阅管理层。
  4. app_user_id 把订阅状态映射到具体 Customer。
  5. ChatGPT 后端最终决定业务账号能否获得权益。

商品标识替换与账号权益迁移看似是两种操作,本质上都在检验同一件事:服务端有没有把“谁付款、买了什么、交易是否唯一、权益属于谁”绑定为一条不可被客户端拆开的信任链。

参考资料#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

相关文章智能推荐
1
GPT Plus 订阅转移全解析 - 从 Google Play Token 到一键开通的完整技术链路
安全研究深度剖析 GPT Plus Android 订阅架构:Google Play purchase token 如何通过 RevenueCat 中间层实现跨账号转移,从原理到 ADB 提取、MITM 拦截、API 调用的完整实战指南。
2
提链 — ChatGPT 跨区支付链接提取的完整技术拆解
漏洞分析完整拆解 ChatGPT 提链技术:从 Session Token 出发,通过 12 步协议流程构造 Stripe Checkout Session,最终提取 iDEAL/UPI/PIX/Kakao Pay 等本地支付链接。附带八种支付方式的技术对比和完整代码分析。
3
0 PHP 白嫖 ChatGPT Plus — 跨区定价混淆攻击的完整技术拆解
漏洞分析深度拆解最近在群里疯传的 ChatGPT Plus 0 PHP 开通方法:日区注册 → 美区提 Token → 第三方工具生成 Checkout → 菲律宾 BIN 完成支付。跨区信号碎片化制造歧义,第三方工具利用异常路径注入零元参数。三种假说的完整技术分析。
4
GPT-5.6-Sol 隐藏模型无限调用指南 - 任意付费账号不消耗额度(2026 年 8 月实测)
AI 工具2026 年 8 月实测,任意 ChatGPT 付费账号(Plus/Pro/Team)通过 Codex 调用隐藏的 gpt-5.6-sol-wm 模型,不消耗额度、不计入消费明细,附完整配置教程
5
GPT Plus 订阅漏洞深度分析 - Google Play Billing 鉴权缺失导致 0 元订阅
漏洞分析技术拆解:通过 API 响应注入和 offerToken 内存替换两种方式,利用 Google Play Billing 与 RevenueCat 回调鉴权缺失实现 GPT Plus/Pro 零元订阅。
随机文章随机推荐
Profile Image of the Author
47工作室
47工作室:记录技术实践与安全研究。
公告
欢迎来到 47工作室!微信:gzs-47
分类
标签
最新动态
站点统计
文章
42
分类
18
标签
175
总字数
129,269
运行时长
0 天
最后活动
0 天前