[安全程式設計] 002 OWASP Top 10 2025 防禦實戰指南:十大風險逐一擊破,從觀念到程式碼的完整攻防手冊

「知道鎖壞了不夠,你得知道怎麼換一把好鎖。
OWASP Top 10 不只是一張清單,它是你防禦的施工藍圖。」
— SSDLC by 飛飛


一、OWASP Top 10 是什麼?為什麼每個開發者都該懂?

在 SSDLC 蓋房子的旅程中,我們走過了安全需求定義(確認要防什麼)、安全設計(畫好藍圖)、安全編碼基礎(學會用防火建材)。現在,是時候面對真正的敵人了。

OWASP Top 10 就像建築界的「十大常見工安事故報告」。每隔幾年,OWASP(Open Worldwide Application Security Project)會根據全球數十萬個應用程式的真實漏洞數據,整理出最危險的十大安全風險。2025 年版本是第八版,基於超過 175,000 筆 CVE 紀錄和 589 個 CWE 分析而來。

你可能會問:「我已經學了輸入驗證和輸出編碼,為什麼還需要這個?」

輸入驗證和輸出編碼是「基本功」——就像學會了怎麼接電線和鋪防火磚。但真正蓋房子的時候,你會遇到各種不同類型的風險:有人從窗戶爬進來(權限控制失效)、有人複製了你的鑰匙(認證失敗)、有人在你的建材裡摻了劣質品(軟體供應鏈攻擊)。OWASP Top 10 就是告訴你:這十種風險最常發生,你得優先防好。

飛飛觀點:
我常跟團隊說,OWASP Top 10 不是「考試範圍」,而是「體檢報告」。它告訴你全世界的開發者最常在哪裡跌倒。如果你能把這十項都防好,你的系統就已經比八成的網站更安全了。


二、2025 年版有什麼不同?從 2021 到 2025 的演進

在進入逐條解析之前,先快速看看 2025 年版的重大變化:

2021 年版 2025 年版 變化說明
A01: Broken Access Control A01: Broken Access Control 維持第一,SSRF 併入此類
A05: Security Misconfiguration A02: Security Misconfiguration 從第五躍升至第二
A06: Vulnerable and Outdated Components A03: Software Supply Chain Failures 🆕 擴大為供應鏈安全
A02: Cryptographic Failures A04: Cryptographic Failures 下降兩位
A03: Injection A05: Injection 下降兩位
A04: Insecure Design A06: Insecure Design 下降兩位
A07: Identification and Authentication Failures A07: Authentication Failures 微調名稱
A08: Software and Data Integrity Failures A08: Software or Data Integrity Failures 微調名稱
A09: Security Logging and Monitoring Failures A09: Security Logging & Alerting Failures 強調「告警」
A10: Server-Side Request Forgery A10: Mishandling of Exceptional Conditions 🆕 全新類別

兩個重點趨勢:供應鏈安全異常處理成為新焦點——這代表現代攻擊已經從「找你程式碼的洞」演進到「從你的依賴和失敗路徑下手」。


三、十大風險逐一擊破:觀念 + 程式碼

A01:2025 — Broken Access Control(權限控制失效)

用生活比喻理解: 你住的大樓,每戶都有各自的門鎖。但如果管理員不小心把所有房卡都設成通用的,任何住戶都能進別人家——這就是權限控制失效。

為什麼排第一? 根據數據,平均 3.73% 的受測應用程式存在此類漏洞,涵蓋 40 個 CWE。2025 年版還把 SSRF(伺服器端請求偽造)也併入了這個類別。

常見攻擊情境:

  • 修改 URL 中的 ID 就能看到別人的訂單:<code>/api/orders/1234</code> → <code>/api/orders/5678</code>
  • 普通使用者直接存取管理員 API
  • 前端隱藏了按鈕,但後端沒有檢查權限

防禦程式碼範例:

