npm「btree」惡意套件攻擊:不靠安裝腳本,惡意程式碼藏在套件主程式裡

資安研究

2026-09-17 by 高田鑑識

本文整理自 Checkmarx Zero 研究團隊發表的〈npm ‘btree’ Malware Campaign Affects Millions of Downloads, No Need for Install Script〉。原文為持續更新的研究文章,請以原文最新內容為準。

Checkmarx Zero 揭露一起仍在進行中的 npm 供應鏈攻擊:攻擊者利用智慧合約作為命令與控制(C2)管道,並把惡意程式碼藏在套件本身的主程式裡,而非安裝腳本中。截至原文發表時,攻擊者據估已獲利 109 ETH,約 23 萬歐元。

重點摘要

  • 惡意套件:indexed-btree,偽裝成正常的 B 樹/索引工具 sorted-btree,週下載量接近 200 萬。
  • 不使用安裝腳本:package.json 中沒有 preinstall/postinstall,只安裝不會有任何動作;惡意程式碼在應用程式執行時才觸發。
  • C2 藏在區塊鏈上:惡意程式讀取 Sepolia 測試網上的智慧合約取得指令與位址,比傳統網域或 IP 更難封鎖。
  • 相關套件:另有 9 個後續被下架的套件使用同一套手法,其中 btree-core 累計下載近 195 萬次。

為什麼這件事值得注意:安裝腳本封鎖之後的攻擊轉移

npm 在 v12 版本限制了生命週期腳本(lifecycle scripts)的執行,不少人因此認為「沒有安裝腳本的套件比較安全」。Checkmarx 先前就預測攻擊者會調整手法,這次的案例正好印證:惡意行為只是從「安裝階段」延後到「載入並使用套件的階段」。

這也讓防守方容易產生誤判——只因為套件沒有安裝腳本,就認定它是正常的。事實上,indexed-btree 在這樣的情況下,仍然達到了每週近 200 萬次下載。

觸發方式:藏在最常被呼叫的方法裡

惡意載入程式被放在套件自己的 BTree.prototype.set 方法裡,也就是使用者幾乎每次都會呼叫的主要寫入函式。當條件成立時(原文範例中是 key 等於 100),程式會用 child_process.spawn 在背景啟動 extended/sharedLoad.min.js,以分離的子程序執行,不輸出任何訊息、也不顯示視窗,整段包在空的 try/catch 中,失敗時完全不會出現錯誤。

這是一種很有效的藏匿方式:安裝階段完全乾淨,感染點位在覆寫的原型方法裡,只要套件被實際使用就幾乎必然觸發,因此能繞過許多只看安裝腳本的檢測方式與一般靜態掃描工具。

以下是這段載入邏輯的簡化示意(非原文逐字內容):

BTree.prototype.set = function (key, value, overwrite) ;

惡意程式的行為

套件隨附的檔案經過高度混淆(例如字串陣列編碼與具自我校驗的陣列旋轉),主要功能如下:

  1. 蒐集並外傳主機資訊:作業系統、架構、主機名稱,以及 CPU、記憶體與開機時間等資訊,會傳送到寫死的 Slack 頻道與 Telegram 聊天室。
  2. 以智慧合約作為 C2:惡意程式輪詢部署在 Sepolia 測試網上的合約,透過合約的讀取與寫入函式取得資訊,而不是連線到固定網域。合約等於一個「指標」,舊位址被查封後,只要更新合約內容就能指向新位址,比傳統 C2 更耐得住網域或 IP 的查封。
  3. 以鏈上金鑰交換解鎖第二階段:載入程式自行產生 X25519 金鑰對,並從合約取得攻擊者的 X25519 公鑰,透過 ECDH 計算共享密鑰,再導出 AES 金鑰,用來解密儲存在合約中的兩段密文;兩段合併後就是第二階段惡意程式。
  4. 清除痕跡:具備刪除惡意檔案,以及從主程式中移除觸發程式碼的功能,增加事後調查的難度。

