跨網站指令碼 (XSS):攻擊向量與防禦
為何 XSS 仍困擾網頁應用程式
跨網站指令碼(XSS)仍是最普遍的網頁弱點之一。儘管大家普遍意識到這個問題,它仍持續出現在 OWASP Top 10 中。核心問題在於:應用程式信任使用者輸入,並在未妥善處理的情況下直接渲染。攻擊者注入惡意指令碼,在受害者的瀏覽器中執行,導致工作階段劫持、資料竊取和網頁竄改。
本文將解析 XSS 攻擊向量,並提供您今天就能實作的實用防禦措施。
什麼是 XSS?它如何運作?
當應用程式未經驗證或編碼就將不受信任的資料包含在網頁中時,就會發生 XSS。瀏覽器接著會將注入的指令碼視為合法網站的一部分來執行。由於指令碼在易受攻擊網站的上下文中執行,它可以存取 cookie、本機儲存空間,並代表使用者發出請求。
考慮一個簡單的搜尋頁面,它會反射查詢內容:
<?php echo 'You searched for: ' . $_GET['q']; ?>
如果攻擊者建構一個像 search.php?q=<script>alert('XSS')</script> 的 URL,當受害者造訪該連結時,指令碼就會執行。
三種主要的 XSS 類型
1. 反射型 XSS
惡意指令碼是請求的一部分(例如 URL 參數),並立即反射在回應中。必須誘騙受害者點擊連結或提交表單。這常用於網路釣魚活動中。
2. 儲存型 XSS
指令碼被永久儲存在伺服器上(例如資料庫、留言欄位或使用者個人資料中)。每個造訪受影響頁面的訪客都會執行該指令碼。儲存型 XSS 更危險,因為除了檢視頁面外,不需要任何互動。
3. DOM 型 XSS
弱點存在於用戶端 JavaScript 中,它從不受信任的來源(如 location.hash)讀取資料,並在未安全處理的情況下寫入 DOM。伺服器可能永遠不會看到惡意酬載。
document.getElementById('output').innerHTML = location.hash.substring(1);
如果雜湊包含 <img src=x>,指令碼就會執行。
常見攻擊向量
- HTML 中未轉義的使用者輸入:直接注入 HTML 內容、屬性屬性或 JavaScript 中。
- 不當使用危險的接收點:如
innerHTML、document.write、eval以及帶有字串參數的setTimeout等函式。 - 基於 URL 的注入:使用
javascript:URI 操縱href、src或style屬性。 - 第三方元件:渲染不受信任資料的易受攻擊函式庫或小工具。
XSS 防禦措施
1. 輸出編碼(情境轉義)
在將所有不受信任的資料渲染到 HTML、屬性、JavaScript、CSS 或 URL 之前進行編碼。使用適合情境的編碼:
- HTML 實體編碼:將
&、<、>、"、'轉換為實體。 - JavaScript 編碼:將非英數字元轉義為 Unicode。
- URL 編碼:對查詢參數使用
encodeURIComponent()。
大多數現代框架(React、Angular、Vue)預設會自動轉義,但在使用 dangerouslySetInnerHTML 或類似的逃生出口時要謹慎。
2. 輸入驗證與清理
使用允許清單在伺服器端驗證輸入。對於富文字,使用 DOMPurify 等函式庫來清理 HTML,移除危險的標籤和屬性。
const clean = DOMPurify.sanitize(userInput);
絕不要僅依賴用戶端驗證。
3. 內容安全政策(CSP)
CSP 是強大的縱深防禦機制。它限制可執行指令碼、內聯指令碼和其他資源的來源。嚴格的 CSP 即使發生注入也能阻止 XSS。
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
避免使用 unsafe-inline 和 unsafe-eval。對合法的內聯指令碼使用 nonce 或雜湊。
4. 安全的 Cookie 屬性
設定 Cookie 時使用 HttpOnly(防止 JavaScript 存取)和 Secure(僅限 HTTPS)。這可減輕透過 XSS 進行的工作階段劫持。
5. 使用現代框架並避免危險的 API
像 React、Angular 和 Vue 等框架會自動轉義資料。避免使用 innerHTML 直接操作 DOM。如果必須插入 HTML,請先清理。
6. 定期安全測試
納入 SAST、DAST 和手動滲透測試。像 OWASP ZAP 等工具可以幫助識別 XSS 弱點。
XSS 類型與主要防禦比較
| XSS 類型 | 說明 | 主要防禦 |
|---|---|---|
| 反射型 | 酬載在請求中,反射於回應中 | 輸出編碼、輸入驗證 |
| 儲存型 | 酬載儲存在伺服器上,提供給所有使用者 | 清理、輸出編碼、CSP |
| DOM 型 | 透過不安全的 DOM API 進行用戶端注入 | 安全的 DOM API、CSP、避免 eval |
逐步指南:實作 XSS 防禦
- 識別所有輸入點:表單、URL 參數、標頭、Cookie。
- 套用情境輸出編碼:使用內建函式或 OWASP Java Encoder 等函式庫。
- 清理富文字:使用 DOMPurify 或類似工具。
- 部署 CSP:從僅報告模式開始,然後強制執行。
- 設定 HttpOnly 和 Secure Cookie。
- 教育開發人員:培訓安全編碼實踐。
- 定期測試:將安全測試整合到 CI/CD 中。
常見問題
XSS 和 CSRF 有什麼區別?
XSS 在受害者的瀏覽器中執行惡意指令碼,而 CSRF 誘騙瀏覽器向使用者已驗證的網站發送未經授權的請求。XSS 可用於繞過 CSRF 保護。
內容安全政策能完全防止 XSS 嗎?
不能,CSP 是一種縱深防禦措施。它顯著降低風險,但無法取代適當的輸出編碼和輸入驗證。設定錯誤的 CSP 仍可能允許某些攻擊。
僅靠用戶端驗證足以防止 XSS 嗎?
不夠。用戶端驗證很容易被繞過。始終在伺服器端進行驗證和編碼,並將所有用戶端資料視為不受信任。
結論
XSS 是一個持續存在的威脅,但透過分層防禦策略——輸出編碼、輸入驗證、CSP 和安全 Cookie——您可以有效減輕它。保持警覺,保持框架更新,並持續測試。
如需更多安全工具,請查看我們的 Nginx 日誌分析器,以偵測伺服器日誌中的可疑模式。