npm 蠕蟲如何竊取憑證,以及憑證輪替次序
How npm worms steal your credentials, and the order to rotate them in
npm 蠕蟲可利用套件生命週期指令碼竊取開發者憑證,文章提出遭入侵後按風險安排的輪替次序。npm 的 preinstall、install 和 postinstall 指令碼會以用戶權限執行,能讀取用戶可讀檔案並發出網絡請求。作者建議先隔離及清除惡意程式,再撤銷 npm 等套件 registry token,之後處理 GitHub、雲端、CI 和其他憑證,並在乾淨裝置上進行輪替。
文章按蠕蟲的散播風險排列憑證輪替步驟,解釋為何應先清除持續執行的惡意程式,再撤銷套件 registry token。
幾乎每則 npm 供應鏈安全公告,最後都會附上同一項指示:更換這部電腦可能存取到的所有憑證。聽起來很簡單,直到你在凌晨 2 時真的要動手。這部電腦有哪些憑證?它們存放在哪裏?更換次序重要嗎?
次序確實重要。至少在近期一宗個案中,先做最直覺的事會刪除你的家目錄。
本文會逐步説明惡意套件可以讀取開發者電腦上的哪些資料,為何蠕蟲特別會先竊取某些憑證,以及一套可靠的更換次序。文末的實例會使用一個小型開源命令列工具,但無論你使用工具還是紙本清單,次序都適用。
惡意套件如何執行
npm 套件可以在 package.json 中宣告生命週期指令碼:preinstall、install 和 postinstall。執行 npm install 時,npm 會以你的用戶權限執行這些指令碼。不會提示,也沒有沙盒。
{
"name": "innocent-looking-lib",
"version": "1.2.4",
"scripts": {
"preinstall": "node ./setup.js"
}
}
setup.js 中的任何內容都可以讀取你的用戶有權讀取的任何檔案,並發出網絡請求。整個攻擊就是如此。本月的兩個真實案例,展示了這些酬載在實際情況下的運作方式。
2026 年 10 月 8 日,tensorlake@0.5.144 由一個遭入侵的維護者帳戶發佈。根據項目自己的安全公告(GHSA-8g63-53c9-65j2),酬載透過 preinstall 掛鈎執行,並竊取了「GitHub 和 npm 權杖、雲端服務供應商憑證(AWS、GCP)、Kubernetes 和 Vault 權杖、SSH 和 GPG 金鑰、.env 檔案、瀏覽器儲存的密碼、加密貨幣錢包,以及 AI 工具和 MCP 設定」。之後,它利用偷取的權杖,將自身重新發佈至受害者有權寫入的其他套件。該版本上線約一小時四十分鐘,而且具備有效的 npm 來源證明,因為攻擊者是透過項目自己的發佈工作流程發佈套件。
幾天前,@subql/common@5.8.3 發佈了一個 postinstall 掛鈎,用來解碼並執行經混淆處理的酬載。OSV 條目 MAL-2026-17571載有相關分析。
這兩個案例使用的手法都不是新招。CISA 在2025 年 9 月有關 Shai-Hulud 蠕蟲的警示中,描述了同一種模式:竊取權杖,再利用它們發佈受害者自己套件的受感染版本。
竊取程式會先讀取甚麼
大部分這類酬載不需要甚麼複雜手法。開發者憑證通常存放在廣為人知的明文檔案中:
| 檔案 | 內容 |
|---|---|
~/.npmrc |
//registry.npmjs.org/:_authToken=...,你的發佈權杖 |
~/.config/gh/hosts.yml,~/.git-credentials
|
未使用作業系統金鑰環時的 GitHub 權杖 |
~/.aws/credentials |
AWS 存取金鑰,通常有效期很長 |
~/.kube/config |
叢集權杖及用戶端金鑰 |
~/.ssh/id_* |
私鑰,有時沒有密碼短語 |
專案中的 .env 檔案 |
資料庫 URL、付款金鑰、webhook 密鑰 |
MCP 設定(~/.cursor/mcp.json、Claude Desktop 設定等) |
直接貼入 env 區塊的 API 金鑰 |
CI 中的 process.env |
工作傳遞至安裝步驟的任何密鑰 |
你可以用一行指令檢查自己在 npm 上的憑證暴露情況:
grep -E '_authToken|_auth|_password' ~/.npmrc
如果輸出的是實際權杖(而非 ${NPM_TOKEN}),這部機器上的任何安裝指令碼都可以讀取它。
為何次序重要
當一部機器遭入侵時,你要同時與兩件事競賽:可能仍在運行的惡意軟件,以及已被竊取並正在其他地方使用的憑證。以錯誤次序輪換憑證,可能讓攻擊者再次竊取你剛輪換的憑證,或引發更嚴重的後果。
以下逐步説明箇中原因。
1. 先停止惡意軟件。如果載荷安裝了持久化機制,你從該電腦輪替的每項憑證都會再次被盜。tensorlake 的載荷令這點變得具體。其安全公告指出,它會安裝一個名為 gh-token-monitor 的服務,「如果被盜的 GitHub token 被撤銷,就會刪除用戶的家目錄」,並指示你在輪替任何 GitHub token 前先將它移除。因此,第一步是中斷該電腦的網絡連線、移除持久化機制,然後在另一部乾淨的電腦上完成以下所有輪替。
在 Linux 上,安全公告的移除步驟如下:
systemctl --user disable --now gh-token-monitor.service
rm -rf ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh
在 macOS 上:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
另外檢查 crontab -l、systemd 用戶單元、LaunchAgents 和 .vscode/tasks.json,看看有沒有任何你沒有新增的項目。
2. 接着處理套件登錄檔 token。這正是蠕蟲能成為蠕蟲的關鍵。被盜的 npm token 讓攻擊者可以為你維護的每個套件發布受感染版本,而這些版本會在每部安裝它們的電腦上執行。只要 token 仍然有效,感染就可能繼續擴散到你以外。撤銷它:
npm token list
npm token delete <token-id>
npm 自己的文件指出,撤銷最多可能需要一小時才會生效。然後對你維護的每個套件執行 npm view <your-package> time,並查看有沒有你未曾發布的版本。同樣的做法亦適用於 PyPI、RubyGems 和 crates.io token。
3. GitHub。GitHub token 讓攻擊者可以推送提交、加入工作流程,以及讀取私人儲存庫。它們排在 npm 之後,因為作為傳播途徑稍為不直接;也排在第 1 步之後,原因是上文提到的挾持服務伎倆。在 github.com/settings/tokens 撤銷 token,並在 /settings/applications 撤銷 OAuth 應用程式,然後執行 gh auth logout 和 gh auth login。
4. 雲端及基礎設施。AWS、GCP、Azure、Terraform、Kubernetes。這些憑證往往造成最嚴重的損害,但它們不會令蠕蟲傳播到其他人,因此排在傳播途徑之後。使用 AWS 時,先建立新金鑰再刪除舊金鑰,以免影響正在運作的系統;並在受影響期間檢查 CloudTrail,查看是否有人讀取 SSM Parameter Store 和 Secrets Manager。
5. 部署平台及 CI。包括儲存在平台環境變數儲存區的所有資料,以及 CI 傳遞給各個步驟的機密資料。記得重新部署,因為大多數服務會在啟動時讀取環境變數,並一直沿用舊值,直至重新啟動。
6. AI 供應商金鑰、SSH 金鑰和應用程式機密資料。這些仍然重要,也仍須處理,但無法讓攻擊擴散。
因此,處理順序是:停止正在執行的程式碼,封鎖攻擊用來擴散的途徑,然後先處理影響最大的項目,再清理其他所有項目。
範例:在工具中編碼處理順序
我把這個順序寫進一個名為 secret-blast-radius 的小型 CLI 工具,讓掃描結果可以把「輪替所有項目」轉成具體清單。有趣之處在於,排序所需的程式碼非常少。掃描器產生的每項發現都有一個 category,而各類別會對應至 src/index.js 中的一個表格:
export const ROTATION = {
persistence: { order: 0, label: 'Stop the malware first (worm services, malicious packages/extensions)', /* steps, verify, docs */ },
npm: { order: 1, label: 'Package registry tokens: npm, PyPI, RubyGems, crates.io, Composer (stops a worm republishing your packages)' },
github: { order: 2, label: 'GitHub tokens and sessions' },
cloud: { order: 3, label: 'Cloud and IaC credentials (AWS, GCP, Azure, Terraform, Pulumi)' },
kubernetes: { order: 4, label: 'Kubernetes and container registries' },
paas: { order: 5, label: 'Deploy platforms (Railway, Vercel, Fly, Render, Heroku, Netlify)', redeploy: true },
env: { order: 6, label: 'Secrets present in the environment / CI', redeploy: true },
ai: { order: 7, label: 'AI provider keys and MCP servers' },
ssh: { order: 8, label: 'SSH keys' },
dotenv: { order: 9, label: 'Application secrets in .env files', redeploy: true },
// ...
};
(已刪節。每個項目亦包含 steps、用來確認舊憑證已失效的 verify 檢查,以及供應商文件的連結。)
接着,操作手冊會進行篩選和排序。它只保留掃描實際發現項目的類別,避免一部只有 npm token 和 SSH 金鑰的電腦收到一份 12 步驟計劃:
export function runbook(result) {
const cats = [...new Set(result.findings.map((f) => f.category))]
.filter((c) => ROTATION[c])
.sort((a, b) => ROTATION[a].order - ROTATION[b].order);
return cats.map((c) => ({ category: c, ...ROTATION[c], items: result.findings.filter((f) => f.category === c) }));
}
一般報告中的發現會先按嚴重程度排序,再按輪替順序排序,因此兩項重大發現中,持久化項目會排在 npm token 上方。
如果你自行編寫工具,我會採納其中一個設計:表示「你已經感染」的發現,應歸入 persistence 類別,而不是另設類別。鎖定檔中的已知惡意版本、蠕蟲標記相依套件,或會執行偽裝檔案的 VS Code 工作,都歸入第 0 步。如果你已安裝惡意版本,首要工作是清理電腦,而不是在該電腦上輪替金鑰。
掃描器會檢查上表列出的明文儲存位置,並以簡短指紋(SHA-256 的首 8 個十六進位字元)代替實際值輸出,讓你分辨兩個權杖,而工具無須輸出任何機密資料。
試用方法:
npx secret-blast-radius # scan and print findings plus the rotation order
npx secret-blast-radius --runbook # Markdown checklist with steps and verify checks
npx secret-blast-radius --harden # read-only audit of settings that would have limited the damage
減輕下一次事件的損害
輪替屬於清理工作。一些設定可限制下一個惡意負載能存取的範圍:
- 在
~/.npmrc中設定ignore-scripts=true,並將少數需要建置指令碼的套件加入允許清單。pnpm 預設已封鎖相依套件的建置指令碼。 - 設定版本發佈後的冷卻期,避免立即安裝剛發佈的版本。pnpm 提供
minimumReleaseAge。tensorlake 的版本上線時間不到兩小時。 - 避免將權杖存放在明文檔案中。
gh auth login在作業系統金鑰環可用時會使用它。從 CI 發佈時,請使用 npm trusted publishing,而非長期有效的NPM_TOKEN。 - 不要在 CI 中把機密資料傳給
npm install步驟。只向需要機密資料的步驟提供這些資料。
關於來源證明,有一點要注意:它能告訴你套件的建置位置,卻不能判斷程式碼是否安全。tensorlake 公告指出,該惡意版本附有有效的來源證明,因為攻擊者使用了該專案自己的工作流程。
停止惡意軟件,撤銷讓它得以擴散的權杖,然後在乾淨的電腦上輪替其餘權杖。
披露:我是 secret-blast-radius 的維護者。
來源:DEV Community · dev.to