// Node.js + Express — 資源層級的權限檢查
async function getOrder(req, res) {
  const order = await Order.findById(req.params.orderId);

  if (!order) {
    return res.status(404).json({ error: '訂單不存在' });
  }

  // ✅ 關鍵:檢查這筆資料是不是屬於當前使用者
  if (order.userId !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({ error: '無權存取此訂單' });
  }

  res.json(order);
}
# Python + Django — 使用裝飾器做權限檢查
from django.core.exceptions import PermissionDenied

def check_order_owner(view_func):
    def wrapper(request, order_id, *args, **kwargs):
        order = Order.objects.get(id=order_id)
        # ✅ 確認資源擁有者
        if order.user_id != request.user.id and not request.user.is_staff:
            raise PermissionDenied("你沒有權限存取此訂單")
        return view_func(request, order_id, *args, **kwargs)
    return wrapper
// Java + Spring Boot — 方法層級的權限控制
@PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#orderId, authentication)")
@GetMapping("/api/orders/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable Long orderId) {
    return ResponseEntity.ok(orderService.findById(orderId));
}

飛飛觀點:
權限控制的黃金法則——預設拒絕(Deny by Default)。不是「這個 API 誰不能存取」,而是「這個 API 誰可以存取」。先全部鎖上,再一扇一扇開門。


A02:2025 — Security Misconfiguration(安全設定錯誤)

用生活比喻理解: 你買了一扇頂級防盜門,但安裝時忘了把預設密碼改掉,門鎖出廠密碼是 <code>0000</code>——這就是安全設定錯誤。

為什麼從第五升到第二? 現代系統的行為越來越依賴設定檔(configuration),從雲端服務到容器到 API Gateway,每一層都有大量的設定選項。設定錯一個,門就大開。

常見攻擊情境:

  • 開啟了 Debug 模式上線,錯誤訊息洩露完整堆疊追蹤
  • 雲端 S3 Bucket 設定為公開存取
  • 預設帳號密碼沒有修改(admin/admin)
  • 不必要的 HTTP 方法(PUT、DELETE)沒有關閉

防禦程式碼範例:

// Node.js + Express — 安全標頭與生產環境設定
const helmet = require('helmet');
const app = express();

// ✅ 使用 helmet 一次設定多個安全標頭
app.use(helmet());

// ✅ 根據環境切換設定
if (process.env.NODE_ENV === 'production') {
  app.set('trust proxy', 1);
  app.disable('x-powered-by');  // 不洩露技術棧

  // ❌ 永遠不要在生產環境啟用
  // app.use(errorHandler({ dumpExceptions: true, showStack: true }));
}

// ✅ 安全的錯誤處理:只回傳通用訊息
app.use((err, req, res, next) => {
  console.error(err.stack);  // 記錄到日誌
  res.status(500).json({ 
    error: '系統處理時發生錯誤,請稍後再試'  // 不洩露細節
  });
});
# Docker — 安全設定範例
# ✅ 不要用 root 執行應用程式
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# ✅ 只開放必要的 port
EXPOSE 3000

# ✅ 設定唯讀檔案系統
# docker run --read-only --tmpfs /tmp myapp

安全設定 Checklist:

- [ ] 移除所有預設帳號密碼
- [ ] 關閉 Debug 模式和詳細錯誤頁面
- [ ] 移除不必要的功能、port、服務
- [ ] 設定適當的 CORS 白名單(不要用 *)
- [ ] HTTP 安全標頭已正確設定
- [ ] 雲端服務權限已最小化
- [ ] 定期審查設定變更

A03:2025 — Software Supply Chain Failures(軟體供應鏈失敗)🆕

用生活比喻理解: 你蓋房子用的水泥是跟供應商買的。如果供應商偷偷在水泥裡摻了劣質材料,你蓋出來的房子表面看不出問題,但結構已經不安全了——這就是供應鏈攻擊。

為什麼是新類別? 2021 年版只關注「過時和有漏洞的元件」,但現代攻擊者已經進化了。他們會入侵上游套件、在 npm 上發布惡意套件、甚至直接汙染 CI/CD 流程。一個被入侵的套件可以在幾小時內影響數百萬個專案。

防禦程式碼範例:

