Cursor 插件市场后门:VS Code 分支的信任问题
Cursor 插件市场中的 Claude Code 后门事件说明,VS Code 分支无法继承微软的扩展信任模型。现在该检查什么?

某个周二早上,一位开发者打开 Cursor,看到 Claude Code 扩展有更新,就点了安装。表面上一切如常:扩展正常加载,AI 助手也能用。到了午饭时间,这台机器的 ~/.ssh/authorized_keys 里已经多了一把攻击者的 Ed25519 公钥,一条反向 SSH 隧道正在连向互联网上的一台服务器发送心跳,还有一个伪装成 Apple 系统进程的 LaunchAgent 让这一切在重启后依然存活。这就是 Phorion 披露的 Cursor 插件市场后门,也是迄今最清楚的例子,证明了安全圈多年来一直在嘀咕的一件事:VS Code 分支继承了微软的代码库,却没有继承微软的信任模型。
我在职业生涯里花了不少时间处理开发者机器上的应急事件,这次事件让我印象特别深,因为投递方式实在太寻常了。没有钓鱼,没有零日漏洞,只是一个被篡改的扩展包,版本 2.1.312 和 2.1.313,发布到了一个开发者默认信任的插件市场,因为它看起来就是他们本来就信任的那个东西。Anthropic 自己的渠道是干净的,有人利用了 Cursor 发布流水线的访问权限,推送了一份 Anthropic 从未为该市场构建过的修改版扩展。影响范围是在十一个小时窗口内自动更新的所有 Cursor 用户。
后门实际做了什么
植入代码是一个立即执行函数表达式(IIFE),被添加到压缩后的 extension.js 包的最前面。注入的前缀之后的所有内容看起来都和正常扩展一样,同样的 activate 和 deactivate 导出,同样的 UI 代码,同样的字符串。如果你的检测策略是“把包和上游做 diff”,那这才是真正该让你担心的地方:恶意部分只是叠在一个几兆字节的合法文件上方的几百字节额外 JavaScript。它在 Node 加载模块的那一刻就会运行,早于扩展自身的 activate 回调。用户不需要点击任何东西,不需要调用 Claude,也不需要和扩展有任何交互。
这个载荷本身是一个四阶段的任务,说实话,写得相当老练:
- 植入密钥。把攻击者控制的 Ed25519 公钥追加到
~/.ssh/authorized_keys,注释为svc-telemetry,并设置正确的 0700/0600 权限。 - 启用 SSH。尝试确保 sshd 正在运行:Linux 上用 systemctl,macOS 上用“远程登录”,Windows 上通过 PowerShell 安装 OpenSSH 服务器。
- 反向隧道。通过
-R *:0:localhost:22向攻击者控制的服务器发起出站 SSH 连接,这样攻击者就能通过隧道回连受害者的 22 端口,并使用植入的密钥登录。无需自定义 C2 协议,SSH 本身就是 C2。 - 持久化与侦察。macOS 上是名为
com.apple.sysmond.plist的用户级 LaunchAgent,Linux 上是 @reboot 类型的 cron 条目,Windows 上则是启动文件夹作为备用。然后发送一次性信标,包含主机名、用户名、git 邮箱、公网 IP,以及最多四十个匹配/KEY|TOKEN|SECRET|PASS|AWS|GCP/i的环境变量名称。
最后这一项的设计很巧妙,值得细想。信标并不会窃取你的 AWS 密钥,它窃取的是你拥有这个密钥的事实。这相当于一份目标清单:攻击者能拿到一份列表,知道哪些被控机器的环境里有有效云凭证,也就是说,通过隧道接下来最该先回去找的就是这些人。
意外救了人的 macOS Bug
作为应急响应人员,我觉得有个细节特别有意思:恶意软件在 macOS 上的启用路径被一个经典的脚本错误搞坏了。代码以非特权用户身份运行 systemsetup -getremotelogin,只有当输出匹配 /Off/i 时才会尝试开启远程登录。但在 macOS 上,这个命令没有管理员权限就拒绝运行,它会向标准输出打印“You need administrator access to run this tool... exiting!”,然后以退出码 0 结束。
// what the malware effectively does
var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
if (/Off/i.test(rl)) {
det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
}
这段错误信息里没有“Off”,所以正则不匹配,启用步骤就从不执行。脚本把“我没有权限检查 SSH 是否关闭”当成了“SSH 已经开启”。命令失败却返回退出码 0,这是 shell 脚本里最古老的坑之一,这次它反过来坑了恶意软件作者,而不是某个系统管理员。在没有开启远程登录的 Mac 上,反向隧道可以正常连出去,但任何经隧道转发回来的访问都会撞上关闭的 22 端口。密钥和持久化仍然会被投放,机器依然已经失陷,只是交互式访问的路径一开始就是死的。
别因此就觉得安心。如果用户已经开启了远程登录,或者以后某天开启了,植入的密钥就在 authorized_keys 里等着。Windows 路径也有类似的 bug(它通过一个写死 /bin/sh 的辅助程序转发 PowerShell 命令),而 Linux 的 cron 逻辑在重复运行时可能会清空用户已有的 crontab。人写的恶意软件也会有人为的 bug,但下个月的不会。
为什么分叉的插件市场无法继承微软的信任模型
这是结构性的问题,也是我希望工程负责人们好好思考的一点。当你从官方 Visual Studio Marketplace 安装扩展时,背后有一套真正的流程:发布者账户需要域名验证,发布时会扫描,有下架机制,而另一端还有一个设有法务部门的公司。它远谈不上完美,恶意扩展也确实有漏网的时候,但它是一个体系。
Cursor 这类 VS Code 分支因为授权方面的原因无法使用那个市场,于是它们指向了其他注册表,比如 Open VSX 或者自己的基础设施。编辑器界面和原版一模一样,扩展页面上的名称、图标、看起来很像样的发布者名称都一样。但每一项让原始市场值得信任的控制措施,要么是被重新实现的,要么干脆不存在。谁验证了发布者?谁扫描了包?这个产物是否真的从它声称的源码仓库构建而来,你能不能验证?在这次事件中,被篡改的版本显然从未出现在微软的注册表上,它们只存在于这个分支的仓库里。所谓的信任纯粹是视觉上的。
包管理器的世界里我们已经看过同样的剧情。LiteLLM PyPI 被攻破的原因,正是发布权限才是薄弱环节,而不是代码本身。扩展在一个重要方面比包更糟:没有 lockfile 的文化,没有 npm ci 那样的可复现构建,也没有哈希锁定。编辑器会静默自动更新。正如一位 Hacker News 评论者发现的,VS Code 的自动更新延迟甚至不适用于“受信任的发布者”,而这份名单你还无法配置。你的扩展集合就是一个实时的、未锁定版本的依赖树,它以你的完整用户权限运行,并且在你睡觉时自己更新。

