自动转义防住了多少?它防不住什么?这篇不讲原理,直接用一组「攻击字符串」喂给不同的上下文,看输出。
{{ x }} → <b>&"'</b>
{{ x|safe }} → <b>&"'</b>
mark_safe 的对象 → <b>&"'</b>
{% autoescape off %}{{ x }}{% endautoescape %} → <b>&"'</b>
对同一个字符串既 safe 又 upper(escape 时机): <B>A&AMP;B</B>safe 是给字符串打「已经安全」的标记,不是「把它变安全」。
href="{{ url }}" → <a href="javascript:alert(1)">x</a> ← 不会被转义或拦截(需要自己校验协议)
<script>var n = "{{ name }}";</script> → <script>var n = "";alert(1);//";</script> ← HTML 转义把 " 变成 ",在 JS 字符串里并不是安全的写法
用 escapejs 过滤器: <script>var n = "\u0022\u003Balert(1)\u003B//";</script>
用 json_script(推荐): <script id="d" type="application/json">{"name": "\";alert(1);//", "x": "\u003C/script\u003E\u003Cb\u003E"}</script>自动转义是针对 HTML 文本上下文的。属性、URL、<script>、CSS 各有各的转义规则:URL 要校验协议,JS 要用 json_script(或 escapejs)。
<input value={{ v }}> → <input value=x onmouseover=alert(1)> ← 属性没加引号,空格仍能截断
<input value='{{ v2 }}'> → <input value='' onclick='x'>Django 转义了单引号('),所以带引号的属性是安全的;没引号的属性靠空格就能破坏。
format_html("<p>{}</p>", u) → <p><img src=x onerror=alert(1)></p>
mark_safe("<p>" + u + "</p>") → <p><img src=x onerror=alert(1)></p> ← 用户输入被原样标成安全
escape(u) → <img src=x onerror=alert(1)>凡是要产出 HTML 的 Python 代码用 format_html、format_html_join,不要手工拼字符串再 mark_safe。
模板里的字面量 {{ "<i>lit</i>" }} → <i>lit</i>
join 过滤器对列表元素转义: <a>, b
linebreaksbr 对内容转义、只把换行变成 <br>: <x><br>line2
urlize 转链接并转义: see <a href="http://a.com/?q=" rel="nofollow">http://a.com/?q=</a><x>这些是常见过滤器在自动转义下的行为。