Django 模板默认对变量做 HTML 转义,大部分 XSS 就这样挡掉了。这篇不重复概念,只用同一个恶意字符串把「哪些写法安全、哪些写法悄悄关掉了保护」逐个渲染出来。
{{ p }} '<script>alert(1)</script>' 含原样的 <script>: False
{{ p|safe }} '<script>alert(1)</script>' 含原样的 <script>: True
{% autoescape off %}{{ p }}{% endautoescape %} '<script>alert(1)</script>' 含原样的 <script>: True
{{ p }}(变量被 mark_safe 过) '<script>alert(1)</script>' 含原样的 <script>: True
format_html("<b>{}</b>", P) '<b><script>alert(1)</script></b>' 含原样的 <script>: False
mark_safe("<b>" + P + "</b>")(拼接) '<b><script>alert(1)</script></b>' 含原样的 <script>: True用户输入一旦经过 mark_safe 或 |safe,后面的模板引擎就认为它是可信的 HTML。mark_safe 只该用在你自己完全控制的字符串上。
title="{{ q }}" → <div title="" onmouseover="alert(1)">x</div>
title={{ q }}(没加引号)→ <div title=x onmouseover=alert(1)>x</div> ← 空格没被转义,多出了一个属性
href="{{ u }}",u = javascript:alert(1) → <a href="javascript:alert(1)">x</a> (原样保留)
url_has_allowed_host_and_scheme('javascript:alert(1)' , allowed_hosts={"example.com"}) → False
url_has_allowed_host_and_scheme('https://example.com/x' , allowed_hosts={"example.com"}) → True
url_has_allowed_host_and_scheme('/local/path' , allowed_hosts={"example.com"}) → True
url_has_allowed_host_and_scheme('//evil.example/x' , allowed_hosts={"example.com"}) → False自动转义是针对 HTML 文本和带引号属性的。属性值不加引号、URL 里的 javascript: 协议都不在它的保护范围;用户提供的链接要先校验协议与主机再输出。
escapejs → <script>var d = \u003C/script\u003E\u003Cscript\u003Ealert(1)\u003C/script\u003E;</script>
json_script → <script id="cfg" type="application/json">{"name": "\u003C/script\u003E\u003Cscript\u003Ealert(1)\u003C/script\u003E", "n": 1}</script>
json_script 输出里含原样的 </script>: False
只用自动转义放进 JS 字符串 → <script>var name = "";alert(1);//";</script> ← 引号被转成 ",但这不是 JS 的转义方式,不要这样写 {{ t|linebreaks }} → '<p><script>alert(1)</script></p>' 含原样的 <script>: False
{{ t|urlize }} → '<script>alert(1)</script>' 含原样的 <script>: False
{{ t|linebreaksbr }} → '<script>alert(1)</script>' 含原样的 <script>: False
{{ t|truncatechars:40 }} → '<script>alert(1)</script>' 含原样的 <script>: False
{{ t|striptags }} → 'alert(1)' 含原样的 <script>: False
{{ t|safe|linebreaks }} → '<p><script>alert(1)</script></p>' 含原样的 <script>: True
{{ t|safe|truncatechars:40 }} → '<script>alert(1)</script>' 含原样的 <script>: True官方文档对 striptags 有「不能当作安全清洗工具」的提醒,本文没有验证这一点,只演示了它对这个字符串的输出。