Django模板默认转义仅作用于HTML文本节点,对href、onclick等属性无效;必须按上下文选过滤器:URL用|urlencode或|iriencode,JS字符串需前端JSON.stringify(),CSS类名用|escapejs并加双引号,|safe仅限可控、未读写、经bleach.clean过滤的内容。

Django 模板默认转义只防文本节点,对 href、onclick、class 等属性上下文完全无效——这不是配置没开好,而是 HTML 解析机制决定的。必须按上下文手动选过滤器,否则 {{ user_input }} 在 <p> 里安全,在 <a href="{{ url }}"> 里就可能执行 javascript:alert(1)。
为什么 {{ user_input }} 在 <p> 里安全,但在属性里危险
Django 的自动转义只作用于 HTML 文本节点,不分析语义。它不会、也不能判断你写的是 URL 还是 CSS 类名。
-
<a href="{{ url }}">:若url是javascript:fetch('/api/steal'),浏览器直接执行 -
<div class="{{ cls }}">:若cls是btn onmouseover=alert(1),引号缺失 + 事件绑定绕过转义 -
<script>var x = "{{ data }}";</script>:JS 字符串上下文需前端用JSON.stringify()包裹,后端绝不能拼接
哪些过滤器该用在什么上下文
别靠猜,按输出位置选:
- URL 属性(
href、src):{{ url|urlencode }}(英文路径)或{{ url|iriencode }}(含中文) - JS 字符串变量:
JSON.stringify()前端处理;后端禁止写<script>var x = "{{ data }}";</script> - CSS 类名 / style 值:
{{ cls|escapejs }}处理引号和反斜杠,且必须加双引号包裹:class="{{ cls|escapejs }}" - 内联事件(如
onclick):应避免;真要用,改用data-action+ 事件委托,禁用onclick="{{ handler }}"
|safe 和 mark_safe() 的三个硬性前提
|safe 不是“让富文本显示”的开关,而是你对 Django 的书面承诺:“这段 HTML 已经干净”。缺一不可:
立即学习“Python免费学习笔记(深入)”;
- 内容来源完全可控(如后台管理员手工录入,非用户提交)
- 内容未经过任何数据库读写(避免历史脏数据混入)
- 已用
bleach.clean()白名单过滤(仅保留<p>、<strong>、<a>等,且显式限制attributes={'a': ['href']},禁用javascript:协议)
常见错误:把 TinyMCE 输出的 content 直接 {{ content|safe }}——编辑器切源码模式就能手写 <img src=x onerror=fetch('/api/token')>。
绝对不要碰 {% autoescape off %}
它不是“局部关转义”,而是整块区域(含嵌套子模板)全部裸奔。容易踩的坑:
- 写成
{% autoescape off %}{{ user_input }}{% endautoescape %},结果后面所有{{ }}都失效 - 父模板关了,子模板继承后也继承了这个状态,调试时很难定位
- 漏写
{% endautoescape %},导致后续整个页面变量全裸奔
替代方案更稳:{{ user_input|escape }} 显式转义,或 {{ user_input|default:""|escape }} 组合处理空值。
最易被忽略的一点:XSS 防护不是“开了自动转义就完事”,而是每个输出点都得问一句——我此刻输出到哪?HTML 文本?URL?JS 字符串?CSS?不同上下文,过滤逻辑完全不同;而 |safe 是最后手段,不是快捷方式。


















