[資安新手] 013 給資安新手的網站後端開發完整指南

適用對象:資訊安全初學者、想了解網站後端運作原理的技術人員
閱讀時間:約 25 分鐘


前言:為什麼資安人員需要懂後端開發?

在資訊安全的領域中,「攻擊」與「防禦」是一體兩面。如果你不了解一個網站是怎麼被建造出來的,就很難理解它可能在哪裡被攻破。後端程式碼是網站的心臟——它處理使用者的請求、連接資料庫、管理身份驗證,也是絕大多數漏洞的發生地。

這篇文章將帶你走過一個網站後端從無到有的完整流程,在每一個開發階段嵌入資安思維,讓你不只學會「怎麼蓋」,更學會「怎麼蓋得安全」。


第一章:網站後端的基本架構

1.1 什麼是後端?

當你在瀏覽器輸入網址、按下登入按鈕、或送出一筆訂單,這些操作會發送一個「請求(Request)」到伺服器。後端就是負責接收這些請求、處理商業邏輯、存取資料庫,最後回傳「回應(Response)」的那一整套系統。

一個典型的後端系統包含以下核心元件:

Web 伺服器(Web Server):接收 HTTP/HTTPS 請求的第一道關卡,常見的有 Nginx 和 Apache。它負責處理靜態檔案、SSL 終止(SSL Termination)、反向代理(Reverse Proxy),以及將動態請求轉發到應用伺服器。

應用伺服器(Application Server):執行你撰寫的商業邏輯程式碼。根據你選擇的語言和框架不同,可能是 Node.js 的 Express、Python 的 Django/Flask、Java 的 Spring Boot、Go 的 Gin,或是 PHP 的 Laravel。

資料庫(Database):持久化儲存資料的地方。關聯式資料庫如 PostgreSQL 和 MySQL 適合結構化數據;NoSQL 資料庫如 MongoDB 和 Redis 適合彈性結構或快取場景。

快取層(Cache Layer):Redis 或 Memcached,用來暫存頻繁存取的資料,減少資料庫壓力、加速回應速度。

訊息佇列(Message Queue):RabbitMQ 或 Kafka,用於非同步處理任務,例如寄送 Email、產生報表等不需要即時回應的工作。

1.2 一個請求的完整旅程

理解一個 HTTP 請求從發出到收到回應的完整路徑,是資安分析的基礎。以下是一個使用者登入操作的典型流程:

  1. 使用者在瀏覽器填入帳密,點擊登入,瀏覽器發送 POST /api/login 請求。
  2. 請求經過 DNS 解析後抵達伺服器的 Nginx。
  3. Nginx 透過反向代理將請求轉發至應用伺服器(例如 Express)。
  4. 應用伺服器的路由模組將請求導向 loginController
  5. Controller 呼叫 Service 層,Service 層使用 ORM 向資料庫查詢使用者資料。
  6. 比對密碼雜湊值(Password Hash),驗證成功後產生 JWT Token。
  7. 回應帶著 Token 沿路返回瀏覽器,瀏覽器將 Token 存入記憶體或 Cookie。

在這個流程中,每一個環節都可能存在資安風險——從 DNS 劫持、中間人攻擊、SQL Injection、到 Token 洩漏,理解流程才能準確定位威脅。


第二章:建立網站的完整開發流程

2.1 階段一:需求分析與威脅建模

在寫任何一行程式碼之前,專業的開發流程會從需求分析開始。對資安人員而言,這個階段最重要的工作是「威脅建模(Threat Modeling)」。

STRIDE 模型 是微軟提出的經典威脅分類框架:

  • Spoofing(偽冒):攻擊者能否假冒其他使用者?
  • Tampering(竄改):傳輸中的資料能否被修改?
  • Repudiation(否認):使用者能否否認自己做過的操作?
  • Information Disclosure(資訊洩漏):敏感資料是否可能被未授權存取?
  • Denial of Service(阻斷服務):系統能否被癱瘓?
  • Elevation of Privilege(權限提升):一般使用者能否取得管理員權限?

在需求分析階段就進行威脅建模,能讓安全機制融入設計而非事後修補。

