谨防新型iOS内购欺诈,能够闯过服务端凭证校验,成功发货

10,104 阅读7分钟

2023年3月中旬开始,出现了一种新型iOS内购欺诈方式,已有多家公司产品被攻击。黑产可以顺利闯过被攻击产品的服务端凭证校验,成功发货。黑产甚至光明正大的在在淘宝上代充,50元至80元,代充一个648元商品。

具体地,黑产别的App上低价充值(比如1元)换取苹果真实凭证,再在目标App上下单高价(648元)商品,传入该凭证,如果目标App服务端苹果凭证校验接口存在漏洞,只校验了凭证中商品和订单信息,未校验凭证中App来源(bundleID),则会验证通过,进而发货。对黑产来说,黑产实际只支付了1元,就能买到目标App的648元商品;对于目标App来说,他连这1元都没得到(因为是在别的App上充的),完全被白嫖648元商品。可谓伤害极大。

还原盗刷步骤:
1.黑产通过破解(比如抓网络请求)目标App客户端,获得648元商品的苹果商品id(product_id)值。
2.黑产自己上架一款App,在苹果后台添加一款1元的内购商品,设置苹果商品id(product_id)值和目标App一致。
3.黑产在自己的App里真实付款1元商品,拿到苹果返回的真实凭据(receipt)和苹果交易id(transaction_id)。
4.黑产破解目标App客户端后,直接调目标App的苹果凭证校验接口,把上面获得的凭据(receipt)和苹果交易id(transaction_id)作为参数传进去。
5.目标App服务端拿着凭据去苹果后台校验,由于凭据是真实的,苹果验证通过。目标App服务端解析凭证,校验凭证内参数,核对product_id正确、核对transaction_id唯一性通过,全部校验通过,发货。
iOS盗刷流程图.png

漏洞的主要原因
1.苹果后台,苹果商品id(不同开发者账号下)可以重复
2.目标App服务端凭证校验接口,未校验凭证中App来源(bundleID)
利用这两点,黑产上演了一出狸猫换太子。 这个攻击看似很初级,但我问了身边的好几位朋友,他们的服务端竟然都存在这个漏洞,让人惊讶。

“伪造的”凭证长什么样?
下面是被攻击App服务端提供的一段日志,记录了“伪造的”凭证样式,该日志可以验证我上述猜测。

  • 最外层的bundle_id,并不是目标App的包名,而是一个不认识的App包名。这个就是黑产真实充值的App。
  • in_app数组里面product_id,却是目标App里有效的苹果商品id,正是648元商品的product_id。
  • 再看transaction_id。这里的transaction_id实际上是黑产App内充值产生的,并不是目标App内产生的。因为transaction_id只有在苹果内购付款成功后才会生成,且只能由客户端传给服务端,所以服务端是没法校验transaction_id是否来源于自己的App。服务端能做的就是判断transaction_id是否重复,防止重放攻击。因为黑产每次充值都会真实的充一笔,所以transaction_id并不会重复,这也是目标App服务器验证transaction_id通过的原因。
{
  "receipt_type": "Production",
  "adam_id": 6441231333,
  "app_item_id": 6441231333,
  "bundle_id": "com.apps.slanCaizhuang", //这并不是目标App的bundleID,而是黑产自己的的App
  "application_version": "3",
  "download_id": 502222222222222222,
  "version_external_identifier": 855304439,
  "receipt_creation_date": "2023-04-12 16:59:25 Etc/GMT",
  "receipt_creation_date_ms": "1681318765000",
  "receipt_creation_date_pst": "2023-04-12 09:59:25 America/Los_Angeles",
  "request_date": "2023-04-12 17:16:54 Etc/GMT",
  "request_date_ms": "1681319814498",
  "request_date_pst": "2023-04-12 10:16:54 America/Los_Angeles",
  "original_purchase_date": "2023-03-11 05:00:05 Etc/GMT",
  "original_purchase_date_ms": "1678510805000",
  "original_purchase_date_pst": "2023-03-10 21:00:05 America/Los_Angeles",
  "original_application_version": "3",
  "in_app": [
    {
      "quantity": "1",
      "product_id": "com.xiaomi.haha123", //这里却是目标App的product_id
      "transaction_id": "210000000000000",//黑产自己App上支付产生的transaction_id
      "original_transaction_id": "210000000000000",
      "purchase_date": "2023-04-12 16:59:25 Etc/GMT",
      "purchase_date_ms": "1681318765000",
      "purchase_date_pst": "2023-04-12 09:59:25 America/Los_Angeles",
      "original_purchase_date": "2023-04-12 16:59:25 Etc/GMT",
      "original_purchase_date_ms": "1681318765000",
      "original_purchase_date_pst": "2023-04-12 09:59:25 America/Los_Angeles",
      "is_trial_period": "false",
      "in_app_ownership_type": "PURCHASED"
    }
  ],
  "environment": "Production",
  "status": 0
}

如何修复漏洞?
(1)服务端凭证校验时,加上bundleID的校验即可
即服务端去苹果那边校验通过后,还需核对凭证内以下参数:

  • bundle_id,是否为你们自己的App(防止跨App充值);
  • product_id,是否为下单时对应的商品id(防止App内部以小博大);
  • transaction_id,是否已经发过货(防止重放攻击)

(2)共享密钥(推荐)
感谢网友“用户3525508872617”在评论区的留言,他提示可以通过“共享密钥”来避免这个问题。我今天专门研究了一下“共享密钥”,原来官方接口文档上还真有这个参数啊,只不过是选传的!

具体地,服务端请求苹果凭证校验接口时,除了传receipt-data字段,再额外传一个password参数(苹果后台生成的共享密钥)。这样苹果那边核对凭证时,除了验证凭证是否有效,还会核对凭证和密钥是否匹配。如果不匹配苹果会返回错误信息

{
    environment = Sandbox;
    status = 21003;
}

{
  "environment": "Production",
  "status": 21004
}

21003 收据无法被验证(共享密钥校验失败,沙盒环境)
21004 你提供的共享密钥和账户的共享密钥不一致(共享密钥校验失败,生产环境)

关于共享密钥,官方文档上写道:

验证订阅或 App 内购买项目时,你的服务器会与 Apple 服务器进行通信。为提高通信安全,请在发送的收据中嵌入共享密钥。

共享密钥是在 App Store Connect 中生成的十六进制字符串,由 32 个字符组成。你可以生成一个主共享密钥,用于你的所有 App;也可以为单个 App 生成 App 专用共享密钥。你还可以设置部分 App 使用主共享密钥,其他 App 使用 App 专用共享密钥。

更多关于共享密钥的信息,可以查看下列资料:
【官方文档】什么是共享密钥如何创建共享密钥
【官方文档】共享密钥何时失效
【博客】共享密钥使用实战

如何报复黑产?
好人做到底。
苹果官方提供了一个lookup接口,可以通过bundleId(包名)查询App商店信息(App名称、商店详情页url、App图标等)。
https://itunes.apple.com/lookup?bundleId=com.apps.slanCaizhuang
知道了是哪个App在搞你,后续你知道该怎么做了吧,哈哈哈。

图片.png