MicroFish/backend/app/utils/security.py

60 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
安全辅助函数(评审 H5 / 上传嗅探 / 路径校验)。
- safe_traceback: 仅在 DEBUG 时把堆栈返回客户端,生产环境只记到服务端日志
- validate_id: 校验 URL 传入的 id避免落入文件系统 sink路径穿越/异常字符)
- upload_content_ok: 按魔术字节嗅探上传内容,防止改扩展名的二进制混入
"""
import os
import re
import traceback as _traceback
from .logger import get_logger
# 允许的 id 字符集:字母数字 + 下划线 + 连字符,长度 1-64。
# 排除 '/'、'.'、'\\' 等可用于路径穿越或越级的字符。
_ID_RE = re.compile(r'^[A-Za-z0-9_-]{1,64}$')
def safe_traceback() -> str:
"""
H5完整堆栈始终写入服务端日志仅在 DEBUG 模式才把堆栈返回客户端,
生产环境返回通用提示,避免向客户端泄露内部路径/栈帧。
"""
# 延迟导入 Config避免与配置模块的潜在循环依赖
from ..config import Config
tb = _traceback.format_exc()
get_logger('mirofish.error').error(tb)
return tb if Config.DEBUG else 'Internal server error (see server logs)'
def validate_id(value: str, kind: str = 'id') -> str:
"""
路径校验:拒绝任何不匹配 _ID_RE 的 id'..''/'、空值),
在 id 进入 os.path.join / makedirs / rmtree 之前阻断路径穿越。
"""
if not isinstance(value, str) or not _ID_RE.match(value):
raise ValueError(f'Invalid {kind}: {value!r}')
return value
def upload_content_ok(file_storage, filename: str) -> bool:
"""
上传嗅探:按扩展名校验文件头部内容,使改名的二进制无法通过扩展名白名单。
- pdf必须以 %PDF- 开头
- txt/md/markdown头部不得含 NUL 字节(典型二进制特征)
读取后将流指针复位,避免影响后续保存。
"""
ext = os.path.splitext(filename)[1].lower().lstrip('.')
head = file_storage.read(512)
file_storage.seek(0)
if ext == 'pdf':
return head[:5] == b'%PDF-'
if ext in ('txt', 'md', 'markdown'):
# 接受带 BOM 的 UTF-16/UTF-32/UTF-8 文本(这些合法文本会含 NUL 字节,
# 与 file_parser 的多编码支持一致);否则按头部含 NUL 判定为二进制并拒绝。
if head.startswith((b'\xff\xfe', b'\xfe\xff', b'\xef\xbb\xbf')):
return True
return b'\x00' not in head
return False