Webフォームのファイルバリデーション実装チェックリスト
カテゴリ:セキュリティ・実装
ファイルアップロード機能の実装は、セキュリティ上の注意点が多く、見落としがちな落とし穴もたくさんあります。この記事では、本番環境で安全に動作するファイルアップロードを実装するためのチェックリストを解説します。
チェックリスト概要
このチェックリストは <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点は必ず実装してください。
- <strong>サーバー側でのMIMEタイプ検証</strong>(<code>finfo</code> 使用)— クライアントの申告は信頼しない
- <strong>ランダムなファイル名での保存</strong>— 元のファイル名は使わない
- <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>