2.2 階段二:技術選型

技術選型直接影響後續的安全特性和維護成本。以下是主流後端框架的安全特性比較:

Node.js + Express:生態豐富、開發速度快,但安全功能需自行整合。需搭配 Helmet(HTTP 安全標頭)、express-rate-limit(速率限制)、csurf(CSRF 防護)等中介軟體。適合快速原型和 API 開發。

Python + Django:內建安全功能最完善的框架之一。CSRF 保護、SQL Injection 防禦、XSS 過濾、Clickjacking 防護、密碼雜湊等全部開箱即用。ORM 預設使用參數化查詢。適合需要強安全性的企業應用。

Java + Spring Boot:搭配 Spring Security 能實現企業級身份驗證與授權。支援 OAuth 2.0、LDAP、SAML 等多種認證協定。適合大型企業系統。

Go + Gin:編譯式語言,記憶體安全性較高,效能優秀。但安全功能需自行建構,適合微服務架構。

PHP + Laravel:內建 CSRF Token、Eloquent ORM 參數化查詢、Bcrypt 密碼雜湊。中小型專案的常見選擇。

選型時的資安考量重點:框架是否積極維護、安全漏洞的修補速度、社群生態的安全套件是否成熟、預設設定是否「安全優先(Secure by Default)」。

2.3 階段三:環境建置

現代開發環境建置通常包含以下工具鏈:

版本控制:Git + GitHub/GitLab。資安重點是確保 .gitignore 排除所有機密檔案,永遠不要把 API 金鑰、資料庫密碼、私鑰推送到版本庫。使用工具如 git-secretstruffleHog 掃描意外提交的敏感資訊。

容器化:Docker + Docker Compose。將應用及其依賴打包成獨立容器,確保開發、測試、生產環境一致。資安重點是使用最小化基礎映像(如 Alpine Linux)、不以 root 身份執行容器、定期掃描映像漏洞。

環境變數管理:使用 .env 檔案管理機密資訊,搭配工具如 dotenv 載入。進階方案使用 HashiCorp Vault 或 AWS Secrets Manager 集中管理密鑰。

以下是一個安全的 Docker Compose 開發環境範例:

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DB_HOST=db
      - DB_PORT=5432
    env_file:
      - .env  # 機密資訊放這裡,不進版本控制
    depends_on:
      - db
      - redis
    networks:
      - backend

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    networks:
      - backend

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  pgdata:

secrets:
  db_password:
    file: ./secrets/db_password.txt

2.4 階段四:程式碼開發

這是最核心的階段,也是漏洞最容易產生的地方。以下用 Node.js + Express 為例,展示一個帶有安全考量的基礎架構:

// app.js - 應用程式進入點
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const hpp = require('hpp');
const mongoSanitize = require('express-mongo-sanitize');

const app = express();

// === 安全性中介軟體 ===

// Helmet:自動設定多個安全相關的 HTTP 標頭
// 包含 X-Content-Type-Options, X-Frame-Options, 
// Content-Security-Policy 等
app.use(helmet());

// CORS:限制哪些來源可以存取你的 API
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || 'https://yourdomain.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true,
  maxAge: 86400  // 預檢請求快取 24 小時
}));

// 速率限制:防止暴力破解和 DDoS
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分鐘
  max: 100,                   // 每個 IP 限制 100 次請求
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later.' }
});
app.use('/api/', limiter);

// 針對登入端點設定更嚴格的速率限制
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // 15 分鐘內只能嘗試 5 次登入
  message: { error: 'Too many login attempts.' }
});
app.use('/api/auth/login', loginLimiter);

// 解析 JSON 請求主體,限制大小防止 payload 攻擊
app.use(express.json({ limit: '10kb' }));

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

// 防止 NoSQL 注入(過濾 gt,ne 等運算子)
app.use(mongoSanitize());

// === 路由 ===
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));

