bigywater.tech
Flask · 59

Markup 与 safe

Markup 到底是什么?|safe 做了什么?为什么用 f-string 包一层 Markup 会出 XSS?什么写法才是安全地拼接 HTML?这篇用同一个恶意输入把各种写法对比一遍。

1 种
本篇测试的 5 种写法里唯一直接危险的写法:Markup(f"…{u}…")
__html__
Markup 与自定义对象共用的「已是安全 HTML」协议
str
striptags() 与 unescape() 返回的类型(不再是安全的)

Markup 与 escape

实测 · 本机执行输出
  Markup("<b>x</b>"): Markup('<b>x</b>') | 是 str: True | 有 __html__: True
  escape("<b>&\"'"): Markup('&lt;b&gt;&amp;&#34;&#39;') | 类型: Markup
  对 Markup 再 escape 不会双重转义: Markup('<b>') | escape(Markup("&amp;")): Markup('&amp;')
  escape(None): Markup('None') | escape(5): Markup('5') | escape(对象有 __html__): Markup('<i>已是 HTML</i>')

上一篇:自动转义与 XSS 防护;下一篇:Jinja2 宏。过滤器返回 HTML 的写法也见自定义 Template Filter。

Markup 的运算

实测 · 本机执行输出
  Markup("<b>") + u      → Markup('<b>&lt;script&gt;alert(1)&lt;/script&gt;')
  u + Markup("<b>")      → Markup('&lt;script&gt;alert(1)&lt;/script&gt;<b>')
  Markup("<b>%s</b>") % u → Markup('<b>&lt;script&gt;alert(1)&lt;/script&gt;</b>')
  Markup("<b>{}</b>").format(u) → Markup('<b>&lt;script&gt;alert(1)&lt;/script&gt;</b>')
  Markup(f"<b>{u}</b>")(错误写法,先格式化再标记安全)→ Markup('<b><script>alert(1)</script></b>')
  "<b>%s</b>" % u 再 Markup(...) → Markup('<b><script>alert(1)</script></b>')
  Markup.join: Markup('&lt;a&gt;<br>b&amp;c') | Markup 拼接 Markup: Markup('<i><b>')
  切片、upper、replace 的结果仍然是 Markup: Markup Markup Markup | replace 里的新文本被转义: Markup('<b>&lt;y&gt;</b>')

模板里的过滤器

实测 · 本机执行输出
  x               → &lt;b&gt;A &amp;amp; B&lt;/b&gt;
  x|safe          → <b>A &amp; B</b>
  x|e             → &lt;b&gt;A &amp;amp; B&lt;/b&gt;
  x|escape        → &lt;b&gt;A &amp;amp; B&lt;/b&gt;
  x|forceescape   → &lt;b&gt;A &amp;amp; B&lt;/b&gt;
  x|striptags     → A &amp; B
  x|string        → &lt;b&gt;A &amp;amp; B&lt;/b&gt;
  x|upper         → &lt;B&gt;A &amp;AMP; B&lt;/B&gt;
  x|safe|upper    → <B>A &AMP; B</B>
  x|upper|safe    → <B>A &AMP; B</B>

__html__

实测 · 本机执行输出
  {{ w }} → <button>&lt;保存&gt;</button> | {{ w|string }} → Widget(&lt;保存&gt;) | {{ [w]|join(",") }} → <button>&lt;保存&gt;</button>

哪种写法安全

实测 · 本机执行输出
  Markup(f"<a title=\"{u}\">")                   → 危险 <a title=""><img src=x onerror=alert(1)>">
  Markup("<a title=\"{}\">").format(u)           → 安全 <a title="&#34;&gt;&lt;img src=x onerror=alert(1)&gt;">
  Markup("<a title=\"%s\">") % u                 → 安全 <a title="&#34;&gt;&lt;img src=x onerror=alert(1)&gt;">
  Markup("<a title=\"") + u + Markup("\">")      → 安全 <a title="&#34;&gt;&lt;img src=x onerror=alert(1)&gt;">
  "<a title=\"" + escape(u) + "\">"              → 安全 &lt;a title=&#34;&#34;&gt;&lt;img src=x onerror=alert(1)&gt;&#34;&gt;

striptags 与 unescape

实测 · 本机执行输出
  Markup("<p>a &amp; <b>b</b></p>").striptags() → 'a & b' | Markup("&lt;b&gt;").unescape() → '<b>'

分步演示

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,数字每次运行会有小幅抖动。markupsafe 3.0.3
    • 没有验证:Markup 与 Python 其它字符串方法(如 translate、partition)的所有返回类型、Markup 在 pickle 与缓存里的表现、Jinja 沙箱环境里 Markup 的限制
    京ICP备2026067502号-1京公网安备11010502063487号