Laravelにはファイルアップロードを扱う便利なAPIが用意されています。 ただし、ファイルアップロード機能はセキュリティ事故につながりやすい部分でもあるため、いくつか基本的な設計方針を押さえておく必要があります。
自分用の覚え書きとして、Laravelで安全にファイルアップロードを実装する際のベストプラクティスを整理しておきます。
1 バリデーションで入口を制限する
まずはアップロードされるファイルをバリデーションで制限します。
例えば画像アップロードの場合は次のように書きます。
$request->validate([
'avatar' => [
'required',
'file',
'image',
'mimes:jpg,jpeg,png,webp',
'max:5120', // KB(5MB)
'dimensions:min_width=64,min_height=64,max_width=6000,max_height=6000',
],
]);
ポイントとしては次のようなものです。
fileでファイルであることを確認imageで画像として読み取れることを確認mimesで許可する拡張子を限定maxでサイズ制限
サイズ制限は 巨大ファイルによるDoS攻撃対策にもなります。
2 保存先はWeb公開ディレクトリ外にする
アップロードされたファイルは、できれば Web公開ディレクトリの外に保存します。
Laravelのデフォルトの保存先は次のディレクトリです。
storage/app
例えば次のように保存します。
$path = $request->file('avatar')->store('uploads/avatars');
実際の保存場所は次のようになります。
storage/app/uploads/avatars/xxxxx.jpg
この場所はWebから直接アクセスできないため、安全性が高くなります。
3 ファイル名はサーバー側で生成する
ユーザーが指定したファイル名は基本的に信用しません。
Laravelの store() メソッドを使うと、ランダムなファイル名で保存されます。
$path = $request->file('avatar')->store('uploads/avatars');
元のファイル名は表示用としてDBに保存しておく程度にします。
$file = $request->file('avatar');
$originalName = $file->getClientOriginalName();
$path = $file->store('uploads/avatars');
4 拡張子とMIMEタイプの両方をチェックする
HTTPリクエストの Content-Type はクライアント側で簡単に偽装できます。
そのため次のようなチェックを組み合わせます。
imagemimes- 必要に応じて
mimetypes
例:
$request->validate([
'avatar' => [
'required',
'file',
'image',
'mimes:jpg,jpeg,png,webp',
'mimetypes:image/jpeg,image/png,image/webp',
'max:5120',
],
]);
5 ダウンロードはアプリケーション経由で配信する
アップロードしたファイルをユーザーにダウンロードさせる場合は、 アプリケーション経由で配信する方法が安全です。
use Illuminate\Support\Facades\Storage;
public function download(string $id)
{
$file = UploadedFileModel::findOrFail($id);
return Storage::download($file->path, $file->original_name);
}
この方法なら、ダウンロード前に 認可チェックを挟むこともできます。
6 画像は再エンコードするとさらに安全
画像アップロードの場合は、アップロードされたファイルをそのまま保存するのではなく 一度読み込んで再エンコードして保存する方法もあります。
考え方としては
- アップロードされたバイナリをそのまま保存しない
- 画像として読み込んで再保存する
という方法です。
実務では次のようなライブラリを使うことが多いです。
- Intervention Image
- ImageMagick
実務でよく言われる安全設計
実務では、次のような設計がよく推奨されます。
① 保存ファイル名はサーバー生成
3f8a2c9b.jpg
などランダム名を使います。
② Web公開ディレクトリ外に保存
例えば
/var/data/uploads
などWebから直接アクセスできない場所に保存します。
③ 拡張子ホワイトリスト
許可する拡張子のみ受け付けます。
jpg
png
pdf
④ ファイルサイズ制限
例えば
10MB
などの制限を設けます。
⑤ 必要に応じてウイルススキャン
環境によっては
- ClamAV
などのウイルススキャンを組み込む場合もあります。
まとめ
Laravelで安全にファイルアップロードを実装するための基本ポイントは次の通りです。
- バリデーションでファイル種類とサイズを制限
- 保存先はWeb公開ディレクトリ外
- ファイル名はサーバー生成
- MIMEタイプだけに依存しない
- ダウンロードはアプリケーション経由
- 可能なら画像は再エンコード
Laravelの store() や Storage を使うと、比較的シンプルなコードで安全なファイルアップロード処理を書くことができます。
ファイルアップロードはセキュリティ事故につながりやすい部分でもあるので、 基本的な対策は忘れないようにしておきたいところです。