// === 全域錯誤處理 ===
// 重點:永遠不要在錯誤回應中洩漏系統內部資訊
app.use((err, req, res, next) => {
  console.error(err.stack);  // 內部記錄完整錯誤

  // 對外只回傳安全的錯誤訊息
  res.status(err.statusCode || 500).json({
    status: 'error',
    message: process.env.NODE_ENV === 'production' 
      ? 'Something went wrong' 
      : err.message  // 開發環境才顯示詳細錯誤
  });
});

module.exports = app;

2.5 階段五:測試

安全測試應該是開發流程的固定環節,而非部署前的臨時檢查。

單元測試(Unit Test):測試個別函式的邏輯正確性。使用 Jest、Mocha、pytest 等框架。

整合測試(Integration Test):測試多個模組協同運作,例如 API 端點是否正確存取資料庫。

安全測試(Security Test):使用自動化工具掃描已知漏洞,包含靜態應用安全測試(SAST,如 SonarQube、Semgrep)和動態應用安全測試(DAST,如 OWASP ZAP)。

依賴漏洞掃描:使用 npm auditpip audit、Snyk 或 Dependabot 定期檢查第三方套件的已知漏洞。

2.6 階段六:CI/CD 與部署

持續整合/持續部署(CI/CD)流程中應嵌入安全檢查:

# .github/workflows/security.yml
name: Security Pipeline

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 檢查是否有意外提交的密鑰
      - name: Secret Scanning
        uses: trufflesecurity/trufflehog@main

      # 依賴漏洞檢查
      - name: Dependency Audit
        run: npm audit --audit-level=high

      # 靜態程式碼分析
      - name: SAST Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      # Docker 映像漏洞掃描
      - name: Container Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:latest'
          severity: 'CRITICAL,HIGH'

第三章:框架的使用與 MVC 架構模式

3.1 MVC 架構

絕大多數後端框架採用 MVC(Model-View-Controller)或其變體作為基本架構模式:

Model(模型):定義資料結構和資料庫互動邏輯。使用 ORM(Object-Relational Mapping)工具如 Sequelize、TypeORM、Django ORM、SQLAlchemy 等,避免直接撰寫 SQL 語句,從根本上防止 SQL Injection。

View(視圖):在純 API 後端中,View 通常就是 JSON 回應格式的定義。在全端框架中則是 HTML 模板引擎(如 EJS、Pug、Jinja2)。

Controller(控制器):接收請求、呼叫 Service/Model 處理邏輯、回傳回應。Controller 應盡量保持「瘦」,把商業邏輯委派給 Service 層。

3.2 安全的專案結構範例

project/
├── src/
│   ├── config/          # 環境設定(資料庫連線、JWT 密鑰等)
│   ├── middleware/       # 自定義中介軟體
│   │   ├── auth.js          # JWT 驗證
│   │   ├── validate.js      # 輸入驗證
│   │   ├── authorize.js     # 角色授權(RBAC)
│   │   └── sanitize.js      # 輸入消毒
│   ├── models/          # 資料模型定義
│   ├── controllers/     # 請求處理邏輯
│   ├── services/        # 商業邏輯層
│   ├── routes/          # 路由定義
│   ├── utils/           # 工具函式
│   │   ├── logger.js        # 安全日誌
│   │   ├── crypto.js        # 加密工具
│   │   └── validator.js     # 驗證規則
│   └── app.js           # 應用程式進入點
├── tests/
│   ├── unit/
│   ├── integration/
│   └── security/
├── .env.example         # 環境變數範本(不含實際值)
├── .gitignore
├── docker-compose.yml
└── package.json

3.3 中介軟體鏈(Middleware Chain)

中介軟體是後端框架的核心概念。每個請求進入應用後,會依序經過一連串的中介軟體處理。理解這條鏈的順序對資安至關重要:

請求進入
  ↓
[1] Helmet(安全標頭)
  ↓
[2] CORS(跨來源控制)
  ↓
[3] Rate Limiter(速率限制)
  ↓
[4] Body Parser(解析請求主體)
  ↓
[5] Input Sanitization(輸入消毒)
  ↓
[6] Authentication(身份驗證)
  ↓
[7] Authorization(權限檢查)
  ↓
[8] Input Validation(輸入驗證)
  ↓
[9] Controller / Business Logic
  ↓
