Become a sponsor

说明
富文本编辑器内容存在存储型 XSS 风险,通过 bleach 库实现 HTML 白名单清洗,在落盘前统一过滤危险内容。源码位于 src/utils/rich_text.py,涉及富文本的模块(文章、通知公告等)均调用 save_content() 处理。
富文本编辑器允许用户输入 HTML 内容,攻击者可能注入:
1. <script> 标签 —— 执行任意 JavaScript
2. on* 事件属性 —— 如 onerror="alert(1)"
3. javascript: 协议 —— 如 <a href="javascript:alert(1)">
4. CSS 表达式 —— 如 style="background:expression(alert(1))"位于 src/utils/rich_text.py,定义了严格的白名单:
_RICH_TEXT_TAGS = {
'p', 'br', 'hr', 'strong', 'em', 'b', 'i', 'u', 's', 'small', 'sub', 'sup',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'ul', 'ol', 'li', 'blockquote', 'pre', 'code',
'span', 'div', 'a', 'img',
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
}_RICH_TEXT_ATTRS = {
'*': ['class', 'style'],
'a': ['href', 'title', 'target', 'rel', 'name'],
'img': ['src', 'alt', 'title', 'width', 'height'],
'td': ['colspan', 'rowspan', 'width', 'height', 'align'],
'th': ['colspan', 'rowspan', 'width', 'height', 'align'],
'table': ['width', 'border', 'cellspacing', 'cellpadding', 'align'],
}_RICH_TEXT_PROTOCOLS = {'http', 'https', 'mailto', 'tel'}save_content() 函数处理富文本内容:
def save_content(content, title, directory):
# 1. 白名单清洗:剥离 script/事件属性/javascript: 协议等 XSS 向量
content = _RICH_TEXT_CLEANER.clean(content)
# 2. 兜底:剥离 style 中遗留的 CSS 表达式等危险向量
content = _strip_dangerous_style(content)
# 3. 提取并迁移内容中的本地图片
image_urls = re.findall('img src="(.*?)"', content, re.S)
for url in image_urls:
image = save_file(url, directory)
if image:
content = content.replace(url, "[IMG_URL]" + image)
# 4. 设置图片 alt 属性为文章标题
title = html.escape(str(title) if title else "", quote=True)
if "alt=\"\"" in content and title:
content = content.replace("alt=\"\"", "alt=\"" + title + "\"")
return contentbleach 仅按属性白名单过滤,对 style 值中的 CSS 表达式不做检查。额外增加正则兜底:
_DANGEROUS_CSS_RE = re.compile(r'expression\s*\(|javascript\s*:', re.IGNORECASE)
def _strip_dangerous_style(content):
"""丢弃含危险 CSS 表达式的 style 属性"""
def _drop(match):
return '' if _DANGEROUS_CSS_RE.search(match.group(0)) else match.group(0)
return _STYLE_ATTR_RE.sub(_drop, content)get_file_url() 函数拼接文件域名生成完整访问 URL:
def get_file_url(path):
if not path:
return ""
if path.find(FASTAPI_FILE_URL) != -1:
return path
return FASTAPI_FILE_URL + pathsave_file() 函数将临时目录中的文件迁移到正式存储目录:
临时路径: {temp}/{日期}/{文件名}
正式路径: {upload_dir}/{分类}/{日期}/{文件名}迁移时会做路径穿越检查,确保不会跳出 UPLOAD_DIR 目录。
温馨提示
富文本内容在 service 层调用 save_content() 处理,而不是在 endpoint 层。所有涉及富文本的模块(文章、通知公告等)都应使用此函数清洗内容。
富文本过滤通过 bleach 白名单清洗 + CSS 表达式正则兜底两层防护,有效防止存储型 XSS 攻击。同时自动处理内容中的图片文件迁移和 URL 拼接,确保文件路径安全且可访问。