Vite信息泄露 | CVE-2025-46565 复现&研究

0x0 背景介绍

Vite 是一个用于 JavaScript 的前端工具框架。在 6.3.4、6.2.7、6.1.6、5.4.194.5.14 之前的版本中,项目根目录下被文件匹配模式拒绝的文件内容可能会被返回给浏览器。只有明确将Vite 开发服务器暴露给网络的应用程序(使用 --hostserver.host 配置选项)会受到影响。只有位于项目根目录下且被文件匹配模式拒绝的文件可能被绕过。server.fs.deny 可以包含匹配文件的模式(默认情况下,它包括 .env.env.**.{crt,pem} 等模式)。这些模式能够通过使用斜杠和点(/.)的组合来绕过 root 下的文件。该问题已在 6.3.4、6.2.7、6.1.6、5.4.194.5.14 版本中修复。

0x1 环境搭建

1. Ubuntu24环境复现

  • 另存为install.sh并赋予执行权限chmod +x install.sh
bash 复制代码
#!/bin/bash

echo "[+] 开始搭建 Vite CVE-2025-46565 漏洞复现环境"
echo "[+] 目标:使用 vite@6.3.3 复现路径遍历漏洞(/.env/.)"

# 步骤 1:创建项目目录
echo -e "\n[+] 步骤 1/7:创建项目目录"
mkdir -p vite-poc && cd vite-poc
echo "[+] 已创建目录:$(pwd)"

# 步骤 2:初始化 npm 项目
echo -e "\n[+]  步骤 2/7:初始化 npm"
npm init -y
echo "[+] package.json 已生成"

# 步骤 3:安装易受攻击的 Vite 版本
echo -e "\n[+]  步骤 3/7:安装 vite@6.3.3(受影响版本)"
npm install vite@6.3.3
echo "[+]  Vite 6.3.3 安装完成"

# 步骤 4:创建 index.html
echo -e "\n[+] 步骤 4/7:创建 index.html"
cat > index.html << 'EOF_HTML'
<!DOCTYPE html>
<html>
  <body>
    <h1>Vite CVE-2025-46565 PoC</h1>
    <p>如果能通过 /%2E.env%2E/ 读取 .env 文件,则存在漏洞。</p>
  </body>
</html>
EOF_HTML
echo "✅ index.html 创建成功"

# 步骤 5:创建多个敏感文件(模拟真实项目)
echo -e "\n[+] 步骤 5/7:创建敏感配置文件"

# .env
cat > .env << 'EOF_ENV'
SECRET_KEY=ThisIsASecretPassword
DB_PASS=root123
API_TOKEN=abc123xyz
EOF_ENV

# .env.local
cat > .env.local << 'EOF_LOCAL'
DATABASE_URL=mysql://devuser:devpass@localhost/devdb
JWT_SECRET=local_jwt_secret_456
EOF_LOCAL

# ca.pem
cat > ca.pem << 'EOF_PEM'
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIUJfZ1R03qI3Q6OzJd9P/XuXjNvRMwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEAwwHY2EtY2VydDAeFw0yNTAxMDEwMDAwMDBaFw0zNTAxMDEwMDAwMDBa
MBIxEDAOBgNVBAMMB2NhLWNlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQC9JZt1...(模拟证书内容)
-----END CERTIFICATE-----
EOF_PEM

# .npmrc
echo "[+]//registry.npmjs.org/:_authToken=abcdefg12345" > .npmrc

echo "[+] 敏感文件已创建:.env, .env.local, ca.pem, .npmrc"

# 步骤 6:创建 vite.config.js
echo -e "\n[+] 步骤 6/7:创建 vite.config.js(启用 --host)"
cat > vite.config.js << 'EOF_CONFIG'
export default {
  server: {
    host: '0.0.0.0',
    port: 5173
  }
}
EOF_CONFIG
echo "[+] vite.config.js 配置完成(允许外部访问)"

# 步骤 7:添加 dev 脚本
echo -e "\n[+] 步骤 7/7:添加启动脚本"
npm pkg set scripts.dev="vite"
echo "[+] 启动脚本已添加:npm run dev"

# 结束提示
echo -e "\n🎉 环境搭建完成!"
echo "📌 接下来你可以:"
echo ""
echo "   1️⃣ cd进入项目目录,启动 Vite 服务器:"
echo "      npx vite --host"
echo ""
echo "💡 注意:保持服务器运行,不要关闭当前终端。"

2. 启动成功

0x2 漏洞复现

手动复现步骤

  • 正常请求文件时,被拒绝的响应

  • 绕过访问

  • 多姿势绕过访问

  • 示例其它文件

YML检测

bash 复制代码
https://github.com/Kai-One001/cve-/blob/main/Vite-CVE-2025-46565.yml

复现流量特征 (PACP)

0、读取文件

2、多姿势绕过

0x3 漏洞原理分析

确定入口:静态文件服务

  • Vite 是一个开发服务器,处理静态文件的逻辑在:/packages/vite/src/node/server/middlewares/static.ts

  • 进去后查询发现ensureServingAccess,它是静态资源访问控制的核心入口函数,负责判断请求路径是否允许被服务

  • 并且它调用了 isFileServingAllowed(url, server)来进行权限校验

ts 复制代码
export function ensureServingAccess(...) {
  if (isFileServingAllowed(url, server)) {
    return true
  }
  if (isFileReadable(cleanUrl(url))) {
    // → 返回 403 错误页面
  } else {
    next() // → 继续中间件链
  }
  return false
}
  • 如果 isFileServingAllowed 返回 false,但文件不可读(不存在),就 next() → 404
  • 如果文件可读,就返回403错误页面

调用 ensureServingAccess 的上下文

  • 在静态中间件中,路径解析完成后立即调用 ensureServingAccess 做安全检查:
