[安全程式設計] 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 管理是否正確?有速率限制嗎?
建議二:從風險最高的三項開始
不要試圖一次做完十項。根據數據,先搞定前三名就能大幅降低風險:
- A01 Broken Access Control(影響最廣)
- A02 Security Misconfiguration(最容易發生)
- 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 就是建築界的「十大必檢安全項目」。你不一定要拿到完美的分數,但你至少要知道這些檢查項目是什麼,然後一項一項地做好。
安全不是恐懼,而是創造的基礎。
當你知道怎麼防禦這十大風險,你就能更有信心地打造產品——因為你知道,你的房子不會因為一個小疏忽就倒塌。