マガジン

ゲームのランキング機能を作る:不正スコア対策

遊びをつくる

執筆にClaudeを使用しています。

石や花を自由に配置する小さな庭で、点数に頼らない遊びを表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

ランキング機能はプレイヤーのモチベーションを長続きさせる仕組みとして効果的ですが、公開した直後から不正スコアが混入するリスクがあります。「1位が明らかにあり得ない数値」「ゲームクリアに必要な時間より短い記録」といった問題が起きると、正直にプレイしているユーザーが離れてしまいます。 この記事では、ブラウザゲームのランキングを実装するときに直面する不正スコアの問題を整理し、どの対策をどの順番で入れるか判断できるよう手順と基準を示します。完璧な不正防止は難しいですが、「手間に見合わない」と感じさせる程度のハードルを設けるだけで、ほとんどの軽微な改ざんは防げます。

この記事の目次
  1. なぜブラウザゲームのスコアは改ざんされやすいのか
  2. スコアを検証する三つのアプローチ
  3. 実装の優先順位と判断基準
  4. 見落とされがちな落とし穴
  5. 不正対策とユーザー体験のバランス
  6. 次の行動
  7. 参考資料

なぜブラウザゲームのスコアは改ざんされやすいのか

ブラウザゲームはHTML・JavaScript・WebAssemblyなどがクライアント側で動作するため、スコアの計算ロジックそのものをユーザーが読める状態に置かれることが多いです。DevToolsのコンソールから変数を書き換えたり、送信するHTTPリクエストをプロキシで差し替えたりする操作は、専門知識がそれほど深くなくても実行できます。

さらに、ランキング登録APIのエンドポイントが外部から直接叩ける状態になっていると、ゲームを一切プレイせずに任意のスコアを送りつけることも可能です。クライアント側だけで守ろうとしても、リクエストを模倣されれば意味がありません。不正対策は「クライアントを信用しない」という前提から始める必要があります。

スコアを検証する三つのアプローチ

対策の方向性は大きく三つに分かれます。それぞれトレードオフがあるため、ゲームの規模や管理コストを踏まえて組み合わせを選んでください。

1. サーバー側でスコアの上限・下限を検証する

最もシンプルな方法で、まず最初に入れるべき対策です。「このゲームで1分以内に達成できる最大スコア」を計算しておき、それを超えるスコアは登録を拒否します。例えば、1秒に最大10点しか加算されないゲームで、プレイ時間60秒の記録に601点以上のスコアが付いていたら弾く、という判断です。

実装時には、スコアだけでなくプレイ開始時刻・終了時刻も一緒に送るようにします。サーバーはその差分と最大獲得速度を照合すれば、物理的にあり得ない記録を排除できます。

2. セッショントークンでプレイの正当性を担保する

ゲーム開始時にサーバーが一時的なトークンを発行し、スコア送信時にそのトークンを添えさせます。サーバーは「このトークンは発行済みか」「まだ使われていないか」「発行から妥当な時間内か」の三点を確認してから登録します。

これにより、トークンを持たないリクエスト(つまりゲームを起動せずに送られた直接アクセス)を排除できます。トークンは使い捨て(ワンタイム)にするのが重要で、一つの記録を何度も送りつけるリプレイ攻撃を防ぎます。実装はサーバーにセッション管理の仕組みが必要ですが、UUIDをサーバー側のデータベースに短時間保持するだけで実現できます。

3. ゲーム操作のログをサーバーで再現・検証する

最も堅牢ですが実装コストも高い手法です。クライアントはスコアの数値だけでなく、「どの操作をどの順番でどのタイミングで行ったか」というイベントログも送ります。サーバーがそのログをゲームロジックで再現し、結果として得られるはずのスコアと送られてきたスコアが一致するか確かめます。

この方法はゲームロジックをサーバー側にも実装する必要があり、開発工数が倍近くかかります。競技性が高い大規模タイトル向きで、個人・小規模チームが作るカジュアルゲームには過剰なことが多いです。

実装の優先順位と判断基準

どの対策をどのタイミングで入れるかは、以下の観点で判断するとよいです。

  • ランキングの競争度、優先度が高いとき:賞品あり・順位に強い意味がある、優先度が低いとき:自己ベスト参考程度
  • プレイヤー規模、優先度が高いとき:公開範囲が広い・匿名アクセス可、優先度が低いとき:クローズドβ・招待制
  • スコア構造の単純さ、優先度が高いとき:数値一つで表せる、優先度が低いとき:複合条件で外部から再現困難
  • 開発リソース、優先度が高いとき:専任バックエンドがいる、優先度が低いとき:ひとりで全部作っている

小規模のカジュアルゲームであれば、まず「上限チェック+ワンタイムトークン」の二本だけ入れれば十分なことがほとんどです。これ以上の対策は、実際に不正が目立ってきた段階で追加を検討するので遅くありません。

見落とされがちな落とし穴

エラーメッセージを詳しく返しすぎない

「スコアが最大値を超えています」と具体的なメッセージを返すと、攻撃者に「どこまでなら通るか」を教えてしまいます。「送信に失敗しました」程度の汎用メッセージにとどめ、詳細はサーバーログだけに記録するのが安全です。

クライアント側の難読化に頼りすぎない

JavaScriptの難読化(minify・obfuscate)は解析コストを少し上げますが、根本的な防御にはなりません。難読化はあくまで補助手段として位置づけ、サーバー側の検証と組み合わせて使ってください。

レート制限を忘れない

スコア送信APIには、同一IPや同一アカウントからの短時間内の連続送信を制限するレート制限(レートリミット)をかけてください。レート制限がないと、スコアを大量に試し打ちしながら検証ロジックの穴を探す行為を許してしまいます。

既存ランキングの定期監視

自動検証をくぐり抜けたスコアが残ることもあります。定期的に分布を確認し、外れ値を手動でフラグ立てできる管理インターフェースを最初から用意しておくと、後の運用が楽になります。統計的な外れ値検出(平均と標準偏差からの乖離が著しいスコアを候補として抽出する)を組み込むと自動化しやすいです。

不正対策とユーザー体験のバランス

厳しすぎる検証は、正当なプレイをした記録まで弾く誤検知につながります。例えば、回線の遅延で終了時刻がずれてプレイ時間が短く見える、などのケースです。設計時には「正直なプレイ者が誤って弾かれたらどうなるか」を必ず想定してください。

誤検知への対処として、境界値に少し余裕を持たせること(理論上の最大値の110〜120%あたりに閾値を置くなど)と、疑わしいスコアを即削除するのではなく「仮登録→確認後表示」のフローを取ることが有効です。不正と断定できない段階でスコアを消してしまうと、正当なユーザーからのクレームにつながります。

次の行動

ゲームを作り始めたらまずスコアの最大獲得速度を計算し、サーバー側に上限チェックのロジックを書くところから始めてみてください。次にワンタイムトークンを追加し、動作確認としてDevToolsから直接POSTリクエストを投げてみると、対策が機能しているか自分で確かめられます。アソバースにゲームを投稿する際は、スコア送信APIを自前で用意するかサードパーティのランキングサービスを使うかを先に決めておくと、実装の方針が定まりやすいです。

参考資料

  • OWASP Testing Guide(API Security Testing の章)
  • MDN Web Docs — Fetch API・Headers
  • 各クラウドサービス公式ドキュメント(レートリミット・セッション管理の節)