從安全角度理解 Pillow 的 open()、verify()、load()
圖像上傳不只是「接收圖檔」,而是將外部輸入送入解碼器(解析器)進行處理。 因此 Pillow 的三個方法,重點不在功能說明,而在於何時呼叫(即何時開啟攻擊面)。
open() 不是「把像素拉起來」的函式
Image.open() 是延遲執行。它只會開啟檔案並辨識格式,像素資料可能尚未讀取,且檔案句柄可能保持開啟。
在安全/運營上,正確使用 open() 的方式很簡單:
- 先用
open()取得格式、寬高等輕量資訊 - 依政策拒絕:允許格式、最大解析度/像素數、上傳容量限制
- 再進行驗證/解碼
簡言之,open() 應作為「在解碼前提取可判斷資訊」的工具。
verify() 保障什麼,保障什麼不是
Pillow 的 verify() 會嘗試確認檔案是否損毀,但不會實際解碼圖像資料。若發現問題會拋出例外,且在 verify() 後若要繼續使用圖像,必須重新開啟檔案。
安全觀點的結論有兩點:
- 優點:避免重度解碼,快速過濾「損毀檔案」
- 限制:
verify()只表示「目前未顯著損毀」;問題仍可能在load()時才暴露。
load() 在驗證階段隨意呼叫會危險
load() 會真正執行解碼(包含解壓縮)並將像素載入記憶體。此時即成為 DoS(資源耗盡)攻擊面。即使檔案外觀小,解碼後可能膨脹至巨量。
Pillow 會以例外或警告處理「解壓縮炸彈」風險,並設有預設閾值(如 128Mpx)作為保護。
Django 亦因同樣原因,在圖像上傳驗證時使用 verify() 而非 load()。程式碼中有「load() 會將整張圖載入記憶體,成為 DoS 向量」的註解,實際上會在 Image.open() 後呼叫 verify()。
Django/DRF 使用時:ImageField 內部已呼叫 verify(),再呼叫可能重複
Django 的表單 ImageField 驗證會在內部執行 Image.open() + verify(),DRF 的 serializers.ImageField 亦同樣委派給 Django 進行驗證。
因此若已使用 DRF 的 serializers.ImageField:
- 在
validate()中再次呼叫verify()以「檢查損毀」通常是重複工作。 - 若需自訂商業驗證或額外安全檢查,建議改用
FileField(自行設計驗證流程),以明確掌握成本與責任。
為使用者上傳的檔案確保「安全」的實務做法

最實際的答案是:
「不要直接使用上傳原始檔,讓伺服器解碼後再以新檔案儲存,並只使用這個新檔案」。
- 用
open()讀取寬高/格式等低成本資訊,並以政策做一次性拒絕。 - 用
verify()過濾「明顯損毀」的檔案。 - 在受限環境下解碼,並將像素標準化為 RGB/RGBA 等通用格式。
- 伺服器以選定格式重新編碼,產生新檔。
- 服務僅儲存/提供伺服器重新編碼的檔案。
此策略的優點在於「伺服器掌控最終輸出」,能大幅移除原始檔中不必要的元資料或奇怪結構。
當然,重新編碼仍包含 load() 的解碼工作,故需先設定像素數/記憶體限制(防止解壓縮炸彈),並盡量在工作者/隔離流程中執行,以確保安全。
總結
open():辨識 + 低成本資訊確認(像素可能尚未載入)verify():損毀檔案的初步過濾(不解碼,使用前需重新開啟)load():解碼/記憶體使用開始(驗證階段應避免頻繁呼叫)- 上傳安全實務:只信任伺服器重新編碼的結果(但必須先設置限制/隔離)
相關文章: