八百六十三个点赞、五百一十三条评论,10 月 30 日 Hacker News 头条不属于任何新模型,而属于一个版本号:Claude Code for VS Code 的 2.1.313。macOS 安全厂商 Phorion 当天发布通告,确认这个挂在 Open VSX 市场上、页面里写着 Anthropic 官方字样的扩展包被注入了持久化 SSH 后门,攻击链覆盖 macOS、Linux、Windows 三个平台。Cursor 方面给出的统计是受影响用户至多五千人。数字本身不算惊人,刺眼的是渠道:投毒者没有碰任何野生下载站,而是把后门直接塞进了开发者最不设防的地方,也就是编辑器自带的扩展市场。对每天让 AI 编程助手读写自己代码的人来说,这件事值得逐帧看清楚。
事件回放:48 小时里发生了什么
10 月 29 日,Cursor 在自己的扩展市场里发现了被篡改的 Claude Code 扩展,版本号 2.1.313。这个扩展在 Open VSX 上的列表页显示发布者为 Anthropic,实际提交账号却是一个名为 igorkofman 的账户。Open VSX 是 Cursor、Windsurf 等 VS Code 系编辑器共用的扩展注册中心,开发者在这些编辑器里搜索安装扩展,命中的就是它。
Cursor 的处置动作很快:下架扩展、重建整个市场、协助把攻击者的服务器下线。Anthropic 随后也把 NPM 和 VS Code 官方市场里 2.1.313 的对应版本一并撤掉。处置过程里有个细节值得记住:恶意包的部分安装步骤因为需要管理员权限而失败,但它收集信息和部署持久化的动作在普通用户权限下就完成了,权限不足并没有拦住核心破坏面。10 月 30 日,Phorion 公布完整逆向报告,把后门的工作原理、持久化手法和入侵指示器全部摊开,同一天这个话题冲上 HN 头条,评论区里最多的感慨是:原来编辑器扩展一直是个没人看的权限黑洞。
需要明确的一点是,这是一起市场渠道的包替换事件,不是 Anthropic 官方构建管线被攻破。但站在受害机器的视角,这个区别毫无意义:扩展以 IDE 和当前用户的权限运行,加载即执行,不需要任何额外交互。
后门藏在哪里:扩展包的第一行
被篡改的 extension.js 有一个非常朴素的特点:恶意代码不是混淆在几万行压缩代码中间,而是作为一个立即执行函数直接拼在整个压缩包的最前面。后面的部分是完全正常的扩展代码,Claude Code 的字符串、标准的 activate 和 deactivate 导出、界面逻辑一应俱全。
这个设计意味着两件事。第一,Node 加载这个文件时,前置的恶意函数立刻执行,根本等不到扩展的 activate 回调被调用,用户不点任何按钮、不开任何面板, payload 照样跑。第二,它只引用了 child_process、fs、os、path、http、crypto 这几个 Node 标准模块,没有任何外部依赖,也就没有可疑的网络安装行为可供静态扫描发现。
对审计来说这是个提醒:扩展安全扫描如果只查依赖树和安装脚本,对这种前置注入是盲的。文件哈希才是硬证据,Phorion 给出的带毒包 SHA-256 是 ecb58cb2 开头的一串,任何环境里的 2.1.313 都可以对着它验。更实际的一点在于团队管理:大多数公司能说出生产服务器上跑了哪些依赖版本,却说不出五十个开发者的编辑器里装着哪些扩展。这次事件之后,扩展清单应该和依赖清单一样进入资产台账,否则下一次通告来的时候,你连排查范围都画不出来。
四段攻击链:从 authorized_keys 到反向隧道
整个 payload 的目标是长期交互式访问,逻辑分成四段,每一段都很克制。
第一段是铺 SSH 入口。脚本确保用户家目录下的 .ssh 存在,然后把一个攻击者控制的 Ed25519 公钥追加进 authorized_keys,目录权限设为 0700,文件权限设为 0600。公钥的注释字符串是 svc-telemetry,伪装成遥测服务。有意思的是它判重时查的是注释字符串而不是密钥指纹,所以取证时不能只按注释搜,要按指纹对。
第二段是确保 SSH 服务在跑。Linux 上尝试用 systemctl 拉起 sshd,失败再退到 service 和 init.d。macOS 上先查 Remote Login 状态,报 Off 就用 sudo -n 去开。Windows 上尝试用 PowerShell 安装并启动 OpenSSH Server。这一段没有任何提权漏洞,非交互的 sudo -n 在大多数机器上直接失败。
第三段是反向隧道,也是整条链的核心,逻辑大致如下:
// 隧道私钥落盘:文件名取主机名与家目录拼接值 MD5 的前 10 位
var kf = path.join(TMP, ".stk_" + crypto.createHash("md5")
.update(String(os.hostname()) + HOME).digest("hex").slice(0, 10));
// 反向端口转发:让攻击者服务器动态分配端口,回连受害机 22 端口
var RF = "'*:0:localhost:22'";
// -N 只维持转发不执行命令,BatchMode 保证静默,每 60 秒重试
// 优先用 autossh,没有就用系统 ssh,pgrep 防重复起隧道
机器主动向外连到 134.209.82.225 的 22 端口,用一个注释为 svc-telemetry-tun 的内置私钥认证。因为连接方向是出站的,防火墙通常不会拦。攻击者想进来时,从自己服务器上动态分配的端口走既有隧道回到受害机的 22 端口,再用第一段种下的公钥登录。由于隧道进程是脱离父进程独立运行的,关掉 IDE 它照样活着。
第四段是侦察信标。脚本收集主机名、用户名、系统平台、家目录、全局 Git 邮箱,用 ifconfig.me 查公网 IP,再收集至多 40 个匹配 KEY、TOKEN、SECRET、PASS、AWS、GCP 的环境变量的名字,注意只是名字不是值。这份清单通过明文 HTTP POST 到攻击者服务器的 8099 端口,让运营者快速判断哪台机器上有云凭证值得优先动手。
救场的 bug:权限检查写反了之后
这份恶意代码的质量并不高明,两个实现错误客观上减了灾。
macOS 上的错误最戏剧化。脚本用普通权限跑 systemsetup -getremotelogin 想知道 SSH 是否开着,但这个命令在标准用户下根本不返回状态,而是打印一行"你需要管理员权限"然后以退出码 0 正常结束。脚本拿到这段错误文本,用正则去匹配 Off,匹配不上,于是得出结论:SSH 已经开了,跳过开启步骤。它把"我没权限查"当成了"已经是开着的"。结果就是,在一台本来没开 Remote Login 的 Mac 上,公钥和持久化都种下了,唯独 22 端口没人听,攻击者连回来只能撞一堵墙。当然这不是防线,机器上只要本来开着 SSH,或者之后有人打开,后门立刻完整生效。
Windows 上更离谱:负责执行命令的辅助函数把 shell 路径写死成了 /bin/sh,这个路径在标准 Windows 上不存在,整条服务安装链直接空转。Linux 侧也有一个 bug, crontab 判重逻辑写错了,重复运行可能把用户已有的 cron 任务整个清掉,属于损人不利己的破坏。
这几个 bug 的教训不在攻击者粗心,而在于防御方不能把运气当控制措施。入侵是否成功,这次取决于受害机器 Remote Login 的开关状态和攻击者一个正则表达式的写法,这两样都不在任何人的安全策略里。一个健康的防护体系应该假设对方代码没有 bug,假设隧道一定建得起来,然后在这个前提下问:我的机器上有没有进程该写 authorized_keys 却不该写?有没有 LaunchAgent 在执行隐藏目录里的脚本?这两个问题,Phorion 报告里给出的检测思路都能回答,普通团队用 osquery 之类的开源工具也能搭出基础版本。下一次投毒者把正则写对,整批 Mac 就是另一个故事。
为什么卸载扩展清不干净
Cursor 在事件后给出的清理建议里有一条很容易被忽略:只卸载扩展是不够的。后门在第一次运行时就把自己复制到了扩展目录之外,扩展没了,持久化机制还在。三平台的残留物对比如下:
还有一条容易漏的:SSH 日志里通过隧道进来的成功登录,来源地址会显示成 localhost。如果机器上过这条链,要查的不只是隧道有没有建起来,还有攻击者有没有真的登进来过,登进来过就意味着那台机器上所有该用户可触达的凭证都要轮换。
十分钟自查脚本
不管用不用 Claude Code,只要用过 Cursor 或 Windsurf 装过扩展,下面这段脚本值得跑一遍,覆盖上面所有主机侧痕迹:
#!/bin/bash
# Claude Code 2.1.313 后门自查:只读检查,不改动任何文件
echo "[1] authorized_keys 可疑公钥"
grep -n "svc-telemetry" ~/.ssh/authorized_keys 2>/dev/null || echo " 干净"
echo "[2] 隧道私钥临时文件"
ls -la /tmp/.stk_* "$TMPDIR"/.stk_* 2>/dev/null || echo " 干净"
echo "[3] 持久化脚本"
ls -la ~/.config/.smt ~/.config/.smtk ~/.config/smt.cmd 2>/dev/null || echo " 干净"
echo "[4] macOS LaunchAgent"
ls -la ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null || echo " 干净"
echo "[5] 活着的隧道进程"
ps aux | grep -E "autossh|ssh .*-R" | grep -v grep || echo " 干净"
echo "[6] 外联痕迹"
lsof -i -n 2>/dev/null | grep "134.209.82.225" || echo " 干净"
echo "[7] 已安装的扩展版本"
ls -d ~/.cursor/extensions/anthropic.claude-code-2.1.313* ~/.vscode/extensions/anthropic.claude-code-2.1.313* 2>/dev/null || echo " 未装 2.1.313"
任何一项有输出,就按上一节的清单走完整清理,然后从一台确定干净的机器上轮换这台机器碰过的所有凭证。顺序很重要:在被污染的机器上改密码等于把新密码也交出去。
扩展市场的信任账该重算了
把视角拉远,这起事件不是孤例。去年 11 月就有攻击者用恶意的 Solidity 扩展在 Open VSX 上投毒偷开发者数据,Forever Security 上个月还演示过把一份带指令的文件丢进 Downloads 目录,就能诱导在那里打开的 AI 编程助手自己去装包、改配置。甚至连模型服务本身都会出卖你:Cloudflare 上个月披露,它托管的 GLM 模型因为 SGLang 的错误处理 bug 和配置错配,会把一个客户的长提示词缓存内容吐给下一个请求。攻击面正在从依赖库转向编辑器本身,理由很简单:扩展有用户的全部权限,审查强度却远低于 npm。
对比之下,几个环节的落差很明显。包管理器社区这几年陆续上了构建溯源和签名,而多数扩展市场仍停留在账号加扫描的阶段,发布者身份核验形同虚设,一个陌生账号能顶着官方名义出现在列表页。编辑器侧也缺一层默认隔离,扩展进程能直接读写 authorized_keys 这件事本身,在移动端操作系统里早就是不可想象的。
对普通开发者,可行的动作是把扩展也纳入资产管理:装了什么、什么版本、哈希对不对,像锁依赖一样锁住。对团队,把 Open VSX 的扩展安装收敛到白名单,配合终端侧的文件遥测盯住 SSH 配置目录,是性价比最高的两步。扩展市场这套信任体系是 Web2 时代留给 AI 编程时代的遗产,这次只是第一次有人用力踹了一脚,门轴已经响了。
本文由 AI图鉴自动抓取并翻译,版权归原作者所有;如需查看原文请点下方链接。
On October 30th, the Hacker News headlines did not belong to any new model but to a version number: Claude Code for VS Code 2.1.13. The MacOS safety manufacturer Phorion announced on that day publish, confirming that the extension, which was hanging on the Open VSX market and on the page with the official Anthropic, was injected into the durable SSH back door, with the attack chain covering the macOS, Linux, Windows platforms. Cursor gives statistics of up to 5,000 affected users. The numbers are not surprising per se, but the poignant is the channel: instead of touching any wild download stations, the back door is inserted directly into the developers' least protected place, the extended market with the editor. It's worth looking at every single day for those who let the AI programming assistant read and write his own code.
Incident replay: 48 hours after what happened
On October 29, Cursor discovered the altered Claude Code extension, version 2.1.13, in his own extended market. This list page on Open VSX shows publish as Anthropic, and the actual submission is an account named igorkofman. Open VSX is an extended registration centre shared by editors like Cursor, Windsurf and others, where developers search for extensions and install them.
Cursor's disposal was quick: the shelf was extended, the entire market was rebuilt, and the servers of the attackers were assisted in getting offline. Anthropic then removed the corresponding version of NPM and the VS Code official market in 2.1.13. There is a detail in the disposal process that is worth remembering: the partial installation of the malicious package failed because of the need for a manager, permission, but its gathering of information and deployments was completed under the common user, permission, which did not stop the core damage. On October 30th, Phorion published a complete reverse report that spreads the back door's working principles, persistent cylindrical techniques and intrusion indicators. The same day, the topic hit the headline of the HN, and the most popular impression in the comment area was that the editor's expansion had been an invisible black hole of permission.
It needs to be made clear that this is a replacement of a package from a market channel, not an Anthropic official pipeline that was broken. But from the point of view of the injured machine, the difference is meaningless: the extension runs with IDE and the current user ' s permission, loads and executes without any additional interaction.
Where's the back door? First line of the extension package.
Extension.js has a very simple feature: Malicious codes are not confused in the middle of tens of thousands of lines of compression, but as an immediate execution function, they are spelled directly in front of the entire compression package. The latter part is a fully normal extension code, with Claude Code string, standard activate and deactivate export, interface logic.
This design means two things. First, when Node loads this file, the preset malicious function is executed immediately, without waiting for the extended inactivate callback to be called, without any buttons, without any panel, or payload running. Secondly, it refers only to the standard Node modules of child process, fs, os, path, http, crypto, with no external dependence, and no suspicious network installation for static scanning.
This is a reminder for the audit: the extended security scan is blind if it only relies on trees and installs scripts. The document Hashi is the hard evidence, and Phorion gave the poison bag SHA-256 as a string beginning with anec58cb2, which can be examined in any environment by 2.1.13. What is more practical is team management: most companies can tell which of the dependent versions have run off of the production servers, but not which extensions are in the editors of 50 developers. Following this incident, the expanded list should be entered into the asset desk account on the same basis as the relying list, otherwise you will not be able to draw up the scope of the next circular.
Four attack chains: from authorized keys to reverse tunnel
The overall goal of the Payload is long-term interactive access, with logic divided into four sections, each with restraint.
The first segment is the paving of the SSH entrance. Script ensures that.ssh exists in the user's directory and then adds an attacker-controlled Ed25519 public key to authorized keys, directory permission 0,700 and file permission 0600. The public key comment string is svc-telemetry, disguised as telemetry service. It was interesting that it was looking at note strings rather than key fingerprints when weighing them, so the evidence could not be searched by note, but by fingerprint.
The second is to ensure that SSH services are running. Linux tried ssystemtl to pull sshd, failed again to service and init.d. MacOS first check Remote Login's state, report Off, use sudo-n. Windows tries to install and start OpenSSH Server with PowerShell. There are no power gaps in this section, non-interactive sudo-n has failed directly on most machines.
The third is the reverse tunnel, which is also at the heart of the whole chain, with the following logic:
/ / Tunnel Private Key Dropboard: Filename takes hostnames to the top 10 places of MD5
var kf = path.join(TMP, ".stk_" + crypto.createHash("md5")
.update(String(os.hostname()) + HOME).digest("hex").slice(0, 10));
/ / Reverse port forwarding: allow the aggressor server to dynamically allocate the port, back to the victim 22 port
var RF = "'*:0:localhost:22'";
/-N maintains only forwarding orders not to execute, BatchMode ensures silence, every 60 seconds retry
/ / Prioritize autoossh, use ssh, pgrep to avoid repeat tunnel
The machine voluntarily connects to the twenty-two (22) port of 134.209.82.25, authenticating with an internal private key for svc-telemetry-tun with a note. Because the connection direction is out of the station, the firewall usually does not stop. When the attackers want to come in, take the existing tunnel from the dynamically distributed port on their server back to the 22nd port of the victim machine and log in with the first public key. Since the tunnel process is run independently of the father process, turn off the IDE it's still alive.
The fourth is the detection beacon. Scripts collect the names of up to 40 environmental variables matching KEY, TOKEN, SECRET, PASS, AWS, GCP, and note that names are not values. This list allows operators to quickly determine which machine has a cloud certificate that deserves priority action by specifying HTTP POST to the 8099 port of the attacker server.
SOS: permission.
The quality of this malicious code is not high, and two errors made objectively reduce the disaster.
The most dramatic mistake on macos. Script runs systemsetup-getremotelogin with regular permission to find out if SSH is on, but this command does not return to status at all under standard users, but prints a line "You Need Administrator permission" and ends normally with exit code zero. The script got the wrong text, matched Off with the regular rule, and couldn't match, so it concluded that SSH had gone, skipping the opening step. It's "I don't have a permission" as it's already open." As a result, on a script Mac that did not open Remote Login, the public key and permanence were planted, except for 22 ports where no one listened, and the attackers came back with only one wall. It's not a line of defense, of course. The back door is fully operational as long as it's open or someone opens it.
Windows is more eccentric: The auxiliary function responsible for the execution of the command has killed the shell path to /bin/sh, which does not exist on standard Windows, and the whole service installation chain is empty. There is also a bug on the side of Linux, crontab which is wrong about the logic, and repeating can wipe out the existing cron task of the user, which is damaging to others.
The lesson of these bugs is not that the attackers are careless, but that the defensive cannot use luck as a control measure. The success of the invasion depended this time on the state of the switch on the injured machine Remote Login and on the way in which the attackers had a regular expression, both of which were not part of any human security strategy. A healthy protective system should assume that the other side has no bug, that the tunnel must be built, and then ask, in that context, whether there is a process on my machine that should be written but not written? Is there a script for LaunchAgent in the hidden directory? These two questions are answered in the Phorion report, and the basic version can be developed by the common team using open source tools like osquery. Next time the drug thrower writes the right thing, the whole Mac is another story.
Why isn't the unloaded extension clean?
Cursor's clean-up proposal after the event included one that was easily overlooked: it was not enough to unload the extension. The back door copied itself to the extended directory at the time of its first operation, without extension and with a mechanism for sustainability. The residues of the three platforms are compared as follows:
There's another easy one: a successful entry through a tunnel in the SSH log, the source address will be shown as localhost. If the machine had passed this chain, not only had the tunnel been built, but also had the assailant actually entered it, it would have meant that all the documents that the machine could reach were rotated.
Check your script for 10 minutes.
With or without Claude Code, as long as it has been extended with Cursor or Windsurf, the following script is worth running over, covering all the side tracks on it:
#!/bin/bash
# Claude Code 2.1.313 Backdoor self-checking: read-only check without changing any documents
"[1] suspected keys"
{\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cHFFFFFF}{\cH00FF00} {\cH00FF00} {\cH00FF000 {\cH00} {\cH30303030303030D3F4} {\cH3030D3D3D3D3CH
echo "[2] tunnel private key temporary file"
ls-la/tmp/.stk * "$TMPDIR"/.stk *2>/dev/null|| cho "clean"
Echo, "[3] Enduring scripts."
ls-la ~/.config/.smt ~/.config/.smtk ~/.config/smt.cmd 2>/dev/null echo
echo "[4] macOS LaunchAgent"
~/LaunchAgents/com.apple.sysmond.plist 2>/dev/null|| "clean"
Echo, "[5] Living tunnel process."
It's "clean."
Echo, "[6] Outreach marks."
I'm sof-i-n 2>/dev/null
echo "[7] installed extension"
ls-d ~/.cursor/extensions/antropic.claude-code-2.1.313* ~/.vscode/extensions/antropic.claude-code-2.1.313* 2>/dev/null|| cho "unloaded 2.1.313"
Any item that has an output is cleared as a whole by following the list in the previous section and then rotates all the documents that the machine touches from a certain clean machine. Order is important: changing the code on contaminated machines is tantamount to handing over the new code.
It's time to go back to expanding the market.
It is not an isolated incident to take away the perspective. Last November, when the attackers expanded their drug-spoker data on Open VSX with malicious Solidity, Forever Security demonstrated last month that throwing a document with instructions into the Downloads directory could induce the AI programming assistant who opened it there to pack and reconfigure himself. Even model service itself sells you: Cloudflare revealed last month that its hosting GLM model would throw out the content of a client's long promptcache to the next request because of SGLang's misprocessing bug and configuration mismatch. The face of the attack is moving from relying on the library to the editor itself, for the simple reason that extending all permission with users is much less intense than npm.
In contrast, the gap between several links is clear. The package manager community has been building tracers and signatures over the years, and most expansion markets are still in the account-scanning phase, publish Zer identification is non-existent, and a strange account number can appear on the list page under the official name. There is also a default isolation on the side of the editor, so the extension process can read and write directly about the matter itself, which is already unimaginable in the mobile operating system.
For ordinary developers, it would be feasible to include the extension in asset management: what was installed, what version, Hash, right, locked like a lock dependence. For the team, the extension installation of Open VSX is reduced to a white list, which is the two highest value for money, with remote sensing of the SSH configuration directory on the side of the terminal. The extension of the market system is a legacy of the Web2 era for the AI programming age, and this is only the first time that someone has kicked a leg, and the axes have gone off.
Auto-fetched and translated by AI Atlas. All rights belong to the original author.