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 执行任意代码。
正确做法是用 |tojson,它生成合法 JSON 字符串并自动处理引号、反斜杠、Unicode:
<script>
var msg = {{ user_input | tojson }};
</script>
其他 JS 注入点也要注意:
- 避免
onclick="{{ js_code }}",改用data-message="{{ user_input | tojson }}"+ 前端 JS 读取 - URL 跳转链接如
,需确保redirect_url是绝对 URL 且协议白名单(https?),不能是javascript:或data:
属性值要用单/双引号包裹 + |e,别裸写
Jinja2 的 HTML 转义不防属性注入。写成 必须显式加引号,并用 123 onclick=alert(1),结果变成 |e(等价于 |escape):<div id="{{ user_id | e }}">
<a href="%7B%7B%20url%20%7C%20e%20%7D%7D">Link</a><p>关键细节:</p>
<ul>
<li>
<code>|e</code> 默认不转义单双引号,但加引号后就能覆盖属性内所有危险字符</li>
<li>非必要别把用户数据塞进 <code>class</code>、<code>style</code>、<code>on*</code> 类属性</li>
<li>若必须用于 <code>data-*</code> 属性,仍建议统一用 <code>|tojson</code>(更严格)</li>
</ul>
<h3>富文本必须后端清洗 + 前端二次防护</h3>
<p>允许用户发带 <code><b></b></code>、<code><a href></a></code> 的评论?那不能只靠模板层防护。Jinja2 的 <code>|safe</code> 是“信任开关”,不是“消毒工具”。</p>
<p>实操路径:</p>
<ul>
<li>后端用 <code>bleach.clean(html, tags=['b', 'i', 'a'], attributes={'a': ['href']}, strip=True)</code> 过滤后再传给模板</li>
<li>响应头加上 <code>X-Content-Type-Options: nosniff</code> 防 MIME 混淆</li>
<li>前端拿到 JSON 后,绝不写 <code>el.innerHTML = data.content</code>;改用 <code>DOMPurify.sanitize()</code> 或服务端返回纯文本字段由 <code>textContent</code> 渲染</li>
</ul>
<p>最容易被忽略的是存储环节:自动转义只管“输出”,不管“存进去的是什么”。恶意脚本进了数据库,下次别人查出来再渲染,照样中招——输入验证和存储前过滤不是可选项。</p>
</div>
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