[10] Error Handler(錯誤處理)
  ↓
回應送出

順序很重要——你應該在驗證身份之前就擋掉超過速率限制的請求,在進入商業邏輯之前就完成輸入消毒和驗證。


第四章:資安必備的網站架構元件

4.1 身份驗證(Authentication)

身份驗證回答的問題是「你是誰?」。以下是安全的密碼處理和 JWT 實作:

// services/auth.service.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');

class AuthService {
  // 安全的密碼雜湊
  async hashPassword(password) {
    // bcrypt 的 salt rounds 建議至少 12
    // 每增加 1,計算時間約翻倍
    const saltRounds = 12;
    return await bcrypt.hash(password, saltRounds);
  }

  // 密碼驗證(使用 timing-safe comparison)
  async verifyPassword(inputPassword, storedHash) {
    return await bcrypt.compare(inputPassword, storedHash);
  }

  // 產生 JWT Token(Access Token + Refresh Token)
  generateTokens(userId) {
    const accessToken = jwt.sign(
      { sub: userId, type: 'access' },
      process.env.JWT_ACCESS_SECRET,
      { expiresIn: '15m' }  // Access Token 壽命短
    );

    const refreshToken = jwt.sign(
      { sub: userId, type: 'refresh' },
      process.env.JWT_REFRESH_SECRET,
      { expiresIn: '7d' }   // Refresh Token 壽命長
    );

    return { accessToken, refreshToken };
  }

  // 驗證 JWT Token
  verifyAccessToken(token) {
    try {
      const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET);
      if (decoded.type !== 'access') {
        throw new Error('Invalid token type');
      }
      return decoded;
    } catch (error) {
      throw new Error('Invalid or expired token');
    }
  }
}

module.exports = new AuthService();

密碼政策建議:最小長度 8 至 12 字元、支援全 Unicode 字元集、檢查是否為已洩漏的密碼(使用 Have I Been Pwned API)、不要強制定期更換密碼(NIST SP 800-63B 建議)。

4.2 授權(Authorization)與 RBAC

授權回答的問題是「你能做什麼?」。角色存取控制(Role-Based Access Control)是最常見的授權模型:

// middleware/authorize.js
const authorize = (...allowedRoles) => {
  return (req, res, next) => {
    // 前提:auth middleware 已經把使用者資訊放到 req.user
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    if (!allowedRoles.includes(req.user.role)) {
      // 重要:403 vs 401 的差異
      // 401 = 未驗證身份
      // 403 = 已驗證但無權限
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
};

// 使用範例
router.get('/admin/users', 
  authenticate,                    // 先驗證身份
  authorize('admin', 'superadmin'),// 再檢查權限
  userController.getAllUsers       // 最後執行邏輯
);

router.delete('/admin/users/:id',
  authenticate,
  authorize('superadmin'),         // 只有超級管理員能刪除使用者
  userController.deleteUser
);

4.3 輸入驗證與消毒(Input Validation & Sanitization)

永遠不要信任使用者輸入——這是後端開發最重要的安全原則。

// middleware/validate.js
const { body, param, query, validationResult } = require('express-validator');
const xss = require('xss');

// 自定義消毒函式
const sanitizeInput = (value) => {
  if (typeof value !== 'string') return value;
  return xss(value.trim());
};

// 註冊表單的驗證規則
const registerValidation = [
  body('email')
    .isEmail().withMessage('Please provide a valid email')
    .normalizeEmail()               // 標準化 email 格式
    .customSanitizer(sanitizeInput), // XSS 消毒

  body('password')
    .isLength({ min: 8, max: 128 })
    .withMessage('Password must be 8-128 characters')
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
    .withMessage('Must contain uppercase, lowercase, and number'),

  body('username')
    .isLength({ min: 3, max: 30 })
    .matches(/^[a-zA-Z0-9_]+$/)     // 只允許字母、數字、底線
    .withMessage('Username can only contain letters, numbers, and underscores')
    .customSanitizer(sanitizeInput),

  // 統一的驗證結果處理
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        status: 'fail',
        errors: errors.array().map(e => ({
          field: e.path,
          message: e.msg
          // 注意:不要回傳使用者的輸入值,防止反射型 XSS
        }))
      });
    }
    next();
  }
];

