Django等模板引擎默认开启autoescape,但仅对HTML文本节点转义,不分析上下文;在href、onclick等属性中直接输出未适配转义的变量会触发XSS,因浏览器将javascript:或on*事件视为可执行代码。

autoescape 默认开着,但关了它或乱用 |safe 就等于把门钥匙交给攻击者——绝大多数XSS漏洞不是框架没防护,而是开发者主动绕过了防护。
为什么 {{ user_input }} 在 <p> 里安全,在 href 或 onclick 里却危险
Django 和 Flask 的模板引擎只对 HTML 文本节点做转义,不分析上下文。比如:<a href="{{ url }}"> 中,如果 url 是 javascript:alert(1),浏览器会直接执行;又比如 <div class="{{ cls }}">,若 cls 是 btn onmouseover=alert(1),引号缺失+事件绑定就触发了 JS。
- HTML 文本节点:默认
{{ }}足够安全(转义、<code>>、&、"、') - URL 上下文:必须用
{{ url|urlencode }},不能拼javascript:或data:协议 - JavaScript 字符串上下文:必须用
{{ data|tojson }}(Flask/Jinja2)或{{ data|escapejs }}(Django),绝不用"{{ data }}" - 内联事件属性(如
onclick):应避免,改用data-*属性 + 前端事件委托
|safe 不是“让富文本显示”的开关,而是安全责任声明
加了 |safe,等于告诉模板引擎:“这段 HTML 我已确认不含 <script>、on* 事件、javascript: 协议”。它不校验,只放行。
- 仅当同时满足三个条件才可用:
内容来源完全可控(如后台管理员录入)、未经过任何数据库读写(避免历史脏数据混入)、已用 bleach.clean() 白名单过滤(例如只留<p>、<strong>、<ul>) - 常见错误:把 TinyMCE 编辑器输出的
content直接{{ content|safe }}——编辑器无法阻止用户切源码模式手写<img src=x onerror=fetch('/api/token')> - 禁用
{% autoescape off %}极其危险:它会让从该标签开始到{% endautoescape %}所有变量裸奔,包括后续{% include %}进来的子模板里的变量
别在 Python 层拼 HTML,也别信“前端转义就够了”
用 f"<h2>{name}</h2>" 或 str.format() 拼响应,等于彻底放弃模板引擎的转义机制。而只靠前端 innerText 或简单正则替换,漏掉 onerror、style=expression(...) 等隐蔽入口。
立即学习“Python免费学习笔记(深入)”;
- 后端必须做输出转义:Python 标准库
html.escape()可用于非模板场景(如生成邮件 HTML) - 前端不能替代后端防护:DOMPurify 是兜底手段,不是主力——它运行在用户浏览器,无法防止初始页面渲染时的 XSS
- CSP(Content-Security-Policy)是重要补充,但不能代替输入/输出控制:它拦不住内联事件或已存在的恶意 script 标签
真正难防的是那些“看起来不像代码”的输入:昵称里的 onmouseover=alert(1)、URL 参数里的 %3Cscript%3E、甚至 base64 编码的 data:text/html;base64,...。安全不是加个过滤器就完事,而是每个变量输出前,都得问一句:它出现在什么上下文?用了哪个转义方式?谁最终为这段 HTML 的安全性签字负责?


















