flask文件上传必须校验后缀、使用secure_filename、独立存储目录、设置max_content_length并禁用执行权限。仅依赖前端检查或content_type校验不安全,需多层防护防止路径遍历与恶意文件执行。

Flask request.files 不校验后缀,上传任意文件就危险
用户传个 .py 或 .sh 文件,直接落在服务器上,再被 Web 服务意外执行,等于白送 shell。Flask 默认只管收,不管验——request.files 拿到的是原始文件对象,连后缀都不拆开看一眼。
实操建议:
- 别信前端
accept属性或 JS 后缀检查,它们全可绕过 - 必须用
werkzeug.utils.secure_filename()处理原始文件名,否则../../etc/passwd这类路径遍历会成功 - 校验逻辑要放在
save()前:先取file.filename,用os.path.splitext()拆出扩展名,转小写后比对白名单 - 避免用
file.content_type做唯一判断——浏览器可伪造,且空文件或二进制文件常报application/octet-stream
示例关键片段:
from werkzeug.utils import secure_filename
ALLOWED_EXTENSIONS = {'.jpg', '.png', '.pdf'}
file = request.files['upload']
if file and '.' in file.filename:
ext = os.path.splitext(file.filename)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
return 'File type not allowed', 400
filename = secure_filename(file.filename)
file.save(os.path.join('/safe/upload/dir', filename))
上传目录不能是 Web 根目录或有执行权限
把文件存到 /var/www/html/uploads/ 这种地方,等于告诉攻击者:“请上传一个 shell.php,然后访问它”。即使限制了后缀,服务器配置稍有松动(比如开启 MultiViews 或错误启用 php_flag engine on),照样崩。
实操建议:
- 上传目录绝对路径必须独立于 Web 可访问路径,推荐放在项目外如
/data/uploads/ - 该目录属主设为运行 Flask 的非 root 用户(如
www-data),权限设为750,禁止 group/o 写入 - Nginx/Apache 配置中显式禁止对该目录下所有文件执行:
location ~ ^/uploads/.*\.(php|py|sh)$ { return 403; } - 如果必须通过 Web 提供下载,用 Flask 路由中读取文件并
send_file(),而非直接暴露路径
max_content_length 必须设,否则大文件拖垮服务
不设这个值,用户发个 2GB 的视频,Flask 会试图全读进内存再解析 form,轻则超时、OOM,重则触发系统级 kill。这不是防恶意,是保服务可用。
实操建议:
- 在 Flask 实例创建后立即设置:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024(16MB) - 该值单位是字节,不是 KB 或 MB;超过会直接返回
413 Request Entity Too Large,不进视图函数 - 若需支持大文件,改用分片上传 + 后端合并,而不是调高这个阈值
- 注意:此配置只限制整个 HTTP body,不区分是否为文件字段;若有其他大文本字段,需一并权衡
secure_filename() 不能替代内容扫描,但能拦住大部分路径穿越
secure_filename() 会删掉所有非 ASCII 字符、空格、路径分隔符,把 ../../../.env 变成 .env——但它不会阻止同名文件覆盖,也不防恶意内容本身(比如 PDF 里藏 XSS 或恶意 JS)。
实操建议:
- 它只解决“文件名安全”,不是“文件内容安全”;对敏感系统,上传后仍需做病毒扫描或 MIME 类型二次校验(如用
python-magic) - 别自己写正则替换文件名,
secure_filename()已处理 Unicode 归一化、控制字符等边缘 case - 如果业务允许,上传后重命名文件(如用 UUID),彻底断开原始文件名关联
- 特别注意:Windows 下
secure_filename()对:*?"|的处理和 Linux 不同,跨平台部署时务必测试
路径穿越和内容风险从来不在同一层,前者靠 secure_filename() + 独立目录压制,后者得靠后续环节补位——漏掉哪一层,都可能让前面所有限制形同虚设。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











