bigywater.tech
Flask · 58

自动转义与 XSS 防护

自动转义到底保护了什么、没保护什么?哪些文件名不会转义?为什么不带引号的属性、javascript: 链接、script 标签里的变量还是会出问题?把输入拼进模板字符串有多危险?这篇每个都实测一遍。

5 个
被转义的字符:< > & ' "
7*7 → 49
把输入拼进模板源码时 {{ 7*7 }} 的结果(SSTI)
.html.j2
不会自动转义的常见模板扩展名

文件名与自动转义

实测 · 本机执行输出
  page.html      → 转义   <p>&lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&gt;&
  page.htm       → 转义   <p>&lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&gt;&
  page.xml       → 转义   <a>&lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&gt;&
  page.xhtml     → 转义   <p>&lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&gt;&
  page.svg       → 转义   <text>&lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&g
  page.txt       → 不转义  text: <b id="a" onclick='x()'>&</b>
  page.js        → 不转义  var a = "<b id="a" onclick='x()'>&</b>";
  page.json      → 不转义  {"a": "<b id="a" onclick='x()'>&</b>"}
  page.html.j2   → 不转义  <p><b id="a" onclick='x()'>&</b></p>
  page.jinja     → 不转义  <p><b id="a" onclick='x()'>&</b></p>
  noext          → 不转义  <p><b id="a" onclick='x()'>&</b></p>
  render_template_string → 转义 &lt;b id=&#34;a&#34; onclick=&#39;x()&#39;&gt;&amp;&lt;/b&gt;
  Flask 的规则:扩展名是 .html .htm .xml .xhtml .svg 之一就开启(render_template_string 总是开启)

上一篇:自定义 Template Test;下一篇:Markup 与 safe。XSS 防护专题:XSS 防护。

转义的字符

实测 · 本机执行输出
  输入 <>&'"/\` → &lt;&gt;&amp;&#39;&#34;/\`

不同位置的输出

实测 · 本机执行输出
  text                 输入 '<script>alert(1)</script>'        → <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>
  带引号的属性               输入 '" onfocus="alert(1)'              → <input value="&#34; onfocus=&#34;alert(1)">
  不带引号的属性              输入 'a onfocus=alert(1)'               → <input value=a onfocus=alert(1)>
  href(javascript:)    输入 'javascript:alert(1)'              → <a href="javascript:alert(1)">link</a>
  <script> 里的字符串       输入 '";alert(1);//'                    → <script>var a = "&#34;;alert(1);//";</script>
  <script> 里的 tojson   输入 '";alert(1);//'                    → <script>var a = "\";alert(1);//";</script>
  <style> 里            输入 'red;} body{display:none'          → <style>p {color: red;} body{display:none}</style>
  单引号属性且值含单引号          输入 "' onfocus='alert(1)"              → <input value='&#39; onfocus=&#39;alert(1)'>
  观察:引号被转义的属性(带引号)是安全的;不带引号的属性、href 里的 javascript: 协议、<script> 里的字符串都没有被「HTML 转义」拯救。

模板注入(SSTI)

实测 · 本机执行输出
  错误写法 name=Ann                        → <h1>你好 Ann</h1>
  错误写法 name={{ 7*7 }}                  → <h1>你好 49</h1>
  错误写法 name={{ config.SECRET_KEY }}    → <h1>你好 lab-secret</h1>
  正确写法 name=Ann                        → <h1>你好 Ann</h1>
  正确写法 name={{ 7*7 }}                  → <h1>你好 {{ 7*7 }}</h1>
  正确写法 name={{ config.SECRET_KEY }}    → <h1>你好 {{ config.SECRET_KEY }}</h1>

关闭转义

实测 · 本机执行输出
  {{ x|safe }}: <b>bold</b>
  {% autoescape false %}…{% endautoescape %}: <b>bold</b>
  {% autoescape true %}(在 .txt 里开启): &lt;b&gt;bold&lt;/b&gt;

tojson

实测 · 本机执行输出
   {"a": "\u003c/script\u003e\u003cscript\u003ealert(1)\u003c/script\u003e", "q": "it\u0027s \u0026 \u003cb\u003e"}
  tojson 返回值的类型: Markup (Markup = 已标记为安全,不会再被转义)

分步演示

自动转义
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动
    • 没有验证:内容安全策略(CSP)对 XSS 的补充防护(见第 123 篇)、浏览器对不同 HTML 解析路径的差异(mXSS)、JSON 在