第三卷:六大 Critical / High 核心攻擊鏈攻防實錄與工業級代碼重構 (The Purple Teaming Chronicles)

全書總標題:從 0 到 99/100:現代全端與雲原生架構的「近零可利用性(Near-Zero Exploitability)」實戰演進與 AI 紫隊工程指南
本卷定位:核心攻防實戰與工業級代碼重構手冊(Attack Chains & Code Refactoring)
標準對標:OWASP Top 10 (2025/2026) · CWE/SANS Top 25 Most Dangerous Software Errors · MITRE ATT&CK Enterprise Matrix
目標讀者:全端資深工程師、紅隊滲透專家(Red Teamers)、藍隊安全架構師(Blue Teamers)、DevSecOps 工程師


1. 攻擊鏈一(Critical):無鑑權 2FA 密鑰查詢與密碼直通繞過後門

sequenceDiagram
    autonumber
    actor Attacker as 🔴 紅隊攻擊者
    participant Gateway as 🛡️ API Gateway (/api/auth/*)
    participant Core as 🔐 認證核心 (auth.js)
    participant App as 💻 前端介面

    Note over Attacker, Gateway: 【第一階段:未授權密鑰探測】
    Attacker->>Gateway: POST /api/auth/setup-2fa (無 Token)
    Gateway-->>Attacker: 200 OK { secret: "JBSWY3DPEHPK3PXP", qrCode: "data:image/png..." } ❌ Critical 漏洞
    Note over Attacker: 攻擊者直接將密鑰綁定至自身 Authenticator

    Note over Attacker, Core: 【第二階段:密碼繞過後門】
    Attacker->>Gateway: 發起管理員請求 (帶 Header: x-auth-token = "ADMIN_PASSWORD")
    Gateway->>Core: verifyAuth("ADMIN_PASSWORD")
    Core-->>Gateway: true (命中密碼直通邏輯) ❌ 2FA 防禦全面被繞過

    Note over Gateway, Core: 【藍隊架構級根治】
    Note over Gateway: 1. setup-2fa 強制驗證合法 Session Cookie<br/>2. 拔除密碼直通邏輯,嚴格要求 HMAC 簽名<br/>3. 單元測試斷言「密碼不可作為 Token」

1.1 威脅建模與深度攻防技術剖析

雙因子認證(2FA)的核心設計原則,是在傳統的「你知道什麼(Something you know,即密碼)」之上,強制疊加「你擁有什麼(Something you have,即 Authenticator 裝置)」。 在實作過程中,AI Agent 為了讓使用者「初次設置時更方便」,生成了一個 /api/auth/setup-2fa 路由,卻忘記在該路由入口處檢查呼叫者是否持有有效的登入 Session。 攻擊者利用未鑑權的 POST 請求直接呼叫該端點,伺服器直接回傳 Base32 格式的 TOTP Secret 與 QR Code。攻擊者取得密鑰後將其匯入攻擊者自己的手機 Authenticator,隨後只要拿到洩漏的主密碼,即可順利生成動態碼完成登入。更嚴重的是,AI 在鑑權中介軟體中寫入了「若傳入字串等於主密碼則判定有效」的便利邏輯,直接開闢了一條繞過 2FA 的後門通道。

1.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼(通用 Node.js / Express 範例):

// ❌ 不安全實作:無鑑權即洩漏 2FA 密鑰,且允許直接拿密碼當 Token 繞過 2FA
const express = require('express');
const app = express();

app.post('/api/auth/setup-2fa', (req, res) => {
  // ❌ 致命缺陷 1: 缺少 Session 鑑權中介軟體,任何人皆可讀取 TOTP 密鑰
  const secret = process.env.TOTP_SECRET;
  const qrCodeUrl = generateQrCode(secret);
  return res.json({ secret, qrCodeUrl });
});

function verifyAuth(token) {
  // ❌ 致命缺陷 2: 開發者留下的便利後門,允許直接傳入主密碼作為 Token
  if (token === process.env.ADMIN_PASSWORD) {
    return true; // 2FA 被徹底架空
  }
  return verifySessionToken(token);
}

✅ 安全重構代碼(業界模範實作):

// ✅ 安全實作:強制 Session 鑑權,並嚴格斷言「密碼不可作為 Token」
const express = require('express');
const app = express();

app.post('/api/auth/setup-2fa', (req, res) => {
  // 1. 強制提取並驗證已登入的 Session Token (HttpOnly Cookie)
  const token = req.cookies.admin_token;
  if (!verifySessionToken(token)) {
    return res.status(401).json({ error: 'Unauthorized: 請先完成初次身分認證' });
  }

  // 2. 僅允許通過鑑權的管理員讀取 2FA 綁定資料
  const secret = process.env.TOTP_SECRET;
  const qrCodeUrl = generateQrCode(secret);
  return res.json({ secret, qrCodeUrl });
});

function verifySessionToken(token) {
  // ✅ 核心防禦:傳入主密碼時強制判定為無效,嚴格杜絕 2FA 繞過後門
  if (!token || token === process.env.ADMIN_PASSWORD) {
    return false;
  }
  return validateHmacSignature(token);
}

2. 攻擊鏈二(High):HMAC Fallback 金鑰硬編碼引發的離線 Token 鍛造

sequenceDiagram
    autonumber
    actor Attacker as 🔴 紅隊攻擊者
    participant Local as 💻 本地攻擊環境
    participant Server as 🌐 雲端伺服器 (/api/admin/*)

    Note over Attacker, Local: 【第一階段:逆向代碼庫發現 Fallback Key】
    Attacker->>Local: 檢視開源/反編譯代碼發現:<br/>SECRET_KEY = process.env.KEY || 'IDRI_SECURE_AUTH_FALLBACK_KEY_2026'

    Note over Attacker, Local: 【第二階段:本地離線鍛造 Token】
    Attacker->>Local: 計算 HMAC-SHA256("admin-authenticated:Now", "IDRI_SECURE_AUTH_FALLBACK_KEY_2026")
    Local-->>Attacker: 生成合法 Token: admin-authenticated:1787558378714:a3f9e8...

    Note over Attacker, Server: 【第三階段:零帳密接管後台】
    Attacker->>Server: GET /api/admin/posts (Cookie: admin_token=Forged_Token)
    Note over Server: 伺服器因環境變數遺失,使用相同 Fallback Key 驗證通過!
    Server-->>Attacker: 200 OK (全站資料庫控制權被奪取) ❌ High 漏洞

2.1 深度攻防背景與威脅原理

在現代雲端部署(如 Docker、Kubernetes 或 Vercel Serverless)中,環境變數是由宿主環境動態注入的。若系統因 CI/CD 配置錯誤、變數名稱打錯或 Preview 環境未綁定 Secret,變數值將為 undefined。 AI 在生成密碼學模組時,寫入了預設的明文字串 Fallback。這導致系統在關鍵金鑰丟失時,不會崩潰報錯,而是靜默降級為全球已知的弱密鑰狀態。攻擊者只要在本地利用該字串計算 HMAC-SHA256 簽名,就能鍛造出任意合法 Token。

2.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼(通用 Python / FastAPI 範例):

# ❌ 不安全實作:寛容的 Fallback 導致環境變數遺失時產生已知密鑰漏洞
import os, hmac, hashlib, time

# ❌ 致命缺陷: 明文 Fallback Key 暴露在代碼中
SECRET_KEY = os.getenv("SESSION_SECRET", "IDRI_SECURE_AUTH_FALLBACK_KEY_2026")

def create_session(user_id: str) -> str:
    timestamp = str(int(time.time() * 1000))
    payload = f"{user_id}:{timestamp}"
    sig = hmac.new(SECRET_KEY.encode(), payload.encode(), hashlib.sha256).hexdigest()
    return f"{payload}:{sig}"

def verify_session(token: str) -> bool:
    parts = token.split(":")
    if len(parts) != 3: return False
    user_id, timestamp, sig = parts
    payload = f"{user_id}:{timestamp}"
    expected = hmac.new(SECRET_KEY.encode(), payload.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

✅ 安全重構代碼(Fail-Safe 業界模範):

# ✅ 安全實作:Fail-Safe 啟動守衛,拒絕不安全降級
import os, hmac, hashlib, time

def get_secret_key() -> bytes:
    key = os.getenv("SESSION_SECRET")
    if not key:
        # ✅ 當密鑰未配置時直接中斷啟動,防止系統退化為已知密鑰狀態
        raise RuntimeError("[CRITICAL_SECURITY_ERROR] SESSION_SECRET must be configured in environment variables!")
    return key.encode()

def create_session(user_id: str) -> str:
    timestamp = str(int(time.time() * 1000))
    payload = f"admin-authenticated:{timestamp}"
    sig = hmac.new(get_secret_key(), payload.encode(), hashlib.sha256).hexdigest()
    return f"{payload}:{sig}"

def verify_session(token: str) -> bool:
    parts = token.split(":")
    if len(parts) != 3: return False
    prefix, timestamp_str, sig = parts
    if prefix != "admin-authenticated": return False

    # 驗證時效 (3天過期)
    timestamp = int(timestamp_str)
    if (time.time() * 1000) - timestamp > 3 * 24 * 60 * 60 * 1000:
        return False

    payload = f"{prefix}:{timestamp_str}"
    expected = hmac.new(get_secret_key(), payload.encode(), hashlib.sha256).hexdigest()
    # ✅ 常數時間安全比對
    return hmac.compare_digest(expected, sig)

3. 攻擊鏈三(High):形式化 HttpOnly Cookie 升級 —— Token 雙通道暴露

3.1 深度攻防背景與威脅原理

許多開發團隊在落實安全規範時,只完成了「伺服器端設置 Cookie」的表面功夫,卻未重構前端代碼。如果登入 API 的 Response JSON 依然回傳 { token },前端依然執行 localStorage.setItem('token', ...),那麼哪怕 Cookie 設置了 HttpOnly,攻擊者只要在頁面上發起任何 XSS 攻擊(例如留言區、Markdown 渲染繞過),依然能一行代碼讀取 Storage 內的 Token。

3.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼:

// ❌ 不安全實作:Body 洩漏 Token 且前端寫入 localStorage
app.post('/api/auth/login', async (req, res) => {
  if (checkCredentials(req.body)) {
    const token = signToken();
    res.cookie('admin_token', token, { httpOnly: true });
    return res.json({ ok: true, token }); // ❌ 漏洞: Token 依然在 JSON 中
  }
});

// 前端代碼仍執行:
// const data = await res.json();
// localStorage.setItem('admin_token', data.token); // ❌ XSS 漏洞可讀

✅ 安全重構代碼:

// ✅ 安全實作:Body 僅回傳狀態,前端 JavaScript 記憶體與 Storage 零 Token
app.post('/api/auth/login', async (req, res) => {
  if (checkCredentials(req.body)) {
    const token = signToken();
    res.cookie('admin_token', token, {
      httpOnly: true,                  // ✅ JS 完全無法讀取
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',              // ✅ 嚴格跨站隔離
      path: '/',
      maxAge: 3 * 24 * 60 * 60 * 1000,
    });
    return res.json({ ok: true });     // ✅ JSON 零憑證洩漏
  }
});

// ✅ 前端登入狀態完全由伺服器端 Cookie 鑑權接口決定
app.get('/api/auth/me', (req, res) => {
  const token = req.cookies.admin_token;
  if (verifySession(token)) return res.json({ authenticated: true });
  return res.status(401).json({ authenticated: false });
});

4. 攻擊鏈四(High):Serverless / 分散式環境下的限速分散與並發爆破

4.1 深度攻防背景與威脅原理

在單體架構時代,使用記憶體變數(new Map())記錄 IP 失敗次數是常見做法。但現代應用廣泛運行於 Serverless 邊緣函數(Edge Functions)或 Kubernetes Pod 自動水平擴展(HPA)環境中。攻擊者發起的 50 次密碼爆破請求會被負載均衡器均勻打散至多個獨立實例,每個實例的記憶體計數器僅記錄到少數幾次,單機限速閾值被完美繞過。

4.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼(單機記憶體限速):

// ❌ 不安全實作:單機記憶體計數,在多容器/Serverless 架構下瞬間失效
const attemptsMap = new Map();

function checkRateLimit(ip) {
  const count = attemptsMap.get(ip) || 0;
  if (count >= 5) return { locked: true };
  attemptsMap.set(ip, count + 1);
  return { locked: false };
}

✅ 安全重構架構(PostgreSQL 原子分散式限速):

-- 建立原子化分散式限速資料表
CREATE TABLE IF NOT EXISTS login_attempts (
  ip VARCHAR(64) PRIMARY KEY,
  attempts INT NOT NULL DEFAULT 1,
  locked_until TIMESTAMPTZ,
  first_attempt_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
// ✅ 安全實作:利用 ON CONFLICT 實現跨實例原子計數與鎖定
async function recordFailureDistributed(ip, maxAttempts = 5, lockDurationMs = 900000) {
  try {
    const lockUntil = new Date(Date.now() + lockDurationMs).toISOString();
    // 跨全球所有邊緣節點原子累加
    const result = await db.query(`
      INSERT INTO login_attempts (ip, attempts, first_attempt_at, updated_at)
      VALUES ($1, 1, NOW(), NOW())
      ON CONFLICT (ip) DO UPDATE SET
        attempts = login_attempts.attempts + 1,
        locked_until = CASE 
          WHEN login_attempts.attempts + 1 >= $2 THEN $3::TIMESTAMPTZ 
          ELSE login_attempts.locked_until 
        END,
        updated_at = NOW()
      RETURNING attempts, locked_until;
    `, [ip, maxAttempts, lockUntil]);

    const { attempts, locked_until } = result.rows[0];
    const isLocked = locked_until && new Date(locked_until) > new Date();
    return { locked: isLocked, remainingAttempts: Math.max(0, maxAttempts - attempts) };
  } catch (err) {
    // ✅ 高可用優雅降級:若 DB 連線異常,自動退回本機記憶體計數,防止服務癱瘓
    return memoryLimiterFallback(ip);
  }
}

5. 攻擊鏈五(High):動態 SQL 查詢注入與布林盲注

5.1 深度攻防背景與威脅原理

當業務需要支援多條件動態過濾(例如依據分類 ID 或標籤 ID 篩選文章)時,開發者為了動態組裝 SQL,往往直接採用字串拼接。攻擊者藉由傳入 SQL 保留字元與註解符號,可操控資料庫執行任意子查詢,造成全庫資料洩漏。

5.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼:

// ❌ 不安全實作:動態拼接 SQL 字串
async function queryPosts(filter: { categoryId?: string }) {
  let query = 'SELECT * FROM posts WHERE 1=1';
  if (filter.categoryId) {
    query += ` AND category_id = '${filter.categoryId}'`; // ❌ 致命 SQL 注入
  }
  return await db.rawQuery(query);
}

✅ 安全重構代碼:

// ✅ 安全實作:嚴格正則格式強校驗 + 參數化查詢
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

async function queryPosts(filter: { categoryId?: string }) {
  if (filter.categoryId) {
    // 1. 輸入正則強校驗:非合規 UUID 立即在進入資料庫前拋出異常
    if (!UUID_REGEX.test(filter.categoryId)) {
      throw new Error('[SECURITY_ALERT] Invalid categoryId format: Must be UUID');
    }
  }

  // 2. 參數化綁定查詢
  return await db.query(
    'SELECT * FROM posts WHERE ($1::uuid IS NULL OR category_id = $1::uuid)',
    [filter.categoryId || null]
  );
}

6. 攻擊鏈六(High):Markdown 匯出打包中的 SSRF 與雲端元數據(169.254.169.254)竊取

6.1 深度攻防背景與威脅原理

現代知識庫與 CMS 系統常提供「離線打包匯出 Markdown 與圖片為 ZIP」之功能。伺服器在執行此功能時,會主動解析 Markdown 內文中的圖片 URL 並在後端發起 HTTP GET 請求下載檔案。若未對 URL 進行嚴格的協定與網段過濾,攻擊者可嵌入指向雲端基礎設施元數據端點(http://169.254.169.254/latest/meta-data/)的連結,將雲端主機的最高權限 IAM 臨時憑證自動打包至 ZIP 下載檔中,達成整座雲端帳號的接管。

6.2 工業級代碼對比(Vulnerable vs Secure)

❌ 不安全代碼:

// ❌ 不安全實作:簡易字串比對,極易被多種 IP 變形繞過
async function fetchRemoteImage(imageUrl: string) {
  if (imageUrl.includes('localhost')) {
    throw new Error('Invalid URL');
  }
  return await fetch(imageUrl); // ❌ 依然能打穿 169.254.169.254 與 10.0.0.0/8
}

✅ 安全重構代碼:

// ✅ 安全實作:多層 CIDR 私有網段、保留位址與雲端元數據端點全面攔截
import { URL } from 'url';

function isSafeExternalUrl(rawUrl: string): boolean {
  try {
    const parsed = new URL(rawUrl);
    // 1. 強制僅允許 HTTPS 協定
    if (parsed.protocol !== 'https:') return false;

    const host = parsed.hostname.toLowerCase();

    // 2. 阻斷本機、內部網域、私有 IP 與雲端元數據 (169.254.0.0/16)
    const isRestricted =
      host === 'localhost' ||
      host.endsWith('.local') ||
      host.endsWith('.internal') ||
      /^127\./.test(host) ||
      /^10\./.test(host) ||
      /^192\.168\./.test(host) ||
      /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(host) ||
      /^169\.254\./.test(host) ||
      host === '0.0.0.0' ||
      host === '::1';

    return !isRestricted;
  } catch {
    return false;
  }
}

async function fetchRemoteAsset(url: string, timeoutMs = 5000) {
  if (!isSafeExternalUrl(url)) {
    throw new Error('[SECURITY_ALERT] Blocked restricted network request');
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timeout);
  }
}