コンテンツにスキップ

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

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

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

チェックリスト概要

このチェックリストは バックエンド(サーバー側)のバリデーション を中心にまとめています。フロントエンドのバリデーションは補助的な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実行無効化— アップロードディレクトリでスクリプトが実行できないようにする

📚 関連記事

PNG vs WebP vs AVIF|画像フォーマットの選び方と変換方法

PNG / JPEG / WebP / AVIF の特徴・用途・ブラウザ対応状況を比較。picture 要素での出し分け、DevLab の画像フォーマット変換ツールの使い方も解説。

2026-04-18

Whois でドメイン情報を調べる方法|有効期限・ネームサーバー・登録者

Whois でわかること (登録者・有効期限・レジストラ・NS)、GDPR によるプライバシー保護の影響、ドメイン管理の実務的な使い方を解説。

2026-04-18

HTTP ステータスコード完全ガイド|よくあるエラーの原因と対処法

開発者が頻出する HTTP ステータスコード (200/301/302/400/401/403/404/413/422/429/500/502/503/504) の意味・原因・対処法を解説。301 vs 302 の SEO 影響、400 vs 422 の使い分けも。

2026-04-18

cURL コマンドを JavaScript fetch・Python requests に変換する方法|DevTools 連携

Chrome DevTools の Copy as cURL を fetch / axios / Python requests / PHP cURL / Go net/http に変換する手順を解説。主要 cURL オプション (-X / -H / -d / -F / -u / -b / -L) の変換パターン、認証トークンの扱い、注意点まで。

2026-04-16

Cookie のセキュリティフラグ完全ガイド|Secure / HttpOnly / SameSite / __Host-

Cookie のセキュリティ属性 Secure / HttpOnly / SameSite (Strict/Lax/None) / __Host- __Secure- プレフィックス / 4096 バイト制限を解説。CSRF / XSS / セッションハイジャック対策と、Laravel / Express の実装例。

2026-04-16

JWT のセキュリティベストプラクティス|alg none 攻撃 / 有効期限 / 署名検証

JWT (JSON Web Token) の代表的な脆弱性 6 種類 (alg none 攻撃 / 鍵混同 / 無期限トークン / payload への機密情報 / 失効不可 / 弱いシークレット) と対策。リフレッシュトークンパターン、失効リスト、HttpOnly Cookie 格納まで。

2026-04-16