用户上传的文件有一套自己的规矩:谁来提供、文件名会被怎样处理、大文件放在哪、有哪些上限。这篇在自己的测试应用上逐项验证,所有「恶意文件名」都只是对 FileField 自己的防护做测试。
DEBUG = True → static(MEDIA_URL, document_root=…) 返回的路由数: 1 GET /media/a.txt → 200 DEBUG = False → static(MEDIA_URL, document_root=…) 返回的路由数: 0 GET /media/a.txt → 404
上传 'a b.txt' → 保存为 'docs/a_b.txt' 在 MEDIA_ROOT 内: True 保存名长度 12 上传 '../../evil.txt' → 保存为 'docs/evil.txt' 在 MEDIA_ROOT 内: True 保存名长度 13 上传 '/etc/passwd' → 保存为 'docs/passwd' 在 MEDIA_ROOT 内: True 保存名长度 11 上传 'rep.txt' → 保存为 'docs/rep.txt' 在 MEDIA_ROOT 内: True 保存名长度 12 上传 'rep.txt' → 保存为 'docs/rep_UvQcZtV.txt' 在 MEDIA_ROOT 内: True 保存名长度 20 上传 '报告.txt' → 保存为 'docs/报告.txt' 在 MEDIA_ROOT 内: True 保存名长度 11 上传 'xxxxxxxxxx…(304 字符)' → 保存为 'docs/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' 在 MEDIA_ROOT 内: True 保存名长度 100 上传 'page.html' → 保存为 'docs/page.html' 在 MEDIA_ROOT 内: True 保存名长度 14
这是 FileField.save() / 存储后端的处理。如果你自己拼路径(比如把用户给的文件名直接传给 open()),这些保护就不存在了。
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 字节 上传 1 MiB → 请求里的文件对象类型 InMemoryUploadedFile,大小 1048576 上传 3 MiB → 请求里的文件对象类型 TemporaryUploadedFile,大小 3145728
DATA_UPLOAD_MAX_MEMORY_SIZE 设成 1000 字节:
上传 500000 字节的文件 → 200
提交 2000 字节的普通字段 → 400
DATA_UPLOAD_MAX_NUMBER_FILES = 100
一次上传 100 个文件 → 200
一次上传 101 个文件 → 400文件本身的总大小没有被这两个设置限制,要限制上传大小,需要在代理(nginx 的 client_max_body_size)或视图 / 表单里做;本文没有测代理。
GET /dl/page.html Content-Type: text/html Content-Disposition: inline; filename="page.html" GET /dl/page.html?att=1 Content-Type: text/html Content-Disposition: attachment; filename="page.html" GET /dl/a.txt Content-Type: text/plain Content-Disposition: inline; filename="a.txt"
用户上传的内容不应当用站点自己的域名、以 inline 方式提供;更稳妥的做法是用单独的域名或者强制下载。这是通用的安全建议,本文只验证了响应头的差别,没有在浏览器里执行。