投稿された zip は、ClamAV による既知マルウェアの検出に加えて、Zip Slip・圧縮爆弾・シンボリックリンク・拡張子偽装・外部スクリプト読み込みを検査します。危険と判定したファイルは公開領域に配置されず、判定理由が投稿者に 開示されます。検査エンジンが停止している場合、投稿は「問題なし」ではなく受付停止として扱われます。

検査の8段階

危険なものほど早い段階で、かつ展開する前に弾く順序になっています。

投稿したときに受け取る検査結果では、5〜7 が「展開・実体判定」の 1 行にまとまり、8 は全体の判定として出ます。最後まで通った場合は 6 行になります。行が少なく見えても、下の 8 つはすべて実施しています。

危険と判定した時点で打ち切るので、公開できなかった場合は そこまでの行しか出ません(たとえば構造検査で弾いたときは「構造検査」で✕、続く「展開」が–で止まります)。 出うる行の名前はすべて下の「結果の表記」に載せてあるので、突き合わせて読めます。

段階検査内容なぜ必要か
1受付結果の表記: 受付サイズ上限と、先頭バイト(マジックバイト)による形式判定拡張子は投稿者が自由に変えられるため、.zip という名前を信用せず中身のバイト列で判定します。
2ハッシュ照合結果の表記: ハッシュ照合SHA-256 を既知の悪性ハッシュと突合過去に拒否したファイルの再投稿を、展開する前のコストゼロで弾けます。
3構造検査(展開しない)結果の表記: 構造検査ZIP のセントラルディレクトリだけを読み、Zip Slip・シンボリックリンク・圧縮爆弾・暗号化エントリ・危険なファイル名を検出ここが最も重要です。圧縮爆弾を「検査のために」展開してしまってはサーバーが落ちます。1バイトも展開せずに危険を判定します。
4シグネチャ検査結果の表記: シグネチャ検査 (ClamAV) / 個別シグネチャ走査ClamAV にアーカイブごと渡し、内部のファイルまで走査既知のマルウェアはシグネチャで確実に落とします。検査エンジンが停止しているときは「問題なし」ではなく「受付停止」として扱います。
5制限付き展開結果の表記: 展開・実体判定(6・7 とまとめて 1 行)。危険と判定して展開しなかったときは「展開」1エントリずつ、累積サイズ上限に達したら即中断ヘッダに書かれた展開後サイズは偽装できるため、実際に展開されたバイト数で打ち切ります。
6実体判定結果の表記: 展開・実体判定(5・7 とまとめて 1 行)展開した各ファイルをマジックバイトで種別判定し、拡張子との矛盾を検出.png を名乗る Windows 実行ファイルのような偽装は、拡張子を見ている限り永遠に気づけません。
7静的コンテンツ解析結果の表記: 展開・実体判定(5・6 とまとめて 1 行)。指摘は検査 ID で出ますHTML / JavaScript を読み、外部ドメインからのスクリプト読み込み、難読化、コインマイナー、外部への送信を検出投稿時点で無害でも、外部スクリプトを読んでいれば公開後に中身を差し替えられます。だから外部読み込みそのものを許可しません。
8判定結果の表記: 行としては出ません。全体の判定として出ます各指摘を深刻度で集計し、公開可 / 要目視確認 / 公開不可 を決定深刻度 block が1件でもあれば公開しません。判定理由は検査 ID つきで投稿者に開示します。

検出できる脅威

