コンテンツにスキップ

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 する

チェックリスト概要

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7. テストケース

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

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

まとめ

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

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