官方扩展被投毒11小时:5000台开发机装上SSH后门,自动更新与主机取证怎么补

发布时间:2026/11/2 15:27:12
官方扩展被投毒11小时:5000台开发机装上SSH后门,自动更新与主机取证怎么补 11 小时窗口官方 ID、官方渠道、官方签名2026 年 10 月 29 日Cursor 在自己的扩展市场里发现了一件不该发生的事Anthropic.claude-code的2.1.312与2.1.313两个版本被人改过装进去的是一套持久化 SSH 后门。恶意版本从当天 09:27 UTC 挂到 20:35 UTC前后约11 小时Cursor 估算受影响用户最多5000人。这件事最刺眼的地方不是后门本身而是它出现的位置。恶意包用的不是仿冒名称也不是山寨发布者而是 Anthropic 在市场上的官方扩展 ID走的是官方分发账号。攻击者拿到的不是某个开发者的电脑而是整条更新通道。版本线很清楚。恶意版本是 2.1.312 和 2.1.313Anthropic 随后发布 2.1.314并从 Open VSX 撤下 2.1.313 的相关发布Phorion 在 10 月 30 日发布分析时只标了 2.1.31310 月 31 日按 Cursor 的通报补充为 2.1.312 与 2.1.313。Ars Technica 在报道里指出截至其发稿Anthropic 尚未就此事向客户发出公开警告。Cursor 的处置动作可以拆成五步立即下架恶意版本并阻断分发出于谨慎整体关闭扩展市场审计访问记录确认没有其他扩展被动过轮换密钥、重建市场后重新上线联合托管服务商把攻击者的服务器下线让反向隧道与上报接口同时失效。把这件事当成“又一个钓鱼包”会看错重点。钓鱼包需要用户主动搜索、主动安装、主动放行这次不需要因为默认设置就是自动更新用户只要在那 11 小时里打开过一次编辑器扩展就可能已经被换掉。攻击窗口从“用户点不点”压缩成了“下一次启动什么时候”。所以本文不讨论攻击者是谁也不猜测账号是怎么丢的。真正可复用的部分在载荷里一个扩展包能对开发机做什么主机侧留下哪些痕迹以及团队在拿到一个 .vsix 时能检查什么。扩展加载即执行不需要用户做任何动作被改过的extension.js有一个很明显的形态在压缩后的整包前面插入了一段立即执行函数。这段函数一开头就引入了child_process、fs、os、path、http、crypto六个 Node 内置模块后面的业务代码看上去完全正常Claude Code 的字符串、VS Code 的 API、activate与deactivate导出都在。关键在时机。Node 加载这个文件时就会执行文件顶层代码注入的那段函数跑在扩展的activate回调之前。也就是说用户不需要打开侧栏、不需要发一条消息、不需要点任何命令只要编辑器把扩展宿主进程拉起来载荷就已经开始工作。权限模型决定了这一步的代价。扩展运行在 IDE 的进程里继承 IDE 与当前登录用户的权限能读本地文件、源码、Git 配置、开发工具链。IDE 主程序签名有效也拦不住扩展宿主进程去改~/.ssh下的文件因为签名验证的是二进制来源不是运行时的行为边界。VS Code 系的编辑器把扩展放在独立的扩展宿主进程里跑这个设计能防止扩展把编辑器主进程拖死但它不是安全沙箱。宿主进程以当前用户身份启动读写的是同一个文件系统、同一份环境变量、同一套凭据文件没有额外的路径隔离。所以“进程隔离”解决的是稳定性问题不是权限问题。这也解释了为什么这类事件总能一次影响几千台机器。分发侧的信任是集中式的安装侧的判断是分散式的中间还夹着一个默认开启的自动更新。三者叠加攻击者只要拿下发布账号就能把“可信更新”变成“可信投递”。第一步把攻击者的公钥写进 authorized_keys载荷的第一个动作很直接确认~/.ssh目录存在把一枚攻击者控制的 Ed25519 公钥追加到~/.ssh/authorized_keys目录权限设成0700文件权限设成0600。公钥注释是svc-telemetry看起来像一个正常的运维标识。这里有个细节值得所有做取证的人记住。脚本判断“是否已经写过”的方式是检查文件内容里有没有svc-telemetry这个注释字符串而不是比对公钥指纹。注释是攻击者自己写的改一个字就能绕过判重于是同一台机器可能被追加多枚公钥。排查时按注释搜会漏按公钥指纹查才准。公钥落地之后第二步是让 SSH 服务跑起来。三个平台各有一套尝试Linux 走systemctl启sshd或ssh失败再退到service与/etc/init.d/macOS 先查 Remote Login 状态再用sudo -n systemsetup -setremotelogin on尝试打开Windows 用 PowerShell 安装并启动 OpenSSH Server。整条链里没有任何提权利用。非交互的sudo -n在普通用户下基本会失败装系统服务需要管理员权限这两点在绝大多数开发机上都不成立。所以 Cursor 的通报里写得很清楚在多数 macOS 与 Windows 主机上开启 SSH 这一步会失败攻击者无法通过隧道登录。但这不等于主机是干净的。公钥已经写进authorized_keys持久化已经落地侦察包已经发出。只要这台机器原本就开着 SSH或者用户后来手动打开了远程登录那枚公钥立刻就能用。攻击链断在最后一环和攻击链不存在是两件完全不同的事。macOS 上的那个空权限检查Phorion 的分析里有一段很值得抄进检测规则。macOS 分支先用systemsetup -getremotelogin读状态只有输出里匹配到Off才去执行开启命令。逻辑本身没错错在它假设这条读取命令在普通用户下能返回真实状态。实际跑起来是这样的标准用户执行systemsetup -getremotelogin工具会打印一行“需要管理员权限”的提示然后以状态码 0 退出。stdout 里没有Off正则匹配失败脚本就跳过了开启分支。它把“我没有权限检查”当成了“SSH 已经开着”。这个错误同时带来两个后果。一方面攻击链的入站入口在很多 Mac 上是断的另一方面主机上不会出现systemsetup -setremotelogin on这条命令如果检测规则只盯这条命令就会整批漏报。规则要盯的是文件写入与持久化而不是那条可能永远不会执行的开启命令。Windows 分支也有自己的问题。脚本把一个 PowerShell 服务安装命令交给内部辅助函数去跑而这个函数把 shell 固定成了/bin/sh在标准 Windows 环境里根本走不通。两个平台各有一个实现缺陷这也是本次事件影响面没有进一步扩大的直接原因。把实现缺陷当成防线是危险的。攻击者下一次修掉这两行代码同样的载荷就会完整生效而这次的公钥、持久化脚本、隧道私钥都已经真实落盘清理工作量一点没少。反向隧道-R *:0:localhost:22 是什么意思公钥只是门票真正把门打开的是隧道。脚本用内嵌的一枚未加密 Ed25519 私钥向134.209.82.225的 22 端口发起出站 SSH登录用户名是tunnel。这枚私钥的注释是svc-telemetry-tun和前面种到受害者机器上的公钥不是同一对用途正好相反一枚负责让攻击者进来一枚负责让受害机器连出去。私钥的落地方式也设计过。它被写到操作系统临时目录文件名是.stk_加上MD5(hostname HOME)的前 10 位十六进制。名字带点、内容随机既不会撞车也不容易被普通的临时文件清理规则注意到。隧道参数是这段代码里最值得记住的部分-R *:0:localhost:22要求服务端动态分配一个监听端口把打到该端口的流量原路转发回本机的 22-N表示不执行远程命令只保留端口转发。配套的选项是StrictHostKeyCheckingno、UserKnownHostsFile/dev/null、BatchModeyes、IdentitiesOnlyyes再加上 30 秒心跳与ExitOnForwardFailureyes。这套组合的效果是攻击者不需要在 JavaScript 里写任何命令处理逻辑。他只要在自己的服务器上连上被分配到的那个端口用种下去的公钥认证本机的 22就拿到一个完整的交互式 shell 和文件传输能力。C2 通道是系统自带的 sshd流量看起来就是一条出站 SSH。维持连接的部分同样省事。装了autossh就用它没装就用普通ssh外面套一个脱离终端的循环每 60 秒重试一次用pgrep避免重复起进程。因为是脱离进程用户关掉编辑器隧道还在。取证时有个容易看错的地方如果攻击者真的通过隧道登录过SSH 日志里的来源地址会显示127.0.0.1或::1认证用的就是那枚svc-telemetry公钥。看到“本地登录”不要当成正常的本机操作要去看它用的是哪把钥匙。从网络侧看这条隧道很难被“异常端口”规则抓到。它用的是 22 端口协议是标准 SSH加密握手正常出站方向在多数企业网络里默认放行。真正可用的信号是目的地一台开发机主动连一个陌生公网 IP 的 22 端口并且长期保持连接这件事本身就值得告警。三套持久化伪装成系统进程的 LaunchAgent为了让访问在重启后依然可用脚本按平台写了三套落地文件。它们都藏在用户目录的隐藏路径里其中 macOS 那份还特意借用了苹果的命名前缀。平台落地文件持久化方式Linux~/.config/.smt、~/.config/.smtk写入重建隧道并循环连接的脚本向用户 crontab 添加reboot条目macOS~/.config/.smt、~/.config/.smtk、~/Library/LaunchAgents/com.apple.sysmond.plist用户级 LaunchAgentRunAtLoad与KeepAlive均为真用launchctl加载Windows~/.config/smt.cmd、~/.config/smtk、启动目录下的sysmon.cmd先尝试创建以 SYSTEM 身份运行的计划任务SysMon失败则回退到启动文件夹macOS 那一行是这次事件里最像“正规软件”的一处。com.apple.sysmond.plist放在用户的 LaunchAgents 目录里标签带com.apple.前缀配置内容是执行/bin/sh ~/.config/.smt。普通用户扫一眼文件名很容易以为这是系统自带的后台服务。Linux 那份还带了一个副作用缺陷检查 crontab 是否已有条目的逻辑写得不严谨重复执行时可能把用户原有的定时任务一起清掉。这类“顺手破坏”在真实事件里很常见也提醒一件事清后门之前先备份 crontab否则取证和恢复都会丢线索。检测信号其实比文件路径更稳。一个非苹果签名的进程去写带com.apple.标签的 plist本身就是异常一个 LaunchAgent 用/bin/sh去跑用户主目录下的隐藏脚本也基本没有正常业务会这么做。规则写在这两条上比枚举文件名更耐用。侦察包里最值钱的是变量名载荷的最后一步是发一个一次性上报包。字段包括主机名、用户名、操作系统、架构、主目录、当前工作目录、git config --global user.email里配置的邮箱以及通过ifconfig.me失败回退api.ipify.org拿到的公网 IP再加上每个阶段是否成功的状态标记。真正值得停下来看的是环境变量那一段。脚本用Object.keys(process.env)取到所有变量名用/KEY|TOKEN|SECRET|PASS|AWS|GCP/i过滤最多取 40 个然后只把名字发出去不取值。为什么不取值因为值的格式、长度、编码各不相同采集和解析成本高还容易在日志里留下明文痕迹。名字的信息量已经够了一台机器上同时出现 AWS 相关的键名、模型服务的 token 名、支付服务的密钥名这台机器在攻击者眼里就是高价值目标值得后续单独投入。这是一个很明确的战术选择先用低成本侦察做资产清点与优先级排序再对少数主机做定向利用。它把“广撒网偷凭据”换成了“先看谁值得偷”对被盯上的团队反而更危险因为后续攻击会更安静、更有针对性。上报通道是明文 HTTPPOST /beacon打到134.209.82.225:8099同时还发一份到127.0.0.1:8099。用明文说明运营者并不担心中间人看到内容因为包里确实没有秘密值本地那一份的用途没有解释脚本本身也没有在本地起监听。上报在初始化后 1.5 秒触发一次响应直接用res.resume()丢弃。防御侧由此多了一条很具体的规则环境变量名也是敏感信息。正常业务很少需要把几十个匹配 KEY、TOKEN、SECRET 的变量名打包外发出现这种行为无论目的地是不是已知恶意 IP都该当事件处理。卸载扩展不等于清干净Cursor 与 Sophos 在通报里都强调了同一句话卸载扩展不会移除后门。原因很简单扩展只是投递器真正落地的是公钥、持久化脚本、隧道进程和临时目录里的私钥文件这四样都在扩展目录之外。清理要按阶段来。第一步杀掉活动隧道结束那个/bin/sh重试循环以及所有连向134.209.82.225的ssh或autossh进程。第二步清持久化macOS 卸载并删除com.apple.sysmond.plistLinux 清掉reboot条目Windows 删掉SysMon计划任务与启动目录脚本再删~/.config下的隐藏文件。第三步清 SSH 状态从authorized_keys里删掉注释为svc-telemetry的公钥按指纹确认删除临时目录里的.stk_*并确认 Remote Login 没有被意外打开。第四步轮换凭据凡是命中KEY|TOKEN|SECRET|PASS|AWS|GCP这组过滤器的变量对应的令牌都按已暴露处理尤其是那台机器确实开启过 SSH 的情况。排查范围可以先用三个硬指标圈定扩展版本是否为 2.1.312 或 2.1.313安装或自动更新时间是否落在 10 月 29 日 09:27 到 20:35 UTC 之间主机上是否存在~/.config/.smt或~/.config/smt.cmd。三者任意一条命中就进事件响应流程而不是只做一次卸载。如果要做哈希比对Phorion 公布了三个可以直接用的值被篡改的extension.js的 SHA-256 是ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15种到受害者机器上的公钥指纹是SHA256:0Z6RDF64Do2nwQic8a7kkfDLNNk4IiK2U2Rci4htk隧道用的公钥指纹是SHA256:KOAU57ZG6u3MG4WwzPzPX2O5YEe9qdOC1Bl6MVyDQ。给扩展包做静态体检80 行可运行的审计脚本上面这些行为特征有一个共同点它们都必须出现在扩展的入口脚本里否则跑不起来。这就给了我们一个成本很低的检查点——拿到 .vsix 之后先做静态体检读 manifest、扫入口脚本、算哈希、按权重打分再决定这个包能不能进环境。下面这段代码是完整可运行的只依赖标准库。它把authorized_keys写入、SSH 开启、反向隧道、持久化、环境变量枚举、密钥名过滤、IP 字面量、beacon 路径、child_process九类信号做成规则表命中即累加权重最后给出pass、review、block三档结论并用退出码 0 或 3 交给 CI 判断。#!/usr/bin/env python3 vsix_audit.py — VSIX 扩展包静态体检manifest、入口脚本、持久化与网络指标。 import hashlib import json import re import sys import zipfile from pathlib import Path SIGNALS [ (ssh_key_write, 40, rauthorized_keys), (ssh_enable, 25, rremotelogin|systemctl\senable\s(sshd|ssh)|Add-WindowsCapability), (reverse_tunnel, 40, r-R\s\*:0:localhost:22|autossh|ExitOnForwardFailure), (persistence, 30, rLaunchAgents|com\.apple\.[a-z]\.plist|reboot|schtasks|Startup\\\\sysmon), (env_enum, 20, rObject\.keys\(\s*process\.env\s*\)), (secret_filter, 20, rKEY\|TOKEN\|SECRET\|PASS\|AWS\|GCP), (ip_literal, 15, r\b(?:\d{1,3}\.){3}\d{1,3}\b), (beacon_path, 15, r/beacon|:8099), (child_process, 10, rchild_process), ] PINNED { anthropic.claude-code: , } def sha256_bytes(data: bytes) - str: return hashlib.sha256(data).hexdigest() def audit(vsix: Path) - dict: findings, js_files [], [] with zipfile.ZipFile(vsix) as zf: names zf.namelist() manifest json.loads(zf.read(extension/package.json).decode(utf-8)) for name in names: if not name.endswith((.js, .cjs, .mjs)): continue raw zf.read(name) text raw.decode(utf-8, ignore) entry {file: name, sha256: sha256_bytes(raw)[:16]} js_files.append(entry) for rule, weight, pattern in SIGNALS: hits re.findall(pattern, text) if hits: findings.append({rule: rule, weight: weight, file: name, hits: len(hits), sample: str(hits[0])[:48]}) prefix_iife bool(re.match(r\s*[;!]?\s*\(\s*function\s*\(, zf.read( next(n for n in names if n.endswith(.js))).decode(utf-8, ignore))) score min(100, sum(f[weight] for f in findings)) ident f{manifest.get(publisher)}.{manifest.get(name)} pinned PINNED.get(ident) verdict block if score 60 else (review if score 30 else pass) return { package: ident, version: manifest.get(version), main: manifest.get(main), activationEvents: manifest.get(activationEvents, []), vsix_sha256: sha256_bytes(vsix.read_bytes()), hash_pinned: bool(pinned) and pinned sha256_bytes(vsix.read_bytes()), js_entry_prefixed_iife: prefix_iife, js_files: js_files, score: score, verdict: verdict, findings: findings, } def main() - int: target Path(sys.argv[1]) report audit(target) print(json.dumps(report, ensure_asciiFalse, indent2)) return 0 if report[verdict] pass else 3 if __name__ __main__: sys.exit(main())用法只有一条命令python vsix_audit.py 某个扩展.vsix。我本地用两个合成包验证过一个只有正常命令注册的包得到score 0、verdict pass、退出码 0另一个按本次事件的行为特征构造的包九条规则全部命中得到score 100、verdict block、退出码 3。CI 里直接按退出码拦截即可不需要人读 JSON。三个设计点值得说明。第一哈希按字节算不按版本号算。版本号可以被重新发布同一个 2.1.313 既可能是干净的也可能是被改过的只有vsix_sha256能区分。第二只看 manifest 不够。本次恶意包把注入代码放在入口文件顶部manifest 里的发布者、名称、激活事件全都正常所以脚本必须打开main指向的脚本看内容还要检查入口文件是否以立即执行函数开头。第三权重集中在上链的关键动作上。写authorized_keys、开反向隧道各 40 分持久化 30 分环境变量枚举与密钥名过滤各 20 分child_process只给 10 分。单个child_process在很多正常扩展里都有不该单独判死但写 SSH 授权文件这件事几乎没有正当用途。它也有明确边界。字符串匹配会被压缩、混淆、拼接、编码绕过动态加载的代码也扫不到静态体检只能当第一道筛子不能替代沙箱运行与主机侧的文件完整性监控。真正稳的组合是静态扫描挡掉已知形态沙箱限制扩展宿主能碰的路径主机侧盯住~/.ssh与持久化目录的写入。规则表还可以再加一层指纹黑名单把本次公布的三枚哈希与两枚公钥指纹写进PINNED之外的黑名单字典命中直接判block不参与打分。打分适合发现新变种黑名单适合拦住已知样本两者不冲突一个是泛化能力一个是确定性。把信任从“发布者”换成“字节”这次事件的根因不在代码而在权限谁能发布谁就能改所有人的机器。所以防御的重心也应该放在分发与更新链上而不是只盯着恶意代码特征。第一件事是关掉自动更新或至少改成手动确认。Cursor 默认自动更新可以在设置里改。代价是每次更新都要点一下收益是把“11 小时无人察觉”变成“更新前有人看一眼版本号”。对个人开发者这是一分钟设置对团队这是策略。第二件事是版本与哈希双锁。版本锁定不够因为同一版本号可以被重新发布要在 CI 或内部制品库里锁vsix_sha256装包前比对不一致就拒绝。上面那段脚本里的hash_pinned字段就是给这一步留的接口。第三件事是收紧发布权限本身。市场侧的账号被盗是这次事件的核心问题团队侧能做的是给扩展发布账号开双因素、限制可发布人员、发布走双人复核、保留每次发布的产物哈希与审计日志。市场方能轮换密钥、重建索引团队能做的就是把“谁能推一版到所有人机器上”这个问题回答清楚。第四件事落在主机侧。把~/.ssh、~/Library/LaunchAgents、用户 crontab、Windows 启动目录纳入文件完整性监控出口侧盯住非常规出站比如开发机主动连陌生 IP 的 22 端口、或者往 8099 这类端口发明文 JSON。这些信号与具体恶意样本无关换一个攻击者照样有效。第五件事是凭据处置的触发条件。以前大家默认“值没泄露就不用轮换”这次要改只要变量名被外发过就按该机器上的凭据已暴露处理。因为名字的作用是给攻击者排序排序之后就是定向窃取时间差可能只有几天。留下的三个判断先把事实收拢一下2026 年 10 月 29 日 09:27 到 20:35 UTCCursor 扩展市场里的Anthropic.claude-code2.1.312 与 2.1.313 被植入 SSH 后门最多 5000 名用户受影响默认自动更新是扩散主因卸载扩展不能清除后门。第二个判断关于影响面。代码里两个平台的实现缺陷让开启 SSH 这一步在多数主机上失败这确实降低了“被直接登录”的概率但没有降低“公钥已写入、持久化已落地、侦察包已发出”的事实。评估影响时要按最坏情况算只要那台机器开着 SSH后门就是可用的。第三个判断关于“官方”这两个字。官方 ID、官方市场、官方签名这三件事同时成立时仍然可能有 11 小时的窗口被人利用。官方证明的是发布者身份不是这一次发布的字节内容。把这两者分开才谈得上供应链安全。如果只记一个动作就记这个先查版本再清后门最后轮换凭据。顺序不能反因为只轮换不清理等于把新钥匙交给一个还站在屋里的人。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询