payment

売上は変わらないのに通貨だけ増えるなら — アプリ内課金の検証の穴

「精算書と通貨付与ログが合いません」— ある月曜朝の突き合わせ作業

月初の精算を合わせていた運用担当者が、おかしな点に気づきます。ストアコンソールに記録された決済件数より、ゲームサーバーが付与した有料通貨のほうがはるかに多いのです。差は数件ではなく数百件あります。

アカウント別に見ていくとパターンが浮かびます。あるアカウントは同じ商品を一日に四十回以上「購入」しているのに、ストアには記録が一件もありません。別のアカウントは決済直後に返金を受けているのに、通貨はそのまま残っています。

この時点でよく出てくる反応が「ストアの決済が破られたのか」です。その可能性は低いでしょう。Google と Apple の決済処理そのものは堅牢です。攻撃者が狙うのは決済ではなく、決済が終わったとゲームが判断して通貨を渡す瞬間です。

課金クラックは決済ではなく付与を狙う

アプリ内課金は次のように進みます。

  1. ユーザーがストアで決済する
  2. ストアが購入の証跡(レシート・トークン)をクライアントに返す
  3. ゲームがその証跡を確認して通貨を付与する

1と2はストアの領域なので手を出しにくいところです。問題は3です。証跡を誰が、どこで確認するかによって、この段階は堅牢にもなれば事実上の無防備にもなります。

ゲームがクライアントの中だけで証跡を確認しているなら、確認するコードも攻撃者の手が届く場所にあります。課金回避ツールがやっているのはまさにこれです。ストアを欺くのではなく、ゲームに「決済は終わった」と伝えているのです。

改ざんが入り込む四つの扉

現場で繰り返し観測される類型は四つです。

1. クライアントだけで検証している場合

最も多く、最も大きく破られます。決済応答を横取りして成功に書き換えるか、検証関数の戻り値を固定すれば、そのまま通ります。ストアには記録が何も残りません。

2. レシートの再利用(リプレイ)

一度正規に購入したレシートを繰り返し提出する手口です。サーバーは検証していても、すでに使ったレシートかどうかを確認しなければ、同じ証跡で何度でも通貨を受け取れます。精算と付与量が乖離する代表的な原因です。

3. 返金後も残る権利

決済して、通貨を受け取り、返金します。ストアでは代金が戻る一方、ゲーム内の通貨はそのままです。これを繰り返せば事実上ただで通貨を刷れます。

4. 保留状態を成功として扱う場合

決済がまだ確定していない状態があります。これを成功として処理すると、決済が結局完了しなくても通貨はすでに出た後です。

四つに共通点があります。決済を破った攻撃は一つもありません。 どれもゲーム側の検証手順の穴を突いただけです。

検証ライフサイクル — 付与の前に終えるべきこと

「レシート検証」を一度きりの確認と考えると、上の2と3を取りこぼします。検証は購入時点のイベントではなく、購入後まで続く手順です。

段階 やること 抜けると
受け渡し クライアントが受け取った証跡をサーバーへ渡す クライアント検証だけが残り1に露出
検証 サーバーがストアAPIで証跡の真正性を確認する 偽造された証跡が通る
重複確認 すでに処理した証跡かどうか突き合わせる 再利用(2)に露出
付与 確定状態のときだけ通貨を入れる 保留状態での付与(4)
承認 処理完了をストアへ伝える 自動返金による売上の損失
事後 返金・キャンセルを受けて権利を回収する 返金の悪用(3)に露出

六段階すべてがサーバーで起きる必要があります。クライアントの役割は証跡を渡すところまでです。

Google Play:サーバーで確認すべきこと

Google はPlay 課金のセキュリティガイドでバックエンド検証を明示的に推奨しています。文書が挙げる要点は次のとおりです。

  • クライアントが受け取った purchaseToken をサーバーへ送り、すべてのトークン値をサーバーに記録する。
  • このトークンはグローバルに一意で、主キーとして使っても安全である。以前に受け取ったトークンかどうかをまず突き合わせる。
  • Google Play Developer API の Purchases.products:get、Purchases.subscriptionsv2:get で実際の購入を確認する。
  • obfuscatedAccountId または obfuscatedProfileId でアカウント単位の資格と濫用の有無も併せて見る。
  • 購入状態が確定(PURCHASED)のときだけ付与する。保留状態では付与しない。
  • 有効でない購入は Orders:refund を revoke=true で呼び出し、明示的に回収する。

