Django自动转义仅防HTML文本节点,对href、onclick等属性无效,必须按上下文手动选用过滤器;|safe需满足来源可控、未经数据库读写、已用bleach.clean白名单过滤三前提;禁用autoescape或拼接HttpResponse属高危操作。
默认启用的模板自动转义只防html文本节点,对 href、onclick、style 等属性上下文完全无效——这不是配置疏漏,而是html解析机制决定的。必须按上下文手动选用过滤器,否则哪怕变量看起来“安全”,也可能触发执行。
为什么 {{ user_input }} 在 <p> 里安全,但在 href 或 class 里危险
Django 的自动转义只作用于 HTML 文本节点,不分析语义或上下文。比如:
-
<a href="{{ url }}">中,若url是javascript:alert(1),浏览器会直接执行 JS -
<div class="{{ cls }}">中,若cls是btn onmouseover=alert(1),引号缺失 + 事件绑定就绕过转义
这类场景下,Django 不会、也不能自动判断你写的是 URL 还是 CSS 类名,所以必须人工指定上下文过滤器:
- URL 上下文:用
{{ url|urlencode }}(或|iriencode处理中文路径) - JavaScript 字符串上下文:前端用
JSON.stringify()包裹,后端别拼<script>var x = "{{ data }}";</script> - CSS 类名/样式值:强制加引号,并用
{{ cls|escapejs }}处理引号和反斜杠 - 内联事件:应避免;真要用,改用
data-属性 + 事件委托,绝不用onclick="{{ handler }}"
|safe 和 mark_safe() 的三个硬性前提
|safe 不是“让富文本显示出来”的开关,而是“我已确认这段 HTML 不含 <script>、on* 事件、javascript: 协议”的法律声明。只有同时满足以下三点,才能用:
- 内容来源完全可控(如后台管理员手工录入的公告 HTML,非用户提交)
- 内容未经过任何数据库读写(避免历史脏数据混入)
- 已用
bleach.clean()做过白名单过滤(仅保留<p>、<strong>、<ul>等,剥离所有style属性和事件)
常见错误:把 TinyMCE 编辑器输出的 content 直接 {{ content|safe }}——哪怕编辑器声称“安全”,它也不阻止用户切换源码模式手写 <img src=x onerror=fetch('/api/token')>。
立即学习“Python免费学习笔记(深入)”;
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
绝对不要禁用 {% autoescape %}
{% autoescape off %} 不是“局部关闭转义”的快捷方式,它会让从该标签开始到 {% endautoescape %} 之间所有 {{ }} 变量全部裸奔,包括后续 {% include %} 进来的子模板里的变量。
实际项目中,99% 的所谓“需要关转义”需求,本质是前端结构没设计好。正确解法是:
- 把需要渲染 HTML 的部分拆成独立字段(如
rich_intro) - 在视图里用
bleach.clean()处理后再传给模板 - 模板中仍用
{{ rich_intro|safe }},但安全责任明确落在清洗环节
另外,直接用 HttpResponse 拼接用户输入(如 HttpResponse(f"<div>{user_input}</div>"))是 XSS 最常见入口——这种写法完全绕过 Django 转义机制,必须改用 render() 配合模板变量。
最易被忽略的一点:XSS 风险不是“有没有过滤”,而是“是否匹配上下文”。同一个变量,在 <p> 里用 {{ x }} 安全,在 <iframe src="{{ x }}"> 里就是高危。别依赖直觉,每个插值点都要检查 HTML 角色。

















