Skip to content

富文本过滤

说明

富文本编辑器内容存在存储型 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,定义了严格的白名单:

允许的标签

python
_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',
}

允许的属性

python
_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'],
}

允许的协议

python
_RICH_TEXT_PROTOCOLS = {'http', 'https', 'mailto', 'tel'}

清洗流程

save_content() 函数处理富文本内容:

python
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 content

CSS 表达式防护

bleach 仅按属性白名单过滤,对 style 值中的 CSS 表达式不做检查。额外增加正则兜底:

python
_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)

文件 URL 处理

get_file_url() 函数拼接文件域名生成完整访问 URL:

python
def get_file_url(path):
    if not path:
        return ""
    if path.find(FASTAPI_FILE_URL) != -1:
        return path
    return FASTAPI_FILE_URL + path

临时文件迁移

save_file() 函数将临时目录中的文件迁移到正式存储目录:

临时路径: {temp}/{日期}/{文件名}
正式路径: {upload_dir}/{分类}/{日期}/{文件名}

迁移时会做路径穿越检查,确保不会跳出 UPLOAD_DIR 目录。

温馨提示

富文本内容在 service 层调用 save_content() 处理,而不是在 endpoint 层。所有涉及富文本的模块(文章、通知公告等)都应使用此函数清洗内容。

总结

富文本过滤通过 bleach 白名单清洗 + CSS 表达式正则兜底两层防护,有效防止存储型 XSS 攻击。同时自动处理内容中的图片文件迁移和 URL 拼接,确保文件路径安全且可访问。

小蚂蚁云团队 · 提供技术支持