module.exports = { registerValidation };

4.4 SQL Injection 防禦

SQL Injection 長年高居 OWASP Top 10,防禦的核心是「參數化查詢」:

// ❌ 危險:字串拼接 SQL(永遠不要這樣做)
const query = `SELECT * FROM users WHERE email = '{userInput}'`;
// 攻擊者輸入: ' OR '1'='1' --
// 結果: SELECT * FROM users WHERE email = '' OR '1'='1' --'
// 整個使用者表被洩漏

// ✅ 安全:參數化查詢
const query = 'SELECT * FROM users WHERE email =1';
const result = await pool.query(query, [userInput]);

// ✅ 更安全:使用 ORM
// Sequelize 範例
const user = await User.findOne({ 
  where: { email: userInput }  // ORM 自動處理參數化
});

// Prisma 範例
const user = await prisma.user.findUnique({
  where: { email: userInput }
});

4.5 跨站腳本攻擊(XSS)防禦

// Content Security Policy(CSP)是防禦 XSS 的強力工具
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],                // 預設只允許同源資源
    scriptSrc: ["'self'"],                 // JavaScript 只能從同源載入
    styleSrc: ["'self'", "'unsafe-inline'"], // 樣式允許行內(視需求調整)
    imgSrc: ["'self'", "data:", "https:"], // 圖片來源
    connectSrc: ["'self'"],                // API 請求目標
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],                 // 禁止 Flash 等外掛
    frameSrc: ["'none'"],                  // 禁止 iframe 嵌入
    baseUri: ["'self'"],                   // 限制 <base> 標籤
    formAction: ["'self'"],                // 表單只能提交到同源
  }
}));

// 設定 Cookie 安全屬性
const sessionConfig = {
  httpOnly: true,   // JavaScript 無法存取此 Cookie
  secure: true,     // 只透過 HTTPS 傳送
  sameSite: 'Strict', // 防止 CSRF
  maxAge: 3600000,  // 1 小時過期
  path: '/',
  domain: '.yourdomain.com'
};

4.6 安全的 HTTP 標頭

// 除了 Helmet 自動設定的標頭,還應手動加入:

// Strict-Transport-Security:強制使用 HTTPS
app.use(helmet.hsts({
  maxAge: 31536000,         // 1 年
  includeSubDomains: true,  // 包含所有子網域
  preload: true             // 加入瀏覽器預載清單
}));

// X-Content-Type-Options:防止 MIME 嗅探
// X-Frame-Options:防止 Clickjacking
// X-XSS-Protection:啟用瀏覽器 XSS 過濾器
// 以上三者 Helmet 預設已設定

// Referrer-Policy:控制 Referer 標頭資訊洩漏
app.use(helmet.referrerPolicy({ 
  policy: 'strict-origin-when-cross-origin' 
}));

// Permissions-Policy:限制瀏覽器功能存取
app.use((req, res, next) => {
  res.setHeader('Permissions-Policy', 
    'camera=(), microphone=(), geolocation=(), payment=(self)');
  next();
});

4.7 安全的日誌系統

日誌是資安事件調查的關鍵,但不當的日誌記錄本身也可能造成資訊洩漏:

// utils/logger.js
const winston = require('winston');

// 敏感資訊過濾器
const sensitiveFields = ['password', 'token', 'authorization',
  'cookie', 'credit_card', 'ssn', 'secret'];

const maskSensitiveData = winston.format((info) => {
  if (typeof info.message === 'string') {
    sensitiveFields.forEach(field => {
      const regex = new RegExp(`"{field}"\\s*:\\s*"[^"]*"`, 'gi');
      info.message = info.message.replace(regex, `"{field}":"[REDACTED]"`);
    });
  }
  return info;
});

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    maskSensitiveData(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ 
      filename: 'logs/error.log', 
      level: 'error',
      maxsize: 5242880,  // 5MB
      maxFiles: 10
    }),
    new winston.transports.File({ 
      filename: 'logs/combined.log',
      maxsize: 5242880,
      maxFiles: 30
    })
  ]
});