# ✅ 使用 npm audit 檢查已知漏洞
npm audit

# ✅ 鎖定依賴版本(使用 lock 檔案)
npm ci  # 而非 npm install

# ✅ 使用 OWASP Dependency-Check 掃描
dependency-check --project "MyApp" --scan ./

# ✅ 產生 SBOM(軟體物料清單)
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
// package.json — 鎖定版本範圍,避免自動升級到惡意版本
{
  "dependencies": {
    // ❌ 危險:允許任何 minor/patch 版本自動升級
    "some-package": "^1.0.0",

    // ✅ 安全:鎖定精確版本
    "some-package": "1.2.3"
  }
}
# Python — 使用 pip-audit 檢查依賴漏洞
# pip install pip-audit
# pip-audit

# ✅ 鎖定依賴版本
# pip freeze > requirements.txt
# pip install -r requirements.txt --require-hashes

飛飛觀點:
供應鏈安全的核心問題是「你信任誰」。你用的每一個 npm 套件、每一個 Docker image,背後都是別人寫的程式碼。不是說不能用,而是你要知道自己用了什麼、驗證它是安全的、並且持續監控它


A04:2025 — Cryptographic Failures(加密失敗)

用生活比喻理解: 你在日記上加了一把鎖,但鑰匙就貼在日記封面——這就是加密失敗。不是你沒加密,而是你加密的方式有問題。

常見攻擊情境:

  • 密碼用 MD5 或 SHA1 雜湊(已可被破解)
  • 金鑰硬寫在程式碼裡
  • 傳輸資料沒有用 HTTPS
  • 使用已知不安全的加密演算法(如 DES、RC4)

防禦程式碼範例:

// Node.js — 正確的密碼雜湊
const bcrypt = require('bcrypt');

// ✅ 使用 bcrypt 雜湊密碼(cost factor 至少 12)
async function hashPassword(plainPassword) {
  const saltRounds = 12;
  return await bcrypt.hash(plainPassword, saltRounds);
}

// ✅ 驗證密碼
async function verifyPassword(plainPassword, hashedPassword) {
  return await bcrypt.compare(plainPassword, hashedPassword);
}

// ❌ 永遠不要這樣做
// const hash = crypto.createHash('md5').update(password).digest('hex');
# Python — 使用 Argon2 雜湊密碼
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,       # 迭代次數
    memory_cost=65536,  # 記憶體用量 (KB)
    parallelism=4       # 平行度
)

# ✅ 雜湊
hashed = ph.hash("user_password")

# ✅ 驗證
try:
    ph.verify(hashed, "user_password")
except Exception:
    print("密碼錯誤")
// Node.js — 金鑰管理:從環境變數讀取,不寫在程式碼裡
// ❌ 硬寫金鑰
// const SECRET_KEY = 'my-super-secret-key-12345';

// ✅ 從環境變數讀取
const SECRET_KEY = process.env.JWT_SECRET;
if (!SECRET_KEY || SECRET_KEY.length < 32) {
  throw new Error('JWT_SECRET 未設定或長度不足');
}

A05:2025 — Injection(注入攻擊)

用生活比喻理解: 你開了一家餐廳,客人在點餐單上寫「滷肉飯一碗」,你的服務生照做。但有一天,有人寫了「把金庫打開」——如果你的服務生照做了,那就是注入攻擊。

注入攻擊雖然從第三名降到第五名,但它仍然是CWE 數量最多的類別(38 個),涵蓋 SQL Injection、XSS、Command Injection 等經典攻擊。

防禦程式碼範例:

// Node.js — SQL Injection 防禦:參數化查詢
const { Pool } = require('pg');
const pool = new Pool();

// ❌ 危險:字串拼接
// const result = await pool.query(
//   <code>SELECT * FROM users WHERE email = '${email}'</code>
// );

// ✅ 安全:參數化查詢
const result = await pool.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);
# Python + SQLAlchemy — 使用 ORM 防止 SQL Injection
from sqlalchemy import select