為什麼能被大量下載:精心包裝的「正常」外觀

這起攻擊在偽裝上相當用心:不僅沒有明顯的安裝腳本,還建立了一個外觀正常的 GitHub 儲存庫,裡面有大量看似合理的提交紀錄,而且儲存庫本身刻意不放惡意程式碼。攻擊者通常不會花力氣做這件事。

發佈者的 GitHub 帳號則幾乎沒有活動,也沒有詳細簡介,但使用了 AI 生成的大頭照,乍看之下更像真人。這些細節都可能讓開發者在挑選套件時放下戒心。

惡意套件 indexed-btree 的 GitHub 儲存庫(圖片來源:Checkmarx Zero)

惡意套件 indexed-btree 的 GitHub 儲存庫(圖片來源:Checkmarx Zero)

儲存庫中看似正常的提交紀錄(圖片來源:Checkmarx Zero)

儲存庫中看似正常的提交紀錄(圖片來源:Checkmarx Zero)

攻擊者建立的假 GitHub 帳號(圖片來源:Checkmarx Zero)

攻擊者建立的假 GitHub 帳號(圖片來源:Checkmarx Zero)

入侵指標(IoC)

受影響套件

套件 累計下載
indexed-btree(惡意樣本,偽裝成 sorted-btree) 週下載量近 200 萬
btree-core 1,951,274
btree-leaderboard 493,685
btree-range-store 468,092
ordered-kv-index 448,184
sliding-score-window 448,024
btree-time-index 425,312
priority-slot-queue 402,860
btree-lru-cache 372,185
neighbor-key-map 366,019

原文指出,攻擊者使用的智慧合約過去也曾出現在其他套件中,例如 mutex-forge。

智慧合約(以太坊 Sepolia 測試網)

  • 合約位址:0xE390863Dac96a7118C71227C2b099B50cF602D31
  • 攻擊者寫死的 X25519 公鑰(SPKI/DER,十六進位):bad013df6eec5d686f4cc8551e0a5c87a0135164bdd1dafb1c75141d1b526702

外傳管道

  • Telegram:聊天室 ID -1003952553968
  • Slack:頻道 ID C0B8XPGCKQS

原文另列有攻擊者使用的機器人權杖與區塊鏈 RPC 服務的 API 金鑰,這些屬於憑證類資訊,本文不重複刊載,需要時請參閱原文。

如何檢查與處置

1. 檢查是否使用相關套件(含間接相依)

npm ls indexed-btree ordered-kv-index btree-leaderboard priority-slot-queue btree-range-store btree-core btree-time-index btree-lru-cache neighbor-key-map sliding-score-window --all

2. 搜尋鎖定檔:被下架的套件仍可能留在 package-lock.json、pnpm-lock.yaml 或 yarn.lock 中,也可能還存在私有套件庫與快取裡。

3. 檢查套件目錄:檢視 node_modules 中是否存在 extended/sharedLoad.min.js,並留意 BTree.prototype.set 內是否有 child_process 的呼叫。

4. 若確認使用過:視為主機與該環境中的憑證都可能外洩,包含環境變數、CI/CD 金鑰、雲端帳密與 API 權杖,請輪替相關憑證,並檢視對外連線紀錄中是否出現 Telegram、Slack 或區塊鏈 RPC 的異常流量。

5. 調整檢測重點:封鎖安裝腳本仍值得做,但不能只靠這一層。需要搭配執行期的行為分析,並在選用套件時檢查名稱是否仿冒、發佈者歷史與實際程式碼內容,而不是只看有沒有安裝腳本或儲存庫是否好看。

高田觀點

這起事件說明了一件事:防禦手段一旦成為標準做法,攻擊者就會往下一個沒被覆蓋到的階段移動。對事件調查而言,「安裝階段乾淨」不能作為排除嫌疑的依據;需要回頭檢視套件實際被載入後的行為、子程序的建立紀錄,以及對即時通訊服務與區塊鏈節點的對外連線,這些才是還原感染過程的關鍵證據。