django自动转义仅防html文本节点,对href、onclick等属性无效,必须按上下文手动选用过滤器;|safe需满足来源可控、未经数据库读写、已用bleach.clean白名单过滤三前提;禁用autoescape或拼接httpresponse属高危操作。
默认启用的模板自动转义只防html文本节点,对 href、onclick、style 等属性上下文完全无效——这不是配置疏漏,而是html解析机制决定的。必须按上下文手动选用过滤器,否则哪怕变量看起来“安全”,也可能触发执行。
为什么 {{ user_input }} 在 <p></p> 里安全,但在 href 或 class 里危险
Django 的自动转义只作用于 HTML 文本节点,不分析语义或上下文。比如:
-
<a href="%7B%7B%20url%20%7D%7D"></a>中,若url是javascript:alert(1),浏览器会直接执行 JS <div class="{{ cls }}"> 中,若 <code>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></script>、on*事件、javascript:协议”的法律声明。只有同时满足以下三点,才能用:- 内容来源完全可控(如后台管理员手工录入的公告 HTML,非用户提交)
- 内容未经过任何数据库读写(避免历史脏数据混入)
- 已用
bleach.clean()做过白名单过滤(仅保留<p></p>、<strong></strong>、<ul></ul>等,剥离所有style属性和事件)
常见错误:把 TinyMCE 编辑器输出的
content直接{{ content|safe }}——哪怕编辑器声称“安全”,它也不阻止用户切换源码模式手写<img src="x" onerror="fetch('/api/token')">。绝对不要禁用
{% 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></p>里用{{ x }}安全,在<iframe src="%7B%7B%20x%20%7D%7D"></iframe>里就是高危。别依赖直觉,每个插值点都要检查 HTML 角色。- URL 上下文:用