AI 编程工具是你自己选择的权限提升
撇开插件市场的风波,来看看攻击目标到底是什么。AI 编程扩展不是一个配色主题。它以 IDE 的权限运行,而 IDE 的权限就是你的权限,它的全部工作就是读取你的源码树,持有你的 API 凭证,并且越来越多地代表你执行 shell 命令。我们之前写过智能体编程 CLI 如何处理权限,反复出现的主题是:有用的智能体和危险的智能体之间的区别在于策略,而不是能力。一个能运行 child_process 的扩展,离运行任何东西只差一次恶意发布。
有位评论者说得很直白:每个用这些工具的人,实际上每天都在执行 curl 管道接 bash,而我们花了十年时间告诉初级开发者不要这么干。这话有点夸张,但也只是有点。curl | bash 和会自动更新的智能体扩展之间的区别,就是后者的界面更漂亮。我之前论证过CLI 工具默认不该安装后台守护进程,这次事件就是守护进程被别人安装、而你根本没有同意的情况。IDE 退出后仍存活的分离 ssh 进程,正是我在代码审查中会标记的行为模式,而这次它是攻击者通过受信任的更新渠道推送进来的。
现在就检查你的机器群
如果你的组织里有人在用 Cursor,请把这件事当作正在发生的事件来对待,而不是一条新闻。以下是高信号的指标,直接来自取证分析报告:
# macOS / Linux: the planted key is the cleanest signal
grep -s svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "key not found"
# Check persistence artifacts
ls ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null # macOS
ls ~/.config/.smt ~/.config/.smtk 2>/dev/null # macOS + Linux
crontab -l 2>/dev/null | grep -F .smt # Linux @reboot entry
# Windows (PowerShell): check both authorized_keys locations
Select-String svc-telemetry "$HOME\.ssh\authorized_keys",
"C:\ProgramData\ssh\administrators_authorized_keys" -ErrorAction SilentlyContinue
# Live tunnel: look for detached ssh with remote forwarding
ps aux | grep -E 'ssh.*-R' | grep -v grep
根据经验,有几点狩猎建议。第一,要核对密钥指纹,而不只是看注释,因为植入程序会根据注释字符串 svc-telemetry 去重,清理脚本也可能这么做。第二,隧道密钥文件位于临时目录中,文件名形如 .stk_ 加上十个十六进制字符,这十个字符由主机名加主目录路径的 MD5 推导而来。它在每台机器上是确定的,所以你可以算出确切的文件名并进行扫描。第三,对于标准用户,EDR 进程遥测不会显示 macOS 上的启用尝试,因为由于那个 bug,它从来没有真正执行过。你日志里没有 systemsetup -setremotelogin 的记录,并不代表没有被攻陷。密钥和 LaunchAgent 才是真正的依据。
如果你找到了密钥,这台机器就需要完整的应急响应流程:轮换所有环境变量名可能已被信标发送的凭证(这基本上是大部分凭证,因为正则能匹配 AWS、GCP,以及任何名字里带 KEY 或 TOKEN 的东西),撤销该机器的访问权限,然后重装系统。不要只删掉密钥就以为万事大吉。攻击者已经拥有或尝试获得交互式 shell 访问权限,而你无法仅凭植入程序的代码就列出他们接触过的所有内容。这也是云认证日志的盲区会发挥作用的地方:请根据受影响用户的凭证,检查从扩展更新到事件披露这段时间内的云审计日志,看是否有控制台或 API 的使用记录。