脅威検出方法
マルウェアClamAV のシグネチャで検出。zip 内部のファイルも対象。定義データベースが 3 日以上古いままになっていないかを死活確認で見ています。
ディレクトリトラバーサル (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 ファイル目の後ろに置いて検出されることを 自動試験で確認しています。上限を変えるときはこの試験を通すことが条件です。

「検出できる状態か」を確かめてから受け付ける

検査エンジンに接続できても、定義データベースが壊れていれば「何も検出しない エンジン」として動き続けます。その状態の応答は「すべて問題なし」と区別が つきません。そのため起動時と、zip を受け取るたびに、 検出されるはずのテスト検体を実際に投げて、検出されることを確認しています (同じ結果は 5 分だけ使い回します)。確認できない間は投稿を受け付けません。

拒否の理由は投稿者に開示する

「危険でした」とだけ返しても投稿者は修正できません。どの段階の、何という検査 ID が、どのファイルに反応したかまで開示します。誤検知だった場合に異議を申し立てる 材料にもなります。

よくある質問

投稿したファイルはどのように検査されますか?

受付・ハッシュ照合・構造検査・ClamAV によるシグネチャ検査・制限付き展開・ファイル実体判定・静的コンテンツ解析・判定の8段階で検査します。検査結果は検査 ID つきで投稿者に開示され、どの段階の何という検査がどのファイルに反応したかを確認できます。危険と判定した時点で打ち切るため、公開できなかった場合はそこまでの段階しか結果に出ません。

検査にはどのくらい時間がかかりますか?

常駐しているウイルス検査デーモンに直接データを渡す方式のため、一般的な HTML5 ゲーム(数MB程度)で数十ミリ秒から数百ミリ秒です。検査のたびにウイルス定義を読み込む方式だと1回あたり20秒以上かかるため、常駐方式を採用しています。

誤検知された場合はどうすればよいですか?

検査結果には検査 ID(例: external-script、ext-mismatch)と対象ファイル名が表示されます。その情報を添えてお問い合わせください。難読化の検出などはミニファイ済みのゲームエンジンで正常に反応することがあるため、その場合は「要目視確認」として運営が個別に判断します。

ウイルス検査エンジンが停止しているときはどうなりますか?

投稿を受け付けません。検査エンジンに接続できない状態を「問題なし」として扱うと、検査を回避したい攻撃者にとって最も都合のよい抜け道になります。エンジンが応答しない場合は明示的に公開不可として扱います。

検査で拒否されたファイルはどうなりますか?

隔離領域に保管し、原因調査と再投稿時のハッシュ照合に使います。公開領域には一切配置されません。

検査 ID と、直し方

検査結果には external-script のような検査 ID が出ます。 投稿画面にも同じ直し方を出していますが、画面を閉じたあとでもここで引けます。 いずれにも当てはまらない場合や、心当たりが無い場合は [email protected] までご連絡ください。

検査 ID直し方
archive-too-largezip 全体で 200MB までです。素材を圧縮するか、作品を分けて投稿してください。
av-errorこのファイルに直していただくところはありません。こちらの検査エンジンが応答していないために受け付けを止めています。復旧したあと、同じファイルでもう一度お試しください。
av-limit入れ子のアーカイブや極端に大きいファイルを減らしてください。検査が奥まで届くようになります。直さなくても公開できますが、運営の目視確認を待つことになります。
av-limits-exceeded入れ子のアーカイブや極端に大きいファイルを減らしてから、投稿し直してください。
av-unavailableこのファイルに直していただくところはありません。こちらの検査エンジンが応答していないために受け付けを止めています。復旧したあと、同じファイルでもう一度お試しください。
base64-blob素材を Base64 でコードに埋め込まず、ファイルとして同梱してください。直さなくても公開できますが、運営の目視確認を待つことになります。
credential-formID やパスワードを受け取る入力欄は置けません。外してください。直さなくても公開できますが、運営の目視確認を待つことになります。
cryptominer該当するスクリプトを外してください。心当たりが無い場合は、使ったライブラリが差し替えられていないか確認してください。
dangerous-nameファイル名を英数字・ハイフン・アンダースコアだけに直してください(con / nul などの OS 予約名も使えません)。
deep-pathフォルダの入れ子を浅くしてください。直さなくても公開できますが、運営の目視確認を待つことになります。
encryptedパスワードを掛けずに zip にし直してください。中を開けないファイルは検査できません。
entry-count使っていないファイルを減らすか、素材をまとめてから zip にし直してください(エントリ数の上限は 5,000 です)。
entry-too-largeその 1 ファイルを分割するか、音声・動画の圧縮率を上げてください(1 ファイル 200MB までです)。
eval-obfuscation書き出し設定の難読化・圧縮を弱めてから、書き出し直してください。多くはこれで消えます。直さなくても公開できますが、運営の目視確認を待つことになります。
exec-in-html5.exe や .dll などの実行ファイルを外してください。ブラウザで動くファイルだけを入れます。
ext-mismatch拡張子と中身が合うように直してください(名前を正しい拡張子に変えるか、ファイルそのものを差し替えます)。
external-iframe外部ページの埋め込み(iframe)を外してください。直さなくても公開できますが、運営の目視確認を待つことになります。
external-load-in-codefetch や XMLHttpRequest の宛先を、同梱したファイルの相対パスに書き換えてください。直さなくても公開できますが、運営の目視確認を待つことになります。
external-postスコアなどの送信先を外部に置かず、ブラウザの中で完結させてください。直さなくても公開できますが、運営の目視確認を待つことになります。
external-script読み込んでいるライブラリを zip に同梱し、src をその相対パスに書き換えてください。外部ドメインからの読み込みは公開後に中身を差し替えられるため許可していません。
inflate-failedそのファイルを外すか、作り直してから zip にし直してください。直さなくても公開できますが、運営の目視確認を待つことになります。
known-bad-hash以前に受け付けを断ったファイルと同じ中身です。原因を直したファイルを作り直して投稿してください。
long-nameそのファイル名を短くしてください。長いままだと展開に失敗する環境があります。直さなくても公開できますが、運営の目視確認を待つことになります。
malformed-zip圧縮し直してください。アップロードの途中でファイルが壊れていることもあります。
malware制作に使ったパソコンをウイルス検査にかけ、きれいな環境で作り直したファイルを投稿してください。
missing-indexzip を開いてすぐの場所に index.html を置いてください(フォルダを 1 枚かぶせると見つけられません)。
nested-archive中の zip を先に展開し、全体を 1 つの zip にまとめ直してください。直さなくても公開できますが、運営の目視確認を待つことになります。
not-a-zipzip 形式で保存し直してください(.rar や .7z は受け付けられません)。
office-macroゲームの動作に要らない Office 文書は外してください。直さなくても公開できますが、運営の目視確認を待つことになります。
size-mismatch別の圧縮ソフトで zip を作り直してください。
symlinkシンボリックリンク(macOS の「エイリアス」を含む)を、実体のファイルに置き換えてから zip にしてください。
total-too-large画像・音声を圧縮するか、使っていない素材を外してください(展開後の合計に上限があります)。
zip-bomb巨大な空ファイルや、同じ中身の繰り返しを含めないでください。心当たりが無い場合はご連絡ください。
zip-slipフォルダごと選んで zip にし直してください。圧縮ソフトの設定に「絶対パスを保存」があれば切ってください。

脆弱性の報告

検査の回避方法を見つけた場合は [email protected] までご連絡ください。公開前に 修正する時間をいただけると助かります。報告者のご希望に応じて、修正後に謝辞を 掲載します。

投稿の手順は ゲームを投稿する、 掲載できる作品の基準は 投稿ガイドライン をご覧ください。