Skip to content

虚拟商品与内购 (IAP) 防御 — 收据服务端直验、防重放与铸币防护 ​

配对攻击手册:payment-digital-goods.md。数字商品与游戏内购没有物流缓冲,任何一次漏验或重放都会直接转化为永久资产损失;防御的核心是“官方 S2S 强校验 + 环境绝对隔离 + 交易号全局唯一消费”。

威胁模型与控制映射 ​

攻击向量典型缺陷防御控制措施
iOS 伪造收据与沙盒混用仅在客户端校验、信任自签名 JSON、生产库接受 Sandbox 收据(21007 盲目回退)使用 App Store Server API (JWS 签名校验 Apple 根证书);生产环境严禁自动回退 Sandbox 发货
Google Play purchaseToken 复用与测试卡未校验 purchaseState == 0 / consumptionState、未检查 purchaseType 过滤测试订单向 androidpublisher 直验后核对 productId、purchaseState==0、purchaseType != 0(拒绝测试卡),并先 acknowledge/consume 再发权益
跨账号收据重放同一 transaction_id/purchaseToken 可给多个业务账号发货数据库建立 UNIQUE(platform, transaction_id) 消费账本,并绑定 obfuscatedAccountId / appAccountToken
License Key / 卡密预测使用自增 ID、时间戳、CRC32 或线性同余生成序列号使用 secrets.token_bytes 生成 ≥ 128-bit 随机码或服务端 Ed25519 签名载荷;在线兑换严格限速与锁定
虚拟货币铸币与负余额前端上报金币增量、整除溢出或负值转账禁止客户端传入余额增量;采用不可变复式记账流水表(Ledger Entry)+ 数据库 CHECK (balance >= 0)
下载链接与重复发货并发请求发货接口重复发放道具,或静态 URL 无鉴权永久有效发货状态机 CAS + 唯一发货记录表;下载链接采用短时效(≤ 5 分钟)HMAC 预签名 URL 且绑定 user_id

核心加固实现 ​

1. IAP 官方回执强制校验与全局唯一消费账本 ​

python
# iap_receipt_guard.py — IAP 环境隔离、字段校验与防跨账号重放
from dataclasses import dataclass

@dataclass(frozen=True)
class VerifiedReceipt:
    platform: str          # "apple" | "google"
    transaction_id: str
    bundle_id: str
    product_id: str
    environment: str       # "Production" | "Sandbox"
    purchase_state: int    # 0 = purchased
    is_test_purchase: bool
    account_binding: str

class IapVerificationError(PermissionError):
    pass

class IapConsumptionLedger:
    def __init__(self, expected_bundle_id: str, allow_sandbox: bool = False):
        self.expected_bundle_id = expected_bundle_id
        self.allow_sandbox = allow_sandbox
        self.consumed_tx: dict[tuple[str, str], str] = {}

    def verify_and_consume(self, receipt: VerifiedReceipt, current_user_id: str, expected_product_id: str) -> bool:
        if receipt.bundle_id != self.expected_bundle_id:
            raise IapVerificationError("bundle_id mismatch")
        if receipt.environment != "Production" and not self.allow_sandbox:
            raise IapVerificationError("sandbox receipt rejected in production")
        if receipt.is_test_purchase and not self.allow_sandbox:
            raise IapVerificationError("test card purchase rejected in production")
        if receipt.purchase_state != 0:
            raise IapVerificationError("purchase not completed")
        if receipt.product_id != expected_product_id:
            raise IapVerificationError("product_id mismatch (SKU swap blocked)")
        if receipt.account_binding and receipt.account_binding != current_user_id:
            raise IapVerificationError("receipt bound to another account")

        key = (receipt.platform, receipt.transaction_id)
        if key in self.consumed_tx:
            raise IapVerificationError("transaction_id already consumed (replay blocked)")

        self.consumed_tx[key] = current_user_id
        return True

2. 高熵卡密生成、常量时间校验与防铸币账本 ​

python
# digital_goods_security.py — 密码学安全卡密与防铸币复式记账
import hashlib
import hmac
import secrets

