从安全角度理解 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():解码/内存使用起点(验证阶段禁止滥用)- 上传安全实务:仅信任服务器重编码后的结果(但需先设置限制/隔离)
相关阅读: