セキュリティ
安全性への取り組み
GAMEYARD は投稿されたゲームファイルを配信するサービスです。遊ぶ人に危険な ファイルが届かないよう、投稿されたすべての zip に 8 段階の検査を実施しています。 何をどう検査しているかを、判定基準まで含めて公開します。
投稿された zip は、ClamAV による既知マルウェアの検出に加えて、Zip Slip・圧縮爆弾・シンボリックリンク・拡張子偽装・外部スクリプト読み込みを検査します。危険と判定したファイルは公開領域に配置されず、判定理由が投稿者に 開示されます。検査エンジンが停止している場合、投稿は「問題なし」ではなく受付停止として扱われます。
検査の8段階
危険なものほど早い段階で、かつ展開する前に弾く順序になっています。
| 段階 | 検査 | 内容 | なぜ必要か |
|---|---|---|---|
| 1 | 受付 | サイズ上限と、先頭バイト(マジックバイト)による形式判定 | 拡張子は投稿者が自由に変えられるため、.zip という名前を信用せず中身のバイト列で判定します。 |
| 2 | ハッシュ照合 | SHA-256 を既知の悪性ハッシュと突合 | 過去に拒否したファイルの再投稿を、展開する前のコストゼロで弾けます。 |
| 3 | 構造検査(展開しない) | ZIP のセントラルディレクトリだけを読み、Zip Slip・シンボリックリンク・圧縮爆弾・暗号化エントリ・危険なファイル名を検出 | ここが最も重要です。圧縮爆弾を「検査のために」展開してしまってはサーバーが落ちます。1バイトも展開せずに危険を判定します。 |
| 4 | シグネチャ検査 | ClamAV にアーカイブごと渡し、内部のファイルまで走査 | 既知のマルウェアはシグネチャで確実に落とします。検査エンジンが停止しているときは「問題なし」ではなく「受付停止」として扱います。 |
| 5 | 制限付き展開 | 1エントリずつ、累積サイズ上限に達したら即中断 | ヘッダに書かれた展開後サイズは偽装できるため、実際に展開されたバイト数で打ち切ります。 |
| 6 | 実体判定 | 展開した各ファイルをマジックバイトで種別判定し、拡張子との矛盾を検出 | .png を名乗る Windows 実行ファイルのような偽装は、拡張子を見ている限り永遠に気づけません。 |
| 7 | 静的コンテンツ解析 | HTML / JavaScript を読み、外部ドメインからのスクリプト読み込み、難読化、コインマイナー、外部への送信を検出 | 投稿時点で無害でも、外部スクリプトを読んでいれば公開後に中身を差し替えられます。だから外部読み込みそのものを許可しません。 |
| 8 | 判定 | 各指摘を深刻度で集計し、公開可 / 要目視確認 / 公開不可 を決定 | 深刻度 block が1件でもあれば公開しません。判定理由は検査 ID つきで投稿者に開示します。 |
検出できる脅威
| 脅威 | 検出方法 |
|---|---|
| マルウェア | ClamAV のシグネチャ(約362万件)で検出。zip 内部のファイルも対象。 |
| ディレクトリトラバーサル (Zip Slip) | `../` や絶対パスを含むエントリ。展開時にサーバー上の任意の場所へ書き込む攻撃。 |
| 圧縮爆弾 | 圧縮率200倍超、または展開後合計が上限超過。展開せずに検出し、検査エンジンにも渡しません。 |
| シンボリックリンク | 展開後に /etc/passwd などサーバー内の任意ファイルを指させる攻撃。 |
| 暗号化エントリ | パスワード保護されたエントリは中身を検査できないため受け付けません。 |
| 拡張子偽装 | .png を名乗る PE 実行ファイルなど、拡張子と実体の不一致。 |
| 宣言サイズの偽装 | ヘッダ上のサイズと実際の展開サイズの乖離。 |
| 外部スクリプト読み込み | 公開後に配信元の判断で中身を差し替えられるため、投稿作品では禁止。 |
| コインマイナー | 訪問者の CPU を無断で採掘に使うスクリプト。 |
| 難読化 | eval / new Function / 長大な Base64 のデコード実行。 |
| ファイル名による偽装 | 双方向制御文字を使って「exe.gpj」を「jpg.exe」に見せる手口、制御文字、OS 予約名。 |
投稿ゲームのオリジン分離
ウイルス検査は既知の脅威しか止められません。シグネチャにない 新種が 1 本すり抜けた時点で終わり、では守りとして成立しないため、 すり抜けたものの被害を封じ込める仕組みを別に用意しています。
投稿された HTML5 ゲームは任意の JavaScript を実行します。これをサイト本体と 同じドメインで配信すると、ゲーム内のスクリプトから本体の Cookie・ localStorage・DOM がそのまま読めてしまい、ログイン中の利用者になりすませます。 そこで GAMEYARD では、投稿ゲームをサイト本体とは別のドメインから配信し、sandbox 属性付きのフレーム内で動かしています。
遮断するのはブラウザの同一生成元ポリシーであって、当サイトのコードではありません。こちらの実装にミスがあっても守りが消えないのが、この構成を選んだ 理由です。
| 配信元 | サイト本体とは別ドメイン(サブドメインではなく別ドメイン) |
|---|---|
| 埋め込み | sandbox="allow-scripts allow-same-origin allow-pointer-lock"別オリジンのため allow-same-origin で復元されるのは配信ドメイン 自身のオリジンで、本体へは到達できません。ゲームのセーブデータ保存を 成立させるために必要な指定です。 |
| 外部通信 | CSP の connect-src 'self' で禁止。プレイヤーの情報を 外部へ送信する経路を塞いでいます。 |
| 埋め込み元の制限 | CSP の frame-ancestors で当サイト以外からの埋め込みを拒否。 |
| そのほか | X-Content-Type-Options: nosniff / Referrer-Policy: no-referrer / Permissions-Policy で位置情報・カメラ・マイク・決済を無効化 |
なお、公開に使うのはブラウザから再送されたファイルではなく、サーバーが検査時に 保管した実体です。検査を通した後に別のファイルへすり替える余地をなくすためです。
設計上の判断
クライアント側の検証は安全性の根拠にしない
投稿フォームにも入力チェックはありますが、これは入力の作法を整えるためのもので、 安全性の判断は一切していません。ブラウザ側の検証は投稿者が自由に迂回できるため、 サーバーが自分で検査した結果だけを根拠にします。
圧縮爆弾は検査エンジンにも渡さない
構造検査で圧縮爆弾と判定したファイルは、ClamAV にも渡しません。渡せば今度は 検査エンジンが展開を始めて CPU とメモリを消費し、攻撃者の狙いどおりになります。 すでに拒否が確定しているものに計算資源を使わないのが正しい対応です。
検査できないものは通さない
パスワード保護された zip、検査エンジンの停止、エンジンによる検査の打ち切り。 いずれも「危険とは断定できない」状態ですが、「安全と確認できていない」状態でも あります。この場合は公開しません。判断に迷ったら安全側に倒します。
受付上限は「検査が実際に届く範囲」から決めている
zip 全体 200MB・ファイル数 5,000・単一ファイル 200MB という上限は、切りのいい 数字ではなく実測から決めた値です。検査エンジンには自身の上限があり、それを 超えたファイルは中身を一切開かずに打ち切られます(実測: 360MB の zip は 1.4 秒で打ち切り)。またファイル数 21,000 件では検査が時間上限に達します。 受け付けてから「検査できませんでした」と返すのは、投稿者に数百 MB を無駄に 転送させるうえ、こちらも「検査済み」と言えない作品を抱えることになります。
上限の内側で検査が本当に奥まで届いているかは、既知のテスト検体(EICAR)を アーカイブの末尾・入れ子の内側・5,000 ファイル目の後ろに置いて検出されることを 自動試験で確認しています。上限を変えるときはこの試験を通すことが条件です。
「検出できる状態か」を定期的に確かめる
検査エンジンに接続できても、定義データベースが壊れていれば「何も検出しない エンジン」として動き続けます。その状態の応答は「すべて問題なし」と区別が つきません。そのため起動時と 5 分ごとに、検出されるはずのテスト検体を実際に 投げて、検出されることを確認しています。確認できない間は投稿を受け付けません。
拒否の理由は投稿者に開示する
「危険でした」とだけ返しても投稿者は修正できません。どの段階の、何という検査 ID が、どのファイルに反応したかまで開示します。誤検知だった場合に異議を申し立てる 材料にもなります。
よくある質問
投稿したファイルはどのように検査されますか?
受付・ハッシュ照合・構造検査・ClamAV によるシグネチャ検査・制限付き展開・ファイル実体判定・静的コンテンツ解析・判定の8段階で検査します。検査結果は検査 ID つきで投稿者に開示され、どの段階の何という検査がどのファイルに反応したかを確認できます。
検査にはどのくらい時間がかかりますか?
常駐しているウイルス検査デーモンに直接データを渡す方式のため、一般的な HTML5 ゲーム(数MB程度)で数十ミリ秒から数百ミリ秒です。検査のたびにウイルス定義を読み込む方式だと1回あたり20秒以上かかるため、常駐方式を採用しています。
誤検知された場合はどうすればよいですか?
検査結果には検査 ID(例: external-script、ext-mismatch)と対象ファイル名が表示されます。その情報を添えてお問い合わせください。難読化の検出などはミニファイ済みのゲームエンジンで正常に反応することがあるため、その場合は「要目視確認」として運営が個別に判断します。
ウイルス検査エンジンが停止しているときはどうなりますか?
投稿を受け付けません。検査エンジンに接続できない状態を「問題なし」として扱うと、検査を回避したい攻撃者にとって最も都合のよい抜け道になります。エンジンが応答しない場合は明示的に公開不可として扱います。
検査で拒否されたファイルはどうなりますか?
隔離領域に保管し、原因調査と再投稿時のハッシュ照合に使います。公開領域には一切配置されません。