def issue_cryptographic_cdkey(pepper: bytes, batch_id: str) -> tuple[str, str]:
    """返回 (展示给用户的卡密, 仅入库保存的 HMAC-SHA256 摘要)。"""
    raw_token = secrets.token_urlsafe(18)  # 144-bit 熵,不可预测
    code = f"{batch_id}-{raw_token}"
    digest = hmac.new(pepper, code.encode("utf-8"), hashlib.sha256).hexdigest()
    return code, digest

def verify_cdkey_digest(pepper: bytes, submitted_code: str, stored_digest: str) -> bool:
    computed = hmac.new(pepper, submitted_code.encode("utf-8"), hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, stored_digest)

def transfer_virtual_currency(balances: dict[str, int], sender: str, receiver: str, amount: int) -> None:
    if type(amount) is not int or amount <= 0:
        raise ValueError("amount must be positive int")
    if sender == receiver:
        raise ValueError("self transfer blocked")
    if balances.get(sender, 0) < amount:
        raise ValueError("insufficient balance")
    balances[sender] -= amount
    balances[receiver] = balances.get(receiver, 0) + amount

if __name__ == "__main__":
    ledger = IapConsumptionLedger(expected_bundle_id="com.example.app", allow_sandbox=False)
    valid = VerifiedReceipt("google", "GPA.1234-5678", "com.example.app", "gem_pack_100", "Production", 0, False, "u_42")
    assert ledger.verify_and_consume(valid, "u_42", "gem_pack_100") is True
    # 测试重放与测试卡拦截
    for bad in (
        valid,  # 重复提交同 tx
        VerifiedReceipt("apple", "tx_99", "com.example.app", "gem_pack_100", "Sandbox", 0, False, "u_42"),
        VerifiedReceipt("google", "GPA.9999", "com.example.app", "gem_pack_100", "Production", 0, True, "u_42"),
    ):
        try:
            ledger.verify_and_consume(bad, "u_42", "gem_pack_100")
            raise AssertionError("should have blocked invalid receipt")
        except IapVerificationError:
            pass
    pepper = b"server-only-cdkey-pepper"
    cdkey, digest = issue_cryptographic_cdkey(pepper, "B2026")
    assert verify_cdkey_digest(pepper, cdkey, digest)
    print("payment-digital-goods-defense self-check passed")

防御工作流 ​

text
1. 剔除本地校验:废弃客户端“支付成功即发货”逻辑,强制服务端通过 App Store Server API / Play Developer API 直验。
2. 隔离沙盒与测试卡:生产节点配置显式禁用 Sandbox 自动回退与 test purchase 类型订单。
3. 交易号唯一账本:落库前先插入 `UNIQUE(platform, transaction_id)` 记录,失败立即中止发货。
4. 卡密密码学化:替换所有自增/CRC 算法卡密,改为 128-bit+ CSPRNG 生成并仅在库内存储加盐摘要,兑换接口加 IP+账号双维度限流。
5. 资产防重发:数字资产下载链接使用短期签发 URL(绑定 user_id 与过期时间),发货操作加订单行锁。

验证与判定标准 ​

  • 沙盒与测试订单零通过:向生产验证接口提交 Sandbox 收据或 purchaseType=0 测试卡回执,返回 403 且不生成发货记录。
  • 跨账号重放零命中:同一 transaction_id 在账号 A 核销后,使用账号 B 并发或串行重放返回 409 Conflict。
  • 卡密空间不可枚举:生成的卡密样本满足 ≥ 128-bit 熵且库内不存明文;连续错误兑换 5 次触发锁定或指数退避。

MCP 工具映射 ​

防御环节MCP 工具用途
内购与兑换接口检查http_probe验证收据重放、SKU 错配与兑换暴力请求拦截响应
并发与支付防线关联kb_router检索丢失更新防护与速率限制防御文档
配对攻击细节对照kb_read_file对照读取 ctf-website/12-payment/payment-digital-goods 复核防御点
数字商品防御索引kb_catalog浏览支付与业务安全防御条目

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明