# ✅ 安全:ORM 自動處理參數化
stmt = select(User).where(User.email == user_input_email)
result = session.execute(stmt).scalars().first()

# ❌ 危險:raw SQL 字串拼接
# session.execute(f"SELECT * FROM users WHERE email = '{email}'")
// Java — PreparedStatement 防止 SQL Injection
String sql = "SELECT * FROM users WHERE email = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
    stmt.setString(1, userEmail);  // ✅ 參數化
    ResultSet rs = stmt.executeQuery();
}
// Node.js — Command Injection 防禦
const { execFile } = require('child_process');

// ❌ 危險:使用 exec 拼接指令
// exec(<code class="kb-btn">ping ${userInput}</code>);
// 攻擊者輸入: 127.0.0.1; rm -rf /

// ✅ 安全:使用 execFile,參數陣列分離
execFile('ping', ['-c', '4', validatedHost], (error, stdout) => {
  // 安全處理...
});

A06:2025 — Insecure Design(不安全的設計)

用生活比喻理解: 你蓋了一棟房子,牆壁用防火磚、門裝了三道鎖,但你把保險箱設計在大門旁邊——問題不在施工品質,而在設計本身就有缺陷

不安全的設計跟程式碼漏洞不同。它是架構層級的問題,再好的程式碼也救不了糟糕的設計。

防禦策略:

## 安全設計 Checklist

### 認證設計
- [ ] 登入失敗有速率限制(Rate Limiting)
- [ ] 密碼重設流程使用一次性 Token + 過期時間
- [ ] 敏感操作需要二次驗證(如轉帳、修改密碼)

### 業務邏輯
- [ ] 優惠券不能無限使用(伺服器端驗證使用次數)
- [ ] 金額計算在伺服器端進行(不信任前端傳來的價格)
- [ ] 競態條件已處理(如庫存扣減使用資料庫交易)

### 架構設計
- [ ] 使用威脅建模(STRIDE)識別風險
- [ ] 敏感資料有分級保護策略
- [ ] 有 Abuse Case 分析(攻擊者會怎麼用這個功能?)
// Node.js — 防止業務邏輯漏洞:伺服器端驗證優惠券
app.post('/api/checkout', async (req, res) => {
  const { cartItems, couponCode } = req.body;

  // ✅ 伺服器端重新計算價格(不信任前端傳來的金額)
  const serverPrice = await calculatePrice(cartItems);

  // ✅ 伺服器端驗證優惠券
  if (couponCode) {
    const coupon = await Coupon.findOne({ 
      code: couponCode, 
      usedCount: { $lt: '$maxUses' },  // 檢查使用次數
      expiresAt: { $gt: new Date() }   // 檢查過期時間
    });

    if (!coupon) {
      return res.status(400).json({ error: '優惠券無效或已過期' });
    }

    serverPrice.discount = coupon.discountAmount;
  }

  // 用伺服器端計算的金額結帳
  await processPayment(serverPrice);
});

A07:2025 — Authentication Failures(認證失敗)

用生活比喻理解: 大樓的門禁系統有問題——有人忘了設定密碼長度限制,有人的門卡永遠不會過期,有人被鎖在門外卻發現旁邊有個沒上鎖的側門。

防禦程式碼範例:

// Node.js — 安全的 Session 管理
const session = require('express-session');
const RedisStore = require('connect-redis').default;

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  name: '__Host-sid',           // ✅ 使用安全的 cookie 前綴
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,               // ✅ 僅 HTTPS
    httpOnly: true,             // ✅ 禁止 JavaScript 存取
    sameSite: 'strict',         // ✅ 防止 CSRF
    maxAge: 30 * 60 * 1000,    // ✅ 30 分鐘過期
  }
}));
// Node.js — 登入速率限制,防止暴力破解
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分鐘
  max: 5,                     // 最多 5 次嘗試
  message: { error: '登入嘗試次數過多,請 15 分鐘後再試' },
  standardHeaders: true,
  legacyHeaders: false,
  // ✅ 以帳號為單位限制,而非只看 IP
  keyGenerator: (req) => req.body.email || req.ip,
});