// 安全事件專用日誌
logger.security = (event, details) => {
  logger.warn({
    type: 'SECURITY_EVENT',
    event,
    ...details,
    timestamp: new Date().toISOString()
  });
};

// 使用範例
logger.security('LOGIN_FAILED', {
  ip: req.ip,
  email: req.body.email,  // 記錄嘗試的帳號(不記錄密碼)
  userAgent: req.headers['user-agent'],
  reason: 'Invalid credentials'
});

module.exports = logger;

4.8 HTTPS 與 TLS 設定

所有正式環境的網站都必須使用 HTTPS。以下是 Nginx 的安全 TLS 設定:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    # TLS 憑證(建議使用 Let's Encrypt 免費憑證)
    ssl_certificate     /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # 只允許安全的 TLS 版本
    ssl_protocols TLSv1.2 TLSv1.3;

    # 優先使用伺服器端的加密套件
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

    # OCSP Stapling(加速 TLS 握手)
    ssl_stapling on;
    ssl_stapling_verify on;

    # 反向代理設定
    location /api/ {
        proxy_pass http://app:3000;
        proxy_set_header Host host;
        proxy_set_header X-Real-IPremote_addr;
        proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Protoscheme;

        # 隱藏後端伺服器資訊
        proxy_hide_header X-Powered-By;
        proxy_hide_header Server;
    }
}

# HTTP 自動重導向到 HTTPS
server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://server_namerequest_uri;
}

4.9 資料庫安全

// config/database.js
const { Pool } = require('pg');

const pool = new Pool({
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,

  // 連線安全設定
  ssl: {
    rejectUnauthorized: true,          // 驗證伺服器憑證
    ca: fs.readFileSync('/path/to/ca-cert.pem')
  },

  // 連線池設定(防止連線耗盡攻擊)
  max: 20,                  // 最大連線數
  idleTimeoutMillis: 30000, // 閒置連線超時
  connectionTimeoutMillis: 5000, // 連線建立超時
});

module.exports = pool;

資料庫安全清單:使用最小權限原則(應用程式帳號不應有 DROP、ALTER 權限)、啟用連線加密(SSL/TLS)、定期備份並測試還原流程、啟用查詢日誌和慢查詢監控、敏感欄位加密儲存(如信用卡號)。

4.10 API 安全設計

// 安全的 API 版本管理
app.use('/api/v1', v1Router);  // 版本化路由

// API Key 驗證(用於服務間通訊)
const validateApiKey = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];

  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  // 使用 timing-safe comparison 防止計時攻擊
  const validKey = Buffer.from(process.env.API_KEY);
  const providedKey = Buffer.from(apiKey);

  if (validKey.length !== providedKey.length || 
      !crypto.timingSafeEqual(validKey, providedKey)) {
    return res.status(401).json({ error: 'Invalid API key' });
  }

  next();
};

// 請求大小限制
app.use(express.json({ limit: '10kb' }));     // JSON 主體
app.use(express.urlencoded({ limit: '10kb', extended: true }));

// 檔案上傳安全
const multer = require('multer');
const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024,  // 5MB 上限
    files: 1                     // 單次只允許 1 個檔案
  },
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
    if (!allowedTypes.includes(file.mimetype)) {
      return cb(new Error('Invalid file type'), false);
    }
    cb(null, true);
  }
});

第五章:完整安全架構總覽

5.1 縱深防禦(Defense in Depth)

安全的網站架構不依賴單一防線,而是像洋蔥一樣層層包裹。從外到內的完整防禦層次如下:

                    ┌─────────────────────────────┐
                    │    CDN / DDoS Protection     │ ← Cloudflare / AWS Shield
                    │    (第一層:網路邊緣防護)      │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │    WAF (Web Application      │ ← ModSecurity / AWS WAF
                    │    Firewall)                  │
                    │    (第二層:應用層防火牆)       │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │    Load Balancer + SSL        │ ← Nginx / HAProxy
                    │    Termination                │
                    │    (第三層:負載均衡與加密終止)  │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │    Application Server         │ ← Express / Django / Spring
                    │    + Security Middleware       │
                    │    (第四層:應用邏輯與中介軟體)  │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │    Database + Encryption      │ ← PostgreSQL + AES-256
                    │    (第五層:資料儲存與加密)     │
                    └─────────────────────────────┘

