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実行無効化— アップロードディレクトリでスクリプトが実行できないようにする