app.post('/api/login', loginLimiter, loginHandler);
# Python + Flask — JWT Token 驗證
import jwt
from functools import wraps

def require_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization', '').replace('Bearer ', '')

        if not token:
            return jsonify({'error': '未提供認證 Token'}), 401

        try:
            # ✅ 驗證 Token 並指定演算法(防止 Algorithm Confusion 攻擊)
            payload = jwt.decode(
                token, 
                current_app.config['JWT_SECRET'],
                algorithms=['HS256']  # 明確指定演算法
            )
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token 已過期'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Token 無效'}), 401

        return f(*args, **kwargs)
    return decorated

A08:2025 — Software or Data Integrity Failures(軟體或資料完整性失敗)

用生活比喻理解: 你在網路上下載了裝修軟體,但沒有驗證這個軟體是不是被人動過手腳。裝完之後,你的電腦就被植入了後門。

跟 A03(供應鏈安全)不同的是,A08 更關注你自己系統內部的完整性驗證——你信任了不該信任的資料或程式碼。

防禦程式碼範例:

// Node.js — 安全的反序列化:不要直接信任外部 JSON
// ❌ 危險:直接反序列化不可信任的資料
// const data = JSON.parse(untrustedInput);
// Object.assign(user, data);  // Prototype Pollution 風險!

// ✅ 安全:只取需要的欄位(白名單)
function safeUpdate(untrustedInput) {
  const parsed = JSON.parse(untrustedInput);
  return {
    name: typeof parsed.name === 'string' ? parsed.name : undefined,
    email: typeof parsed.email === 'string' ? parsed.email : undefined,
    // 只允許特定欄位,忽略其他所有東西
  };
}
// Node.js — 使用 Subresource Integrity (SRI) 驗證 CDN 資源
// 在 HTML 中引入外部腳本時,加上 integrity 屬性
const scriptTag = `
<script 
  src="https://cdn.example.com/library.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
  crossorigin="anonymous">
</script>`;

A09:2025 — Security Logging & Alerting Failures(安全日誌與告警失敗)

用生活比喻理解: 你家裝了監視器,但從來不看畫面、也沒設警報。等小偷來了又走了,你才發現錄影檔三天前就因為硬碟滿了而停止錄影。

2025 年版特別把名稱從「Monitoring」改為「Alerting」,強調光有 Log 不夠,你需要即時告警

防禦程式碼範例:

// Node.js — 安全日誌的正確做法
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'security.log' })
  ],
});

// ✅ 記錄安全相關事件
function logSecurityEvent(event) {
  logger.warn('SECURITY_EVENT', {
    type: event.type,           // 'LOGIN_FAILURE', 'ACCESS_DENIED', etc.
    userId: event.userId,
    ip: event.ip,
    userAgent: event.userAgent,
    timestamp: new Date().toISOString(),
    // ❌ 永遠不記錄這些
    // password: event.password,
    // creditCard: event.creditCard,
    // sessionToken: event.token,
  });
}

// ✅ 應該記錄的事件
// - 登入成功/失敗(特別是連續失敗)
// - 權限被拒絕的存取嘗試
// - 輸入驗證失敗(可能是攻擊探測)
// - 敏感操作(密碼變更、權限修改、資料匯出)
# Python — 異常登入偵測與告警
import redis
from datetime import datetime, timedelta

r = redis.Redis()

def check_login_anomaly(user_id: str, ip: str):
    key = f"login_failures:{user_id}"
    failures = r.incr(key)
    r.expire(key, 900)  # 15 分鐘視窗

    # ✅ 連續失敗超過閾值,觸發告警
    if failures >= 5:
        send_alert(
            level="HIGH",
            message=f"帳號 {user_id} 在 15 分鐘內登入失敗 {failures} 次",
            metadata={"ip": ip, "timestamp": datetime.utcnow().isoformat()}
        )

A10:2025 — Mishandling of Exceptional Conditions(異常狀況處理不當)🆕

