許多開發者認為只要保護好原始碼倉庫(Source Code Repository)和 npm 登入金鑰,套件就是安全的。然而,近期 TanStack 遭遇的一次精密供應鏈攻擊(Supply Chain Attack)打破了這個認知。這次攻擊並非透過盜取金鑰,而是利用 CI/CD 流程中的權限漏洞與緩存機制,在短短六分鐘內污染了 42 個 npm 套件。
對於工程師來說,這起事件揭示了一個關鍵風險:現代軟體開發的威脅面已從單純的程式碼,轉移到了將程式碼轉化為產品的不可見膠水——也就是 CI/CD 流水線、緩存系統與自動化權限。
攻擊路徑解析:緩存投毒與權限跳轉
這次攻擊的起點是一個看似無害的 Pull Request(PR)。攻擊者首先對 TanStack Router 倉庫建立了一個分叉(Fork),並提交一個 PR。
這裡的核心漏洞在於 GitHub Actions 的 pull_request_target 工作流。在一般的 pull_request 事件中,工作流權限較低,無法對倉庫造成嚴重影響。但 pull_request_target 允許工作流在基礎分支(Base Branch)的權限下執行,這旨在方便維護者自動處理 PR,但如果配置不當,會讓外部提交者能觸發具有高權限的操作。
攻擊者利用這個漏洞,將惡意程式碼植入到 GitHub Actions 的快取(Cache)中。這被稱為緩存投毒(Cache Poisoning)。由於 GitHub Actions 的緩存會在不同工作流之間共享,當維護者隨後合併其他不相關的合法 PR 並觸發正式發佈流程時,系統會自動還原先前被污染的緩存。
結果,惡意程式碼在正式的發佈流水線中被執行。由於該流水線配置了 OIDC(OpenID Connect,一種允許 CI 系統在不需要長期金鑰的情況下,透過信任關係向 npm 申請臨時權限的認證機制),攻擊者成功在不需要 npm 金鑰的情況下,直接向 npm 註冊表發佈了 84 個惡意版本的套件。
惡意程式碼的行為與傳播
一旦開發者或 CI 系統安裝了這些被污染的套件,惡意程式碼會透過 npm 的生命週期腳本(Lifecycle Scripts,例如 postinstall)自動執行。其主要目標是竊取環境憑證,包括 AWS、GCP、Kubernetes、Vault 的金鑰,以及 SSH 金鑰與 GitHub 認證資料。
更危險的是,該惡意軟體具備自我傳播機制。它會掃描受害者機器上維護的其他 npm 套件,並嘗試將同樣的惡意程式碼注入到這些套件中,試圖以此形成連鎖反應,擴大受影響的範圍。
防禦反思與實務建議
TanStack 這次之所以能快速發現問題,是因為惡意程式碼在發佈過程中不小心導致部分測試失敗,而非觸發了內部的監控警報。這提醒我們,依賴測試失敗來發現安全漏洞是非常危險的。
針對此類攻擊,工程實務上應採取以下強化措施:
第一,審慎使用 pull_request_target。除非絕對必要,否則應避免在處理外部 PR 時賦予高權限。
第二,固定 GitHub Actions 的版本。不要使用 @main 或 @v2 等動態標籤,而應使用不可變的 SHA 哈希值(Immutable SHA),確保執行的 Action 內容不會被第三方更改。
第三,強化緩存隔離。意識到 CI 緩存可能成為攻擊向量,定期清理緩存,並對緩存的讀寫權限進行更嚴格的控制。
第四,導入供應鏈驗證機制。例如採用 SLSA(Supply chain Levels for Software Artifacts,一套定義軟體構建完整性的框架)來驗證構建來源,或使用 Sigstore 進行數位簽署,確保發佈的套件確實來自受信任的構建流程。
總結來說,安全不再僅僅是寫出正確的程式碼,更在於如何安全地構建、發佈與信任整個交付流水線。
來源:infoq.com
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。