Vite开发服务器任意文件读取漏洞分析复现(CVE-2025-31486)
MeteorKai 漏洞分析 481浏览 · 2025-04-10 11:33

其实上文(CVE-2025-31125)中所说的未公开POC是此处31486的一种。

svg这种是可以打CVE-2025-31486的。在上一篇CVE的复现中我已经提到了,这里就不说了。

6.2.5修补了上述漏洞。

但是这里还有一种方法:

bypass ensureServingAccess权限校验

这里先给出poc:

在修复CVE-2024-45811时有引入ensureServingAccess对传入的url进行校验,这个poc就绕过了ensureServingAccess的校验从而使用raw语法成功读取到了文件

image.png


接下来我们看看流程!跟进一下ensureServingAccesss,只要ensureServingAccess为true即可绕过!

那么我们要如何使其return一个true呢?发现只有一个isFileServingAllowed方法有机会!

此处的关键点其实在于:

跟进一下fsPathFromUrl看看

先清洗一下url再fsPathFromId一下。

cleanUrl会使用正则处理url中的第一个?或者#开始的到结尾的部分进行移除。接着我们再看看fsPathFromId,它的作用是将一个模块 ID(通常来自 import 语句)转换为文件系统路径(filesystem path)。

这里解释一下normalizePath

将路径中的反斜杠 \ 替换为正斜杠 /(Windows 兼容)。

移除多余的 /(如 a//ba/b)。

解析相对路径符号(如 ./../)。

接下来我们看看isFileLoadingAllowed函数,它主要用于 检查某个文件路径是否被允许加载

fs.strict:若为 false(非严格模式),直接允许加载所有文件(跳过后续检查)。

fsDenyGlob:一个匹配函数,检查文件路径是否命中黑名单(如敏感文件 **/.env**/node_modules/**)。

safeModulePaths:一个 Set 集合,存放明确允许加载的路径(如项目源码目录)。

fs.allow:一个数组,包含允许的路径规则(如 ['/src', '/public'])。

isUriInFilePath:辅助函数,检查 filePath 是否在某个允许的路径下(如 /src/utils.js 匹配 /src)。

我们便是在此处返回true的!因为我们自己的根目录是被允许的!

但是通过目录验证后,后续的处理逻辑却又是使用我们原始的url进行处理。

跟一边poc

这里什么都没remove掉,继续跟

然后进入到if判断中:

这里在rawRE.test便已经为true,那么就会进入ensureServingAccess判断。这时我们的url为/@fs/usr/src/node_modules/vite/?/../../../../../etc/passwd?import&?raw

经过fsPathFromUrlcleanurl后url变成了/@fs/usr/src/node_modules/vite/fsPathFromId可以理解为一种规范化,url还是上面那样。

接着进入了isFileLoadingAllowed方法,我们关注下面这段代码:

检查某个文件路径是否被允许加载,我们跟进allow看看:

image.png


继续跟进allow

看这段allow: raw?.fs?.allow ?? [searchForWorkspaceRoot(root)]

定义允许访问的文件系统路径列表(安全限制)。

raw?.fs?.allow:如果用户显式配置了 raw.fs.allow,则直接使用。

默认值 [searchForWorkspaceRoot(root)]:若未配置,则自动搜索工作区根目录并设为唯一允许路径。

searchForWorkspaceRoot(root):Vite 内部函数,从项目根目录(root)向上查找包含 pnpm-workspace.yamllerna.json 的目录(适用于 monorepo)。

返回的路径会被添加到 fs.allow 数组中(例如 ['/Users/your/project'])。

那么网站的路径就是默认允许的!这也是我们的poc需要用网站路径的原因,他不能是随机的!!!

然后我们就通过了ensureServingAccess,url以最初的poc继续往下走:

http://192.168.79.128:5173/@fs/usr/src/node_modules/vite/?/../../../../../etc/passwd?import&?raw

毋庸置疑,通过了isImportRequest,然后进入:

去除掉了import

变成了/@fs/usr/src/node_modules/vite/?/../../../../../etc/passwd??raw

然后进入了

这里没有变化,继续往下看。接着进入了transformRequest,再进入doTransform,然后又进行了一次removeTimestampQuery,没有变化。

获得了id

接着进入loadAndTransform

然后我们进入load()

通过了rawRE.test,没有防御目录穿越,导致了任意文件读取!

0 条评论
某人
表情
可输入 255