用生活比喻理解: 你家的自動門在正常情況下很安全——刷卡才能進。但有一天停電了,自動門的預設行為是「打開」而不是「鎖上」——這就是「失敗時開放(Failing Open)」,也是這個新類別的核心問題。

這是 2025 年全新加入的類別,涵蓋 24 個 CWE,專注於系統在遇到異常時的行為:錯誤處理不當、邏輯錯誤、失敗時開放等。

防禦程式碼範例:

// Node.js — Fail Secure:異常時預設拒絕
async function checkPermission(userId, resource) {
  try {
    const permission = await permissionService.check(userId, resource);
    return permission.allowed;
  } catch (error) {
    // ✅ Fail Secure:出錯時預設拒絕存取
    logger.error('權限檢查失敗', { userId, resource, error: error.message });
    return false;  // 拒絕存取

    // ❌ Fail Open:出錯時預設允許(危險!)
    // return true;
  }
}
// Node.js — 安全的錯誤處理:不洩露內部資訊
app.use((err, req, res, next) => {
  // ✅ 記錄完整的錯誤資訊到日誌
  logger.error('Unhandled error', {
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
  });

  // ✅ 回傳給使用者的只有通用訊息
  const statusCode = err.statusCode || 500;
  res.status(statusCode).json({
    error: statusCode === 500 
      ? '系統處理時發生錯誤,請稍後再試'
      : err.userMessage || '請求處理失敗',
    // ❌ 永遠不要回傳這些
    // stack: err.stack,
    // query: err.sql,
    // config: err.config,
  });
});
# Python — 資源清理:確保異常時也能正確釋放資源
import contextlib

# ✅ 使用 context manager 確保資源被釋放
@contextlib.contextmanager
def secure_db_connection():
    conn = None
    try:
        conn = database.connect()
        yield conn
    except DatabaseError as e:
        logger.error(f"資料庫錯誤: {e}")
        raise  # 重新拋出,讓上層處理
    finally:
        # ✅ 無論成功或失敗,都要關閉連線
        if conn:
            conn.close()

飛飛觀點:
這個新類別讓我特別興奮。很多開發者只測試「快樂路徑(Happy Path)」——功能正常時怎麼跑。但攻擊者專門找的是「不快樂路徑」——系統出錯時會發生什麼。你的 catch 區塊和 else 分支,才是安全的戰場。


四、2021 vs. 2025 對照速查表

排名 2025 類別 核心防禦策略 對應 SSDLC 階段
A01 Broken Access Control 預設拒絕 + 資源層級權限檢查 設計 + 實作
A02 Security Misconfiguration 安全基線 + 自動化設定掃描 部署 + 維運
A03 Software Supply Chain Failures 🆕 SBOM + 依賴掃描 + 簽章驗證 實作 + 驗證
A04 Cryptographic Failures 強雜湊 + 金鑰管理 + TLS 設計 + 實作
A05 Injection 參數化查詢 + 輸入驗證 + 輸出編碼 實作
A06 Insecure Design 威脅建模 + Abuse Case + 安全設計審查 需求 + 設計
A07 Authentication Failures MFA + 速率限制 + 安全 Session 設計 + 實作
A08 Software or Data Integrity SRI + 簽章 + 安全反序列化 實作 + 部署
A09 Logging & Alerting Failures 結構化日誌 + 即時告警 + 不記錄敏感資料 實作 + 維運
A10 Mishandling of Exceptional Conditions 🆕 Fail Secure + 安全錯誤處理 + 資源清理 實作

五、團隊落地建議:如何把 OWASP Top 10 融入日常開發

建議一:把 Top 10 變成 Code Review Checklist

不要把 OWASP Top 10 當成培訓教材看一次就好。把它變成每次 Code Review 時的檢查項目:

## OWASP Top 10 Code Review 快速檢查

### 每次 PR 必查
- [ ] A01: 新的 API 端點有做權限檢查嗎?
- [ ] A05: SQL 查詢有用參數化嗎?使用者輸入有驗證嗎?
- [ ] A10: catch 區塊的處理安全嗎?不會洩露資訊或 Fail Open?