ts 复制代码
#静态文件服务中间件
    const resolvedPathname = redirectedPathname || pathname
    let fileUrl = path.resolve(dir, removeLeadingSlash(resolvedPathname))
    if (resolvedPathname.endsWith('/') && fileUrl[fileUrl.length - 1] !== '/') {
      fileUrl = withTrailingSlash(fileUrl)
    }
    //  安全检查:是否允许访问此路径?
    if (!ensureServingAccess(fileUrl, server, res, next)) {
      return
    }
  • 使用 path.resolve() 将 URL 路径转换为本地文件系统绝对路径
  • 调用 ensureServingAccess 进行访问控制
  • 若检查失败则直接中断流程,防止敏感文件泄露
  • 顺着看ensureServingAccess具体实现方法:
ts 复制代码
export function ensureServingAccess(
  url: string,
  server: ViteDevServer,
  res: ServerResponse,
  next: Connect.NextFunction,
): boolean {
  if (isFileServingAllowed(url, server)) {
    return true  // 允许访问
  }
  // ... 否则返回 403
}
  • 所以真正决定是否允许访问的是:isFileServingAllowed

核心权限判断:isFileServingAllowed

ts 复制代码
export function isFileLoadingAllowed(
  config: ResolvedConfig,
  filePath: string,
): boolean {
  const { fs } = config.server

  if (!fs.strict) return true

  if (config.fsDenyGlob(filePath)) return false

  if (config.safeModulePaths.has(filePath)) return true

  if (fs.allow.some((uri) => isUriInFilePath(uri, filePath))) return true

  return false
}
  • 它把 url(如 /.env/.)转换成 filePath
  • 转换函数是:fsPathFromUrl(url)
  • 然后传给 isFileLoadingAllowed做最终判断
  • config.fsDenyGlob(filePath) 使用 picomatch进行模式匹配。

fsDenyGlob 的生成:动态匹配函数

通过 IDE 全局搜索发现,发现一共就俩个地方使用,另一个就是packages/vite/src/node/config.ts

ts 复制代码
     fsDenyGlob: picomatch(
      // matchBase: true does not work as it's documented
      // https://github.com/micromatch/picomatch/issues/89
      // convert patterns without `/` on our side for now
      server.fs.deny.map((pattern) =>
        pattern.includes('/') ? pattern : `**/${pattern}`,
      ),
      {
        matchBase: false,
        nocase: true,
        dot: true,
      },
  • 使用 picomatch 库,对于不含 / 的模式会转换为 **/pattern
  • 所有不包含 / 的模式(如 .env)会被自动前缀为 **/,变成 **/.env

回头看fsPathFromUrl 方法

查询文件发现是通告导入包进来的

ts 复制代码
import {
  fsPathFromId,
  fsPathFromUrl,
  isFileReadable,
  isImportRequest,
  isInternalRequest,
  isParentDirectory,
  isSameFileUri,
  normalizePath,
  removeLeadingSlash,
  urlRE,
} from '../../utils'
  • 也就是packages/vite/src/node/utils.ts中,查询fsPathFromUrl函数
ts 复制代码
export function fsPathFromUrl(url: string): string {
  return fsPathFromId(cleanUrl(url))
}
export function fsPathFromId(id: string): string {
  const fsPath = normalizePath(
    id.startsWith(FS_PREFIX) ? id.slice(FS_PREFIX.length) : id,
  )
  return fsPath[0] === '/' || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`
}
  • 路径会被 normalizePath() 处理,使用 path.posix.normalize()
ts 复制代码
export function normalizePath(id: string): string {
  return path.posix.normalize(isWindows ? slash(id) : id)
}
  • 使用 path.posix.normalize() 对路径进行标准化
  • 在 Windows 上会先转为正斜杠 /
  • 不会移除末尾的 /./.. 中的点目录

总结:漏洞成因链

bash 复制代码
用户请求 → /.env/.
          ↓
     path.resolve() → 解析为本地路径
          ↓
   fsPathFromUrl() → 转换为文件系统路径
          ↓
 normalizePath() → 得到 "/.env/."(未清理末尾 .)
          ↓
config.fsDenyGlob("/.env/.") → picomatch('**/.env') 不匹配
          ↓
isFileLoadingAllowed → 返回 true(误判为合法)
          ↓
Vite 尝试读取 "/.env/." → 实际访问 .env 内容或返回 403

0x4 修复建议

修复方案

  1. 升级到安全版本:6.3.4、6.2.7、6.1.6、5.4.19 和 4.5.14版本中已修复
  2. 临时缓解措施:
    部署 Web 应用防火墙(WAF)监控异常请求
    server.fs.allow 配置仅包含必要的目录
    开发服务器不应暴露在公网
    轮换API、数据库密钥

免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

相关推荐
赵谨言4 小时前
基于python二手车价值评估系统的设计与实现
大数据·开发语言·经验分享·python
Metaphor6927 小时前
图片转PPT:用Java高效处理PowerPoint的秘籍
经验分享
赵谨言1 天前
基于python大数据的城市扬尘数宇化监控系统的设计与开发
大数据·开发语言·经验分享·python
会飞的小蛮猪1 天前
ELK运维之路(Logstash-插件)
运维·经验分享·elk·elasticsearch·logstash
LaughingZhu1 天前
Product Hunt 每日热榜 | 2025-10-25
人工智能·经验分享·搜索引擎·产品运营
TeleostNaCl2 天前
OpenWrt | 实现限制只有指定设备才能访问 luci 和 使用 SSH 等方式管理设备的方法
网络·经验分享·ssh·智能路由器
哈喽哈喽哈喽~2 天前
推送文件到github ---服务器关键配置内容
运维·服务器·经验分享·github