加固:围堵编辑器,而不只是扩展
事件之后,大家都会问该改什么。我的立场,而且我愿意为之辩护:你永远不可能通过审计来达到一个安全的扩展生态,所以要靠围堵。我们在AI 智能体的围堵与监控一文中讨论过这类通用的权衡,而这次事件正是围堵的案例研究。在这里,监控什么也没告诉你,直到一家第三方安全公司碰巧拆解了这个包。
- 固定扩展版本,并对拥有广泛权限的扩展关闭自动更新,同样的规则也适用于各个分支。是的,你会承受更新疲劳,但这比应急响应便宜得多。
- 在开发者工作站上做出站过滤。整个访问模型依赖于到一台 VPS IP 的出站 SSH 连接。如果你的开发机器无法随意对外开放 22 端口,隧道阶段就会失效。几乎没人这么做,而这样做足以让这个植入程序完全失效。
- 把 SSH 状态当作金丝雀来监控。对远程登录被开启、authorized_keys 中出现新密钥,以及在本不该运行 sshd 的机器上出现 sshd 进程等情况发出告警。
- 像盘点服务器一样盘点你的 AI 工具。清楚知道哪些扩展和智能体 CLI 已获批准,它们拥有哪些权限,以及它们来自哪个市场。如果被问到“我们的 Cursor 安装来自哪个注册表”时大家只能耸耸肩,那就是你下个季度的项目。
- 在可行的情况下,优先选择在沙箱或容器中运行的智能体,凭证按会话注入,而不是常驻在环境变量里。信标之所以能抓取环境变量名,正是因为我们把密钥随意留在了进程环境中。
一个没有自己验证流水线的扩展市场,根本算不上市场,只是一个带安装按钮的文件托管站。
令人不安的结论
Anthropic 并没有被入侵,被攻破的是 Cursor 的插件市场,但 Anthropic 的品牌还是承受了损失,因为对开发者来说,扩展本身就是产品,不管是谁发布了哪个版本。这就是分叉生态的经济学:攻击高价值目标最便宜的方式,就是攻击该目标用户所信任的最薄弱的注册表。AI 编程助手恰好处在一个完美的交叉点上:安装量巨大,本地权限最高,凭证无处不在,而其供应链会把它们的产物分叉并重新托管到安全水平参差不齐的各个注册表上。
这次事件最终在大约十一个小时内撤下了恶意版本,而一个 macOS 的 bug 限制了损害范围。响应本身还算到位,问题出在架构上,而且它至今没有改变:你的编辑器就是一个包管理器,它以你的身份运行所安装的一切,静默更新自己,而且在越来越多的环境里,它的包来自一个你的安全团队从未评估过的注册表。修好出站规则,固定扩展版本,盘点你的智能体。下一次发布者被攻破时,就不会再有一个正则 bug 来保护你的用户了。