### 設定變更時查
- [ ] A02: 新的設定是否遵循最小權限?有沒有預設不安全的值?

### 加入新依賴時查
- [ ] A03: 這個套件可信嗎?最後更新時間?有已知漏洞嗎?

### 涉及認證/加密時查
- [ ] A04: 使用的加密演算法是否足夠強?金鑰管理方式安全嗎?
- [ ] A07: Session 管理是否正確?有速率限制嗎?

建議二:從風險最高的三項開始

不要試圖一次做完十項。根據數據,先搞定前三名就能大幅降低風險:

  1. A01 Broken Access Control(影響最廣)
  2. A02 Security Misconfiguration(最容易發生)
  3. A05 Injection(後果最嚴重)

建議三:在 CI/CD 中自動化檢測

# GitHub Actions — 自動化安全掃描範例
name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      # A03: 依賴漏洞掃描
      - name: Dependency Check
        run: npm audit --audit-level=high

      # A05: 靜態程式碼分析(找 Injection 漏洞)
      - name: SAST Scan
        uses: returntocorp/semgrep-action@v1
        with:
          config: p/owasp-top-ten

      # A02: 基礎設施設定掃描
      - name: IaC Scan
        run: docker run --rm -v $(pwd):/src checkmarx/kics:latest scan -p /src

六、常見問題 FAQ

Q1:OWASP Top 10 2025 跟 2021 的差別大嗎?需要重新學嗎?

核心概念沒有大變,但有兩個重要新增:A03 軟體供應鏈失敗和 A10 異常狀況處理不當。如果你已經熟悉 2021 版,重點學習這兩個新類別,再注意各項目排名變化背後的趨勢即可。

Q2:我是前端工程師,OWASP Top 10 跟我有關嗎?

當然有。XSS(包含在 A05 Injection 中)跟前端直接相關,A01 的權限控制前端也有責任(不要在前端儲存敏感的權限判斷邏輯),A02 的 CORS 和安全標頭設定也需要前端配合。而且,前端也有自己的依賴供應鏈(npm 套件),A03 也是你的事。

Q3:做了 OWASP Top 10 的防禦就夠了嗎?

不夠,但已經很好了。OWASP Top 10 只涵蓋最常見的十大風險。你的系統可能還有特定於業務邏輯的漏洞、特定技術棧的問題等。但如果你能把這十項都做到位,你已經超越了大部分的應用程式安全水準。想更進一步,可以參考 OWASP ASVS(更詳細的驗證標準)。

Q4:Vibe Coding 時代,AI 生成的程式碼要注意哪些 OWASP 風險?

AI 生成的程式碼最常出現的問題包括:SQL 字串拼接(A05)、缺少權限檢查(A01)、硬編碼金鑰(A04)、以及缺少錯誤處理(A10)。建議把 AI 當成一個「很快但不懂安全的實習生」——它寫完之後,你要用 OWASP Top 10 Checklist 過一遍。


七、結語:OWASP Top 10 不是終點,而是起點

OWASP Top 10 已經發展了超過二十年,從 2003 年的第一版到 2025 年的第八版,它一直在反映一個事實:軟體安全的挑戰不斷演進,但防禦的基本功永遠重要。

2025 年版告訴我們幾件事:

  • 權限控制永遠是最大挑戰(A01 連續兩屆第一名)
  • 你的依賴就是你的攻擊面(A03 供應鏈安全成為新類別)
  • 系統怎麼失敗比怎麼成功更重要(A10 異常處理成為新焦點)
  • 安全不只是寫好程式碼,更是設好設定(A02 躍升至第二名)

回到我們蓋房子的比喻——OWASP Top 10 就是建築界的「十大必檢安全項目」。你不一定要拿到完美的分數,但你至少要知道這些檢查項目是什麼,然後一項一項地做好。

安全不是恐懼,而是創造的基礎。
當你知道怎麼防禦這十大風險,你就能更有信心地打造產品——因為你知道,你的房子不會因為一個小疏忽就倒塌。


延伸閱讀