ここで見落としやすい運用上の落とし穴が一つあります。購入を承認(acknowledge)しないまま3日が過ぎると自動的に返金されます。 検証ロジックは問題ないのに承認の呼び出しだけを忘れて売上が漏れる例は実際にあります。

App Store:古いやり方をそのまま使っていないか

Apple 側で確認することは少し違います。検証の方式そのものが変わりました。

以前はレシートデータを Apple サーバーの verifyReceipt エンドポイントに送って確認していました。この方式はレガシーとなり、現在はApp Store Server APIで取引情報を照会する方式が標準です。この API は取引情報を JWS で署名された形で返すため、サーバーが Apple の公開証明書で署名を検証できます。

古い記事や数年前のサンプルコードをそのまま持ち込んだプロジェクトなら、いまどの方式で検証しているのか一度確認してみる価値があります。具体的な手順と最新の状態は必ず公式ドキュメントで確認してください。課金まわりの API はこの記事より速く変わります。

付与の前に点検する項目

当てはまらない項目があれば、そこがいま開いている扉です。

検証の手順

  • レシート・トークンの検証をサーバーで行っている。
  • ストアAPIで実際の購入かどうかを確認している。
  • 処理したトークンを保存し、付与の前に重複を突き合わせている。
  • 購入状態が確定のときだけ付与し、保留状態では付与しない。

購入のあと

  • 付与後にストアへ承認処理を送っている。
  • 返金・キャンセルの通知を受けて通貨や権利を回収している。
  • 有効でない購入を見つけたら明示的に回収している。

アカウントと運用

  • 購入をアカウントに紐づけて記録し、同一アカウントの異常な反復購入を確認できる。
  • 精算金額と付与量を定期的に突き合わせている。
  • 決済要求を送ったクライアントが改ざんされていないかも併せて判断している。

OZero Security が担う領域と担わない領域

このテーマでは、担わないことから先にお伝えするのが筋でしょう。

担わない領域

  • レシートや購入トークンの検証を代行しません。 ストアAPIを呼び出して購入の真正性と状態を確認するのは、みなさんのサーバーの仕事です。私たちがその位置を代われるわけではありませんし、代われるとも主張しません。
  • 重複処理の防止、返金時の回収、精算の突き合わせも同様にサーバーと運用の担当です。

担う領域

  • 検証を要求するクライアント自体が信頼できるかを判断します。実行ファイルのバイナリ改ざん、デバッガ接続とフッキング、エミュレータやルート化のような非信頼の実行環境を識別し、回避の難度を高めます。
  • MOD APK のリパッケージや再署名の改ざんを検知し、プロジェクトのポリシーに応じて対応します(ログ・コールバック・終了)。課金ロジックを書き換えて再配布されたビルドがこれに当たります。
  • Pro Add-on と連携すると、疑わしいクライアントのテレメトリを収集し、バックオフィスで運用ポリシーに沿って対応できるよう支援します。

二つの領域には順番があります。サーバー検証が先です。 サーバーが証跡をきちんと確認しなければ、クライアントをいくら固めても意味がありません。逆にサーバー検証を備えたあとは、その要求を送るクライアントが改ざんされたビルドかどうかが次の問題になります。

結論:守るのは決済ではなく付与です

アプリ内課金のセキュリティを「決済が破られないようにすること」と考えると方向がずれます。ストアの決済はすでに堅牢です。守るべきなのは、決済が終わったという主張をゲームがどう確認するかです。

精算と付与量が乖離しているなら、たいていは上の六段階のどれかが空いています。とりわけ重複確認と返金時の回収は、最初に作るとき抜けやすく、被害が静かに積み上がる項目です。

次のステップ

  1. 保護範囲を確認する — プラットフォーム別の対応仕様と、プラン(Standard / Pro)ごとの防御範囲を製品ページで確認できます。
  2. すぐに統合を始める — Package Manager の My Assets(Asset Store 購入分)または .unitypackage(ポータルからのダウンロード分)で既存プロジェクトへ組み込めます。
  3. 技術的な相談 — 課金まわりの改ざん事例やライブ運営の課題があれば、お問い合わせください。

まずサーバー検証を点検し、そのうえで要求を送るクライアントを見てください。

検証を要求するクライアントから信頼できるように — OZero Security の Native 整合性検証。

あわせて読みたい