Pillowのopen()verify()load()をセキュリティ観点で理解する

画像アップロードは「画像ファイルを受け取る」だけではなく、外部入力をデコーダ(パーサ)に通す作業です。そこでPillowの3つのメソッドは、機能説明よりもいつ呼び出すか(=攻撃表面をいつ開くか)が重要です。


open()は「ピクセルを上げる関数」ではない

Image.open()遅延動作です。つまり、ファイルを「開いて識別」だけし、ピクセルデータはまだ読み込まないことがあります。また、ファイルハンドルが開いたまま残ることもあります。

セキュリティ/運用でopen()をうまく使う方法はシンプルです。

  • open()でフォーマット識別、幅/高さなど軽量情報を取得
  • ポリシーでブロック:許可フォーマット、最大解像度/ピクセル数、アップロード容量制限
  • 次の段階(検証/デコード)へ進む

つまり、open()は「デコード前段階で判断できる情報を抽出するツール」として安全に使うべきです。


verify()は何を保証し、何を保証しないか

Pillowのverify()は「ファイルが壊れているか」を確認しようとしますが、実際の画像データをデコードせずに検査します。問題があれば例外を投げ、verify()後に画像を使うにはファイルを再度開く必要があります。

ここでのセキュリティ的結論は2点です。

  • メリット:デコード(=重い作業)を避けつつ「壊れたファイル」を高速に除外できる
  • 限界verify()通過は「安全」ではなく「今すぐ大きく壊れていない」に近い。デコードを完了しないため、load()時点で問題が表れる可能性があります。

load()は検証段階で無闇に呼び出すと危険

load()は実際にデコード(圧縮解凍を含む)を行い、ピクセルをメモリに上げる段階です。この点が直ちにDoS(リソース枯渇)攻撃の表面になります。見た目のファイルサイズが小さくても、デコード結果が非常に大きくなる可能性があります。

Pillowは「デコンプレッションスプリング」リスクを警告/例外で扱い、デフォルトの閾値(例:128Mpx程度)などの保護機構を持っています。

Djangoも同じ理由で、画像アップロード検証でload()ではなくverify()を使用します。ソースには「load()は全画像をメモリに上げてDoSベクターになる」というコメントがあり、実際にImage.open()後にverify()を呼び出します。


Django/DRF使用時:ImageFieldverify()をもう一度呼ぶと重複になる

DjangoのフォームImageField検証は内部でImage.open() + verify()を実行します。DRFのserializers.ImageFieldも「Django実装に委譲する」コメント付きでDjango側検証を呼び出すフローを持ちます。

したがってDRFでserializers.ImageFieldを既に使っている場合:

  • ただ「壊れたか確認」だけでvalidate()内でverify()を再度呼ぶのはほぼ重複作業になります。
  • ビジネス検証/追加セキュリティ検証を強くカスタマイズしたい場合は、ImageFieldではなくFileFieldで受け取り(検証パイプラインを直接設計)コストと責任を明確にする選択がよりクリーンです。

ユーザーがアップロードしたファイルに「安全」を確保するには

アップロードファイルを安全に処理する図

最も現実的な答えはこれです。

「アップロード元をそのまま使わず、サーバーがデコードして新ファイルとして再保存した結果のみを使用する」

  • open()で幅/高さ/フォーマットなど低コスト情報を読み、ポリシーで一次ブロック
  • verify()明らかに壊れたファイルを除外
  • 次に(通過したもののみ)制限された環境でデコードしRGB/RGBAなど標準ピクセルへ正規化
  • サーバーが選択したフォーマットで再エンコードして新ファイル生成
  • サービスはサーバーが再生成したファイルのみを保存/配信

この戦略のメリットは「サーバーが最終出力の形を制御できる」点です。元ファイルにあった不要メタデータや奇妙な構造を大部分除去できます。

ただし再エンコードは結局load()に相当するデコードを含みます。 したがってピクセル数/メモリ制限(デコンプレッションスプリング防御)などのガードレールを先に設定し、可能ならワーカー/隔離プロセスで実行するのが安全です。


まとめ

  • open()識別 + 低コスト情報確認段階(ピクセルはまだないかもしれない)
  • verify()壊れたかの一次フィルタ(デコードなしで検査、以降使用するには再オープン)
  • load()デコード/メモリ使用が始まる点(検証段階での乱用は禁物)
  • アップロード安全の実務回答:サーバーが再エンコードした結果のみを信頼(ただし制限/隔離必須)

関連記事