Python/Django中跨平台规范化相对路径的安全方案
跨平台规范化Django文件上传相对路径的问题及解决方案
问题场景与错误代码
在Django中实现跨平台的带时间戳文件上传路径时,原代码触发SuspiciousFileOperation异常,代码如下:
def get_filepath(instance, filename) -> str: """ Returns a timestamped directory path to deposit files into. i.e. "evidence/2023/02/23/96672aa5-94d9-4289-8531-d7a8dc8f060d/data.jpg" """ dt = timezone.now() YYYY = dt.year MM = str(dt.month).zfill(2) DD = str(dt.day).zfill(2) UID = str(uuid.uuid4()) # settings.MEDIA_URL = 'media/', I just checked path = (str(x) for x in ( settings.MEDIA_URL, 'evidence', YYYY, MM, DD, UID, filename )) path = os.path.join(*path) # dubious return path
错误信息
django.core.exceptions.SuspiciousFileOperation: Detected path traversal attempt in '/media/evidence\2023\02\23\1cfa40b9-b522-4c70-b59b-fd270d722ab2\templeos.txt'
尝试过的无效方法
- 使用
pathlib.PurePath:Django的FileField无法直接识别Path对象,会误判为绝对路径并添加系统前缀 - 使用
os.path.normpath:因路径包含绝对前缀,仍触发"路径超出基础组件"的异常 - 手动清理路径组件:虽生效但实现粗糙,希望用正规库处理
跨平台规范化相对路径的标准做法
1. 核心修正:区分URL路径与文件系统路径
Django的upload_to函数返回的是相对于MEDIA_ROOT的文件系统路径,不需要包含MEDIA_URL(这是前端访问用的URL前缀)。原代码的核心错误就是将MEDIA_URL(带开头斜杠的绝对URL路径)混入文件系统路径拼接,导致跨平台下被误判为路径遍历。
2. 用pathlib实现跨平台路径拼接(推荐)
pathlib是Python3.4+的标准库,自动适配不同平台的路径分隔符,转为字符串后可直接被Django识别:
from pathlib import Path import uuid from django.utils import timezone from django.utils.text import get_valid_filename def get_filepath(instance, filename) -> str: """ Returns a timestamped directory path to deposit files into. i.e. "evidence/2023/02/23/96672aa5-94d9-4289-8531-d7a8dc8f060d/data.jpg" """ # 清理文件名,防止路径遍历字符(如../) filename = get_valid_filename(filename) dt = timezone.now() path_components = [ "evidence", str(dt.year), f"{dt.month:02d}", f"{dt.day:02d}", str(uuid.uuid4()), filename ] # 拼接后转为字符串,自动适配平台分隔符 return str(Path(*path_components))
3. 用os.path.join实现跨平台拼接
如果偏好传统方式,确保所有路径组件都是相对路径(无开头斜杠),os.path.join会自动处理跨平台分隔符:
import os import uuid from django.utils import timezone from django.utils.text import get_valid_filename def get_filepath(instance, filename) -> str: filename = get_valid_filename(filename) dt = timezone.now() return os.path.join( "evidence", str(dt.year), f"{dt.month:02d}", f"{dt.day:02d}", str(uuid.uuid4()), filename )
4. 额外安全措施:清理用户上传的文件名
使用Django内置的get_valid_filename函数,可自动过滤文件名中的路径遍历字符(如../)、特殊符号等,进一步避免安全风险。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

