cover

Web 應用安全實務:常見攻擊與防禦

每一種攻擊的本質都是:系統信任了不該信任的東西。

攻擊的共同模式

所有 Web 攻擊都可以歸結為一個模式:

攻擊者的輸入 → 系統沒有正確處理 → 產生非預期的行為

差別在於「非預期的行為」發生在哪裡:

攻擊攻擊者的輸入被當成什麼在哪裡執行
XSSHTML / JavaScript受害者的瀏覽器
SQL InjectionSQL 語法資料庫
CSRF合法的 HTTP Request你的 Server
SSRFURL你的 Server 去存取內部資源
Command InjectionShell 命令你的 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: nosniff

CSP(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 Cookiecookie 加上 SameSite=StrictLax現代瀏覽器都支援
檢查 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 sniffingnosniff
X-Frame-Options防止被 iframe 嵌入(clickjacking)DENYSAMEORIGIN
Strict-Transport-Security強制 HTTPSmax-age=31536000; includeSubDomains
Referrer-Policy控制 Referer header 洩漏strict-origin-when-cross-origin
Permissions-Policy控制瀏覽器 API 存取(camera, mic 等)依需求設定

反思問題

  1. 你的專案裡有用 innerHTMLdangerouslySetInnerHTML 嗎? 去搜一下,每一個都是潛在的 XSS 點。
  2. 你的資料庫查詢都是 parameterized 的嗎? 搜尋 template literal 拼接 SQL 的地方。
  3. 你的 API 有接受 URL 作為參數的 endpoint 嗎? 那就是潛在的 SSRF。
  4. 你的 production 網站有設 security headers 嗎?securityheaders.com 掃一下就知道。
  5. 你能解釋為什麼 React 「預設安全」但不是「絕對安全」嗎?

延伸閱讀