Webフォームのファイルバリデーション実装チェックリスト
カテゴリ:セキュリティ・実装
ファイルアップロード機能の実装は、セキュリティ上の注意点が多く、見落としがちな落とし穴もたくさんあります。この記事では、本番環境で安全に動作するファイルアップロードを実装するためのチェックリストを解説します。
チェックリスト概要
このチェックリストは バックエンド(サーバー側)のバリデーション を中心にまとめています。フロントエンドのバリデーションは補助的なUX改善として実装しますが、セキュリティ上の保証にはなりません。
1. ファイルサイズの検証
- アップロード上限をバイト単位で定義している(MBとMiBの混同なし)
- PHPの場合、
upload_max_filesizeとpost_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点は必ず実装してください。
- サーバー側でのMIMEタイプ検証(
finfo使用)— クライアントの申告は信頼しない - ランダムなファイル名での保存— 元のファイル名は使わない
- 保存先でのPHP実行無効化— アップロードディレクトリでスクリプトが実行できないようにする
この記事で使えるテストファイル
❓ よくある質問
ファイルアップロードのバリデーションで最低限実装すべきことは何ですか?
最低限3つの対策が必要です。第一にfinfoを使ったサーバー側でのMIMEタイプ検証(クライアントの申告を信頼しない)、第二にランダムなファイル名での保存(元のファイル名を使わない)、第三に保存先ディレクトリでのPHP実行無効化(アップロードディレクトリでスクリプトが実行できないようにする)です。
ファイルの拡張子チェックだけでは不十分なのはなぜですか?
拡張子は簡単に偽装できるため、悪意のあるファイル(例: shell.php.jpg のようなダブル拡張子)を検出できません。拡張子のホワイトリストに加えて、finfoによるMIMEタイプ検証、マジックバイト(ファイルヘッダー)の検証を組み合わせることで安全性を高める必要があります。
ファイルアップロード時に適切なHTTPステータスコードは何ですか?
アップロード成功時は200または201、ファイルサイズ超過時は413 Payload Too Large、不正なファイル形式の場合は422 Unprocessable Entityを返すのが適切です。エラーメッセージにはサーバーの内部情報(パスやバージョン等)を含めないよう注意してください。