必须用 bleach.clean() 在后端入库前清洗用户富文本,仅允许白名单标签及安全属性,禁用 javascript:/data: 协议,前端不得直接 innerHTML 拼接,纯文本用 textContent。

不能直接用 |safe 渲染用户提交的富文本,这是最常见也最危险的误操作。
为什么 {{ content|safe }} 会触发 XSS
因为 |safe 只是告诉 Jinja2 “跳过转义”,它不验证内容是否真的安全。用户输入的 HTML 可能包含:<img src=x onerror=alert(1)>、<script>fetch('/api/steal')</script>、<a href="javascript:..."> 等恶意片段。一旦放行,浏览器就会执行。
- 即使前端用了 Quill 或 CKEditor,它们只负责生成 HTML,不负责清洗
- 后端接收到的
request.form.get('content')是原始字符串,没经过任何过滤 -
Markup(content)和|safe效果等价,都等于“我担保这段 HTML 安全”——而你永远不能担保用户输入
必须用 bleach.clean() 做后端清洗
清洗动作必须在数据入库前完成,不是渲染时才做。推荐用 bleach,它基于白名单机制,比正则过滤可靠得多。
- 只允许基础排版标签:如
['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'] - 限制
a标签的属性,仅保留href,且需校验协议(禁用javascript:、data:) - 用
strip=True自动移除非法标签及其内容,而不是保留裸文本 - 示例:
import bleach<br>cleaned = bleach.clean(<br> user_input,<br> tags=['p', 'strong', 'a'],<br> attributes={'a': ['href']},<br> protocols=['http', 'https'],<br> strip=True<br>)
前端 JS 拼接 HTML 仍是高危场景
即使后端已清洗,若前端用 innerHTML = {{ cleaned_content|safe }},仍可能因上下文错配出问题。比如把清洗后的 HTML 插入到 div 是安全的,但塞进 location.href 或 onload= 属性里就失效了。
立即学习“Python免费学习笔记(深入)”;
- 禁止在 JS 中拼接用户数据到事件处理器、URL 属性、
eval()、setTimeout()等执行上下文中 - 如果必须动态插入 HTML,优先用
DOMPurify.sanitize()在前端再过一遍(作为纵深防御,非替代后端清洗) - 纯文本展示一律用
textContent,不走 HTML 解析路径
真正麻烦的点不在“怎么洗”,而在“洗完存哪”和“谁来决定白名单”。清洗必须发生在第一次接收请求时,且白名单策略要和业务强绑定——比如客服工单允许 table,但评论区就不该开。这些规则一旦写死在代码里,改起来就容易漏。



















