
Web 應用安全實務:常見攻擊與防禦
每一種攻擊的本質都是:系統信任了不該信任的東西。
攻擊的共同模式
所有 Web 攻擊都可以歸結為一個模式:
攻擊者的輸入 → 系統沒有正確處理 → 產生非預期的行為
差別在於「非預期的行為」發生在哪裡:
| 攻擊 | 攻擊者的輸入被當成什麼 | 在哪裡執行 |
|---|---|---|
| XSS | HTML / JavaScript | 受害者的瀏覽器 |
| SQL Injection | SQL 語法 | 資料庫 |
| CSRF | 合法的 HTTP Request | 你的 Server |
| SSRF | URL | 你的 Server 去存取內部資源 |
| Command Injection | Shell 命令 | 你的 Server OS |
XSS — 你的網站替攻擊者執行了 JavaScript
Cross-Site Scripting 的本質:攻擊者的文字被你的網頁當成 HTML/JS 來解析。
三種類型:
| 類型 | 攻擊向量 | 危險程度 |
|---|---|---|
| Stored XSS | 惡意內容存進資料庫,所有人看到都中招 | 最高 |
| Reflected XSS | 惡意內容在 URL 參數裡,點連結的人中招 | 中 |
| DOM-based XSS | 前端 JS 直接把使用者輸入塞進 DOM | 中 |
攻擊者能做什麼:
- 偷走使用者的 session cookie → 冒充使用者登入
- 修改頁面內容 → 釣魚攻擊
- 記錄使用者的鍵盤輸入 → 偷密碼
防禦的核心原則:永遠不要信任使用者的輸入
✗ element.innerHTML = userInput
✓ element.textContent = userInput
✗ <div dangerouslySetInnerHTML={{__html: userInput}} />
✓ 使用框架的自動 escape(React 預設就會 escape)
✗ 直接把使用者輸入拼進 HTML template
✓ 使用 template engine 的 escape 機制
HTTP Headers 防禦:
Content-Security-Policy: default-src 'self'; script-src 'self'
X-Content-Type-Options: nosniffCSP(Content Security Policy)是最強的 XSS 防禦之一:它告訴瀏覽器「只允許執行來自這些來源的 script」,即使攻擊者成功注入了 script 標籤,瀏覽器也不會執行。
SQL Injection — 你的查詢被攻擊者改寫了
本質: 使用者輸入被當成 SQL 語法的一部分執行。
-- 你寫的
SELECT * FROM users WHERE name = '{userInput}'
-- 使用者輸入: ' OR '1'='1
-- 實際執行的
SELECT * FROM users WHERE name = '' OR '1'='1'
-- 回傳所有使用者!更危險的變體:
-- 使用者輸入: '; DROP TABLE users; --
-- 實際執行的
SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
-- 整張表被刪除防禦:只有一個正確答案 — Parameterized Query
// ✗ 字串拼接 — 永遠不要這樣做
db.query(`SELECT * FROM users WHERE id = ${userId}`)
// ✓ Parameterized Query
db.query('SELECT * FROM users WHERE id = $1', [userId])為什麼 ORM 「通常」安全: 因為 ORM 內部使用 parameterized query。但用 raw query 的時候還是可能出事。
額外防禦層:
- 資料庫帳號使用最小權限(只給 SELECT,不給 DROP)
- 不要在錯誤訊息中透露 SQL 結構
- 使用 WAF 過濾常見的 SQL injection pattern
CSRF — 攻擊者借用了你的登入狀態
Cross-Site Request Forgery 的本質:你登入了 A 網站,然後訪問了惡意的 B 網站,B 網站偷偷用你的身份向 A 發送請求。
你登入了銀行網站(cookie 還在)
│
你打開了一封釣魚信裡的連結
│
惡意頁面包含:
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
│
瀏覽器自動帶上你的 cookie → 銀行以為是你在操作
防禦:
| 方法 | 原理 | 使用場景 |
|---|---|---|
| CSRF Token | 每個表單帶一個隨機 token,server 驗證 | 傳統 server-rendered 頁面 |
| SameSite Cookie | cookie 加上 SameSite=Strict 或 Lax | 現代瀏覽器都支援 |
| 檢查 Origin/Referer header | 確認 request 來自你的網站 | 輔助手段 |
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly現代框架的狀況: 如果你用 SPA(React/Vue)+ JWT,天生就不太受 CSRF 影響,因為 token 不會自動被瀏覽器帶上。但如果你用 cookie-based session,一定要處理 CSRF。
SSRF — 你的 Server 變成攻擊者的跳板
Server-Side Request Forgery 的本質:攻擊者讓你的 server 去請求他指定的 URL,可能是內部服務。
攻擊者: "請幫我抓這個圖片 URL 的內容"
URL: http://169.254.169.254/latest/meta-data/iam/security-credentials/
↑
這是 AWS metadata API!
可以拿到 IAM credentials
常見的受攻擊功能:
- 「預覽 URL」功能
- 圖片 URL 下載
- Webhook 設定
- PDF 生成(如果接受外部 URL)
防禦:
// ✗ 直接拿使用者給的 URL 去 fetch
const response = await fetch(userProvidedUrl)
// ✓ 白名單 + URL 驗證
function isAllowedUrl(url) {
const parsed = new URL(url)
// 禁止內部 IP
if (isInternalIP(parsed.hostname)) return false
// 只允許 http/https
if (!['http:', 'https:'].includes(parsed.protocol)) return false
// 白名單域名
if (!ALLOWED_DOMAINS.includes(parsed.hostname)) return false
return true
}CORS — 不是安全機制,是存取控制
一個常見的誤解:「設了 CORS 就安全了」。
CORS 的本質是: 告訴瀏覽器「哪些外部網站可以讀取我的 API 回應」。
# 最危險的設定 — 等於沒設
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# ↑ 不要同時開這兩個!CORS 不能防禦的:
- Server-to-server 的攻擊(CORS 只在瀏覽器生效)
- CSRF(preflight 只對某些 request 類型有效)
正確的 CORS 設定:
// 只允許你的前端域名
const allowedOrigins = ['https://yourdomain.com']
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true
}))安全 Headers 速查表
這些 HTTP headers 應該在你的所有 production 網站上設定:
| Header | 作用 | 建議值 |
|---|---|---|
Content-Security-Policy | 限制可執行的資源來源 | default-src 'self' |
X-Content-Type-Options | 防止 MIME type sniffing | nosniff |
X-Frame-Options | 防止被 iframe 嵌入(clickjacking) | DENY 或 SAMEORIGIN |
Strict-Transport-Security | 強制 HTTPS | max-age=31536000; includeSubDomains |
Referrer-Policy | 控制 Referer header 洩漏 | strict-origin-when-cross-origin |
Permissions-Policy | 控制瀏覽器 API 存取(camera, mic 等) | 依需求設定 |
反思問題
- 你的專案裡有用
innerHTML或dangerouslySetInnerHTML嗎? 去搜一下,每一個都是潛在的 XSS 點。 - 你的資料庫查詢都是 parameterized 的嗎? 搜尋 template literal 拼接 SQL 的地方。
- 你的 API 有接受 URL 作為參數的 endpoint 嗎? 那就是潛在的 SSRF。
- 你的 production 網站有設 security headers 嗎? 用 securityheaders.com 掃一下就知道。
- 你能解釋為什麼 React 「預設安全」但不是「絕對安全」嗎?
延伸閱讀
- 資安基礎概念 — CIA Triad 和威脅模型
- 紅隊與藍隊 — 用攻防思維看這些漏洞
- 安全開發生命週期 — 在開發流程中防止這些問題
- API 設計與認證機制 — 認證機制的安全設計