5.2 OWASP Top 10 對照檢查表

以下對照 OWASP 2021 Top 10 風險,列出本文涵蓋的防禦措施:

排名 風險類別 本文對應防禦
A01 Broken Access Control RBAC 授權中介軟體(4.2 節)
A02 Cryptographic Failures bcrypt 密碼雜湊、TLS 設定(4.1、4.8 節)
A03 Injection 參數化查詢、ORM、輸入消毒(4.3、4.4 節)
A04 Insecure Design 威脅建模、STRIDE(2.1 節)
A05 Security Misconfiguration Helmet 安全標頭、最小權限原則(4.5、4.6 節)
A06 Vulnerable Components npm audit、Dependabot(2.5 節)
A07 Auth Failures JWT 雙 Token 機制、速率限制(4.1 節)
A08 Data Integrity Failures CI/CD 安全管線(2.6 節)
A09 Logging Failures Winston 安全日誌、敏感資料遮蔽(4.7 節)
A10 SSRF 輸入驗證、白名單控制(4.3、4.10 節)

第六章:部署與維運安全

6.1 生產環境安全清單

在按下部署按鈕之前,請逐項確認:

伺服器層面:作業系統已更新至最新安全補丁、關閉所有不必要的端口(只開放 80、443)、SSH 禁用密碼登入且僅允許金鑰認證、設定防火牆規則(iptables / ufw)、啟用自動安全更新。

應用層面NODE_ENV=production(或對應的生產模式設定)、移除所有偵錯端點和測試帳號、錯誤訊息不洩漏堆疊追蹤、所有密鑰使用環境變數或密鑰管理服務、Content Security Policy 已正確設定。

資料庫層面:預設管理員密碼已更改、遠端存取已限制為特定 IP、備份已設定且定期測試還原、啟用慢查詢日誌。

監控層面:設定錯誤率告警(Error Rate Alert)、設定異常流量告警、設定伺服器資源使用率告警、日誌集中收集(ELK Stack / Grafana Loki)。

6.2 事件回應計畫

即使做了所有防護,安全事件仍然可能發生。預先準備事件回應計畫(Incident Response Plan)至關重要:定義事件嚴重等級分類標準、建立通報流程與負責人清單、準備好隔離受影響系統的操作手冊、定期演練(Tabletop Exercise)以驗證流程可行性。


結語

後端安全不是一個功能(Feature),而是一種思維方式(Mindset)。它應該滲透在開發流程的每一個環節——從需求分析時的威脅建模,到撰寫程式碼時的輸入驗證,再到部署後的持續監控。

給資安新手的建議是:不要試圖一次學會所有東西。先從理解 OWASP Top 10 開始,然後挑選一個框架動手建一個小專案,在實作中逐步加入安全機制。每修復一個漏洞、每攔截一次攻擊,你對後端安全的理解都會更深一層。

安全是一場持續的旅程,而非終點。

飛飛
飛飛

講師學歷:臺科資工所、逢甲資工系畢業。
技術專長:OSINT、滲透測試、網站開發、專業易懂教育訓練。
證照書籍:OSCP、OSCE³、著《資安這條路:領航新手的 Web Security 指南》。
教學經驗:60+ 企業教學經驗、指導過上百位學員。
教學特色:新手友善、耐心指導、擅長圖解(流程圖、心智圖)引導學習。
社群經驗:目前經營全臺資安社群 CURA,曾任臺科資安社社長、逢甲黑客社社長。
社群交流:LINE 社群《飛飛的資安大圈圈》,即時分享經驗、鼓勵交流。
社群分享:FB 粉專《資安這條路,飛飛來領路》,分享文章與圖卡整理。
個人網站:feifei.tw 分享資安技術文章;pbtw.tw 分享 AI 相關應用;ssdlc.feifei.tw 分享軟體安全開發流程文章。