JSON 工具箱

JSON 裡的 XSS 是怎麼發生的

結論:JSON 本身不會執行,XSS 發生在「把 JSON 當 HTML 用」的那一步。伺服器拼頁面、前端用 innerHTML、把 JSON 直接塞進 `<script>` 標籤,是三個最常見的入口。

進階約 4 分鐘發佈於 2026-10-30更新於 2026-10-30
JSON 資料引發 XSS 的三個入口與防護

結論:JSON 本身不會執行,防護要先堵住三個輸出入口——伺服器按上下文轉義、前端用 textContent 取代 innerHTML、內嵌 JSON 時把 < 轉成 \u003c。

{"name": "<img src=x onerror=alert(1)>"} 這段資料是合法的。它的危險性取決於你把它放到哪裡。

入口一:伺服器拼接 HTML

res.send(`<div>歡迎,${data.name}</div>`);

data.name 裡的標籤被瀏覽器當成 HTML 解析,腳本執行。JSON 在這裡只是「裝著攻擊載荷的容器」,真正的問題是沒有按 HTML 上下文轉義。

修法:範本引擎預設轉義(EJS 的 <%= %>、Vue 的 {{ }}、Django 的 {{ }}),別用 <%- %>、v-html、|safe 這類捷徑去渲染使用者資料。

入口二:前端 innerHTML

el.innerHTML = user.bio;    // 危險
el.textContent = user.bio;  // 安全

textContent 把內容當純文字插入;innerHTML 會解析標籤。 React 的 {user.bio}、Vue 的 {{ user.bio }} 預設走文字節點,是安全的;一旦用 dangerouslySetInnerHTML 或 v-html,就等於主動開了這個口子。

確實要渲染富文本時,必須過一遍白名單淨化(DOMPurify 之類),而不是直接信任後端。

入口三:內嵌進 <script>

<script>
  window.__DATA__ = {"name": "x</script><script>alert(1)</script>"};
</script>

JSON 裡的 </script> 會提前閉合標籤,後面的內容被當成新腳本執行。這是「JSON 內嵌」最容易忽略的一處。

修法:序列化後把危險字元轉成 Unicode 轉義:

JSON.stringify(data)
  .replace(/</g, '\\u003c')
  .replace(/>/g, '\\u003e')
  .replace(/&/g, '\\u0026');

仍然是合法 JSON,解析出來的內容一字不差,但不會再閉合標籤。Next.js、Nuxt 這類框架的 __NEXT_DATA__、__NUXT__ 內嵌就做了類似處理。

順帶兩個相鄰風險

JSON 劫持(老瀏覽器)。 早期瀏覽器允許 <script src="api/user.json"> 載入並執行 JSON,攻擊者重定義 Array 建構子就能讀到資料。現代瀏覽器已修,加上 Content-Type: application/json 與 X-Content-Type-Options: nosniff 就基本免疫。

JSONP 的遺留。 JSONP 本質是「用 script 標籤跨域取資料」,回呼函式名可控就意味著可以注入任意程式碼。新專案不要再用。

一份檢查清單

  • 所有把 JSON 欄位寫進 HTML 的地方,是否按上下文(HTML/屬性/JS/URL)轉義
  • 前端是否還有 innerHTML、v-html、dangerouslySetInnerHTML 直接吃介面資料
  • 內嵌 JSON 是否做了 < > & 轉義
  • 富文本是否有白名單淨化,而不是黑名單過濾 script
  • 回應標頭是否有 Content-Type: application/json 與 nosniff

常見問題

為什麼合法的 JSON 資料也能觸發 XSS?

JSON 本身只是文字,不會執行,合法性只說明它能被解析。危險發生在把它寫進 HTML 或腳本上下文的那一步:標籤被瀏覽器解析、</script> 提前閉合標籤,腳本就會執行。問題不在資料格式,而在輸出時沒有按上下文轉義。

前端渲染介面欄位時該如何選擇 textContent 與 innerHTML?

優先使用 textContent,它把內容當純文字插入,標籤會原樣顯示。innerHTML 會解析標籤,介面欄位裡帶 <img onerror=...> 就會執行。React 的 {user.bio}、Vue 的 {{ user.bio }} 預設走文字節點,dangerouslySetInnerHTML 與 v-html 則是主動開這個口子。

內嵌 JSON 到 <script> 時應該如何處理轉義?

需要。資料裡的 </script> 會提前閉合標籤,後面的內容被當成新腳本執行。序列化後把 <、>、& 分別替換成 \u003c、\u003e、\u0026,結果仍是合法 JSON,解析出來一字不差,但不會再閉合標籤。

延伸閱讀