コンテンツにスキップ

Webフォームのファイルバリデーション実装チェックリスト

カテゴリ:セキュリティ・実装

ファイルアップロード機能の実装は、セキュリティ上の注意点が多く、見落としがちな落とし穴もたくさんあります。この記事では、本番環境で安全に動作するファイルアップロードを実装するためのチェックリストを解説します。

ファイルバリデーションの推奨順序 安全な順序: 軽い検証から重い検証へ 1. ファイルサイズ bytes <= max NG → 即 reject 2. 拡張子 .jpg / .png ... NG → 即 reject 3. Content-Type finfo / mime_content_type NG → 即 reject 4. マジックバイト FF D8 FF (JPEG) ... NG → 即 reject 5. 内容スキャン ClamAV / VirusTotal NG → 即 reject
図1: バリデーションは軽い検証から実行し早期 reject する

チェックリスト概要

このチェックリストは <strong>バックエンド(サーバー側)のバリデーション</strong> を中心にまとめています。フロントエンドのバリデーションは補助的なUX改善として実装しますが、セキュリティ上の保証にはなりません。

1. ファイルサイズの検証

  • <input type="checkbox" disabled> アップロード上限をバイト単位で定義している(MBとMiBの混同なし)
  • <input type="checkbox" disabled> PHPの場合、<code>upload_max_filesize</code> と <code>post_max_size</code> の両方を設定している
  • <input type="checkbox" disabled> Nginxの場合、<code>client_max_body_size</code> に multipart オーバーヘッド分を加算している
  • <input type="checkbox" disabled> <code>$_FILES['file']['error']</code> が UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE の場合のエラーハンドリングがある
  • <input type="checkbox" disabled> 最小ファイルサイズのチェックがある(0バイトファイルの排除)
 $maxBytes) {
        throw new \RuntimeException(sprintf(
            'ファイルサイズ(%s)が上限(%s)を超えています',
            number_format($file['size']),
            number_format($maxBytes)
        ));
    }
}

2. ファイル形式の検証(MIMEタイプ)

  • <input type="checkbox" disabled> クライアントから送られる <code>Content-Type</code>(<code>$_FILES['file']['type']</code>)を信頼していない
  • <input type="checkbox" disabled> <code>finfo</code> / <code>mime_content_type()</code> でサーバー側のMIMEタイプ検証を行っている
  • <input type="checkbox" disabled> 許可するMIMEタイプのホワイトリストを定義している
file($file['tmp_name']);

if (!in_array($mimeType, $allowed, true)) {
    throw new \RuntimeException('許可されていないファイル形式です: ' . $mimeType);
}

3. ファイル拡張子の検証

  • <input type="checkbox" disabled> 拡張子のホワイトリストを定義している(ブラックリストではなくホワイトリスト)
  • <input type="checkbox" disabled> ダブル拡張子(<code>shell.php.jpg</code>)を検出・拒否している
  • <input type="checkbox" disabled> 大文字小文字を正規化して検証している(<code>.JPG</code> と <code>.jpg</code> を同一視)
 2) {
    throw new \RuntimeException('不正なファイル名です');
}

$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
    throw new \RuntimeException('許可されていない拡張子です: ' . $ext);
}

4. マジックバイト(ファイルシグネチャ)の検証

  • <input type="checkbox" disabled> 重要なファイル(実行可能ファイルの排除など)でマジックバイトを検証している

5. 保存先とファイル名の安全な処理

  • <input type="checkbox" disabled> 保存先ディレクトリはWebルートの外(またはXSendFile/X-Accel-Redirectで制御)
  • <input type="checkbox" disabled> 保存ファイル名はUUIDなどランダム生成し、元のファイル名を使っていない
  • <input type="checkbox" disabled> パストラバーサル(<code>../../../etc/passwd</code>)をバリデーションで排除している
  • <input type="checkbox" disabled> 保存先ディレクトリに PHP実行権限がない(<code>.htaccess</code> や Nginx設定でPHP処理を無効化)

6. エラーハンドリングとレスポンス

  • <input type="checkbox" disabled> アップロード成功時に適切なHTTPステータス(200/201)を返している
  • <input type="checkbox" disabled> サイズ超過時に <strong>413 Payload Too Large</strong> を返している
  • <input type="checkbox" disabled> 不正なファイル形式時に <strong>422 Unprocessable Entity</strong> を返している
  • <input type="checkbox" disabled> エラーメッセージにサーバーの内部情報(パス、バージョン等)が含まれていない

7. テストケース

実装後は以下のテストケースを実行して動作を確認してください。DevLab のテスト用ファイルが活用できます。

テストケース期待する結果使用するファイル
上限ちょうどのファイル成功<a href="/ja/files/threshold/">しきい値ファイル</a>
上限を1バイト超えるファイル413エラー<a href="/ja/files/threshold/">しきい値ファイル</a>
0バイトの空ファイルバリデーションエラー手動作成
拡張子を偽装したファイル(PHP→.jpg)MIMEエラー<a href="/ja/files/broken/">壊れたファイル</a>
ヘッダー破損ファイルバリデーションエラー<a href="/ja/files/broken/">壊れたファイル</a>

まとめ

安全なファイルアップロードの実装には、複数の層でのバリデーションが不可欠です。特に以下の3点は必ず実装してください。

  1. <strong>サーバー側でのMIMEタイプ検証</strong>(<code>finfo</code> 使用)— クライアントの申告は信頼しない
  2. <strong>ランダムなファイル名での保存</strong>— 元のファイル名は使わない
  3. <strong>保存先でのPHP実行無効化</strong>— アップロードディレクトリでスクリプトが実行できないようにする

この記事で使えるテストファイル

  • → <a href="/ja/files/broken/" class="text-primary-600 dark:text-primary-400 hover:underline">壊れたファイル一覧(バリデーションエラーのテスト用)</a>
  • → <a href="/ja/files/threshold/" class="text-primary-600 dark:text-primary-400 hover:underline">しきい値テスト用ファイル一覧(9.9MB / 10MB / 10.1MB)</a>
  • → <a href="/ja/files/images/" class="text-primary-600 dark:text-primary-400 hover:underline">画像テストファイル一覧(PNG / JPG / WebP / GIF)</a>

関連記事

  • → <a href="/ja/blog/how-to-test-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">ファイルアップロード上限を正しくテストする方法</a>
  • → <a href="/ja/reference/magic-bytes/" class="text-primary-600 dark:text-primary-400 hover:underline">マジックバイト(ファイルシグネチャ)リファレンス</a>