Jinja2默认转义防XSS,但|safe会彻底禁用防护;JS上下文必须用|tojson而非{{}};属性值需加引号并用|e;富文本须后端bleach清洗+前端DOMPurify二次防护。

默认{{ }}插值已转义,别乱加|safe
Jinja2 对 {{ user_input }} 默认执行 html.escape(),输入 <script>alert(1)</script> 会原样显示为文本,不会执行。但只要模板里出现 {{ user_input | safe }},这层保护就彻底失效——Jinja2 直接输出原始字符串,XSS 立刻触发。
常见错误场景:
- 调试时临时加
|safe查看富文本效果,上线前忘记删掉 - 把数据库读出的用户评论字段直接套
|safe渲染,没做任何清洗 - 误以为“只是显示昵称”,在
id="{{ name | safe }}"里放行
真正需要 |safe 的场景极少:仅限 CMS 后台经 bleach.clean() 过滤后的富文本,且该内容已人工审核或有强白名单控制。
JS上下文必须用|tojson,不能直接插变量
HTML 转义对 JS 语法无效。<script>var msg = "{{ user_input }}";</script> 中,哪怕 user_input 是 " 或 \,也会破坏字符串边界,导致 JS 执行任意代码。
立即学习“Python免费学习笔记(深入)”;
正确做法是用 |tojson,它生成合法 JSON 字符串并自动处理引号、反斜杠、Unicode:
<script>
var msg = {{ user_input | tojson }};
</script>
其他 JS 注入点也要注意:
- 避免
onclick="{{ js_code }}",改用data-message="{{ user_input | tojson }}"+ 前端 JS 读取 - URL 跳转链接如
<a href="%7B%7B%20redirect_url%20%7D%7D"></a>,需确保redirect_url是绝对 URL 且协议白名单(https?),不能是javascript:或data:
属性值要用单/双引号包裹 + |e,别裸写
Jinja2 的 HTML 转义不防属性注入。写成 <div id="{{" user_id> 是危险的——攻击者输 <code>123 onclick=alert(1),结果变成 <div id="123" onclick="alert(1)">,事件直接绑定。
<p>必须显式加引号,并用 <code>|e(等价于 |escape):
<div id="{{ user_id | e }}">
<a href="{{ url | e }}">Link</a>
关键细节:
-
|e默认不转义单双引号,但加引号后就能覆盖属性内所有危险字符 - 非必要别把用户数据塞进
class、style、on*类属性 - 若必须用于
data-*属性,仍建议统一用|tojson(更严格)
富文本必须后端清洗 + 前端二次防护
允许用户发带 <b></b>、<a href></a> 的评论?那不能只靠模板层防护。Jinja2 的 |safe 是“信任开关”,不是“消毒工具”。
实操路径:
- 后端用
bleach.clean(html, tags=['b', 'i', 'a'], attributes={'a': ['href']}, strip=True)过滤后再传给模板 - 响应头加上
X-Content-Type-Options: nosniff防 MIME 混淆 - 前端拿到 JSON 后,绝不写
el.innerHTML = data.content;改用DOMPurify.sanitize()或服务端返回纯文本字段由textContent渲染
最容易被忽略的是存储环节:自动转义只管“输出”,不管“存进去的是什么”。恶意脚本进了数据库,下次别人查出来再渲染,照样中招——输入验证和存储前过滤不是可选项。


















