CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2025-61686
9.10.04%

Cookie Monster's Crumbs: Breaking React Router Session Storage

Alon Barad
Alon Barad
Software Engineer

Feb 18, 2026·7 min read·8 visits

PoC Available

Executive Summary (TL;DR)

If you aren't signing your session cookies in React Router or Remix, attackers can use `../` sequences in their session ID to traverse the filesystem. This allows arbitrary file writes (session saving) and reads (session loading), potentially leading to system compromise.

A critical path traversal vulnerability in React Router and Remix's `createFileSessionStorage` allows attackers to read and write arbitrary files on the server by manipulating unsigned session cookies.

The Hook: Convenience vs. Security

We all love 'Developer Experience'. It's that warm, fuzzy feeling when a library just works out of the box without requiring you to configure a Redis cluster or a PostgreSQL database just to store a user's dark mode preference. React Router (and by extension, Remix) offers createFileSessionStorage for exactly this reason. It's the 'easy mode' for session management: store the session data as JSON files on the local disk. Simple, right?

Here is the catch: Easy mode often comes with easy exploits. The architecture assumes that the session ID—the key used to locate that JSON file on disk—is a trusted, randomly generated UUID. But in the world of web security, assuming trust is the first step toward a catastrophic incident.

CVE-2025-61686 is the result of that assumption meeting reality. If a developer configures this storage mechanism without enabling cookie signing (a distinct possibility in 'dev' or 'proof-of-concept' environments that inevitably crawl their way into production), the application blindly trusts the cookie provided by the user. And when an application trusts user input to construct file paths, we get to play one of my favorite games: Path Traversal.

The Flaw: Trusting the User's Map

The vulnerability lies deep within packages/react-router-node/sessions/fileStorage.ts. The logic is deceivingly simple: the server receives a cookie containing a session ID, and it needs to find the corresponding file on the disk to read the user's data.

Under normal circumstances, the workflow looks like this:

  1. Server receives Cookie: session=1234-uuid-5678.
  2. Server looks in ./sessions/.
  3. Server reads ./sessions/1234-uuid-5678.

However, the code responsible for generating that path was missing a crucial check. It essentially performed a raw concatenation (or path.join) of the session directory and the session ID. It didn't verify that the resulting path was still inside the intended directory.

If the application is configured with unsigned cookies (i.e., no secrets array provided to the storage factory), the server accepts whatever string the client sends as the session ID. An attacker doesn't send a UUID; they send a directory traversal payload. When the server processes ../../../../tmp/evil, it obediently steps out of the session sandbox and touches the filesystem wherever the attacker points.

The Code: The Smoking Gun

Let's look at the logic that caused the headache. While the actual source code involves a few layers of abstraction, the root cause boils down to how getFile handled the ID.

The Vulnerable Logic: In versions prior to 7.9.4, the code treating the ID was essentially pass-through. It took the dir (the folder where sessions live) and the id (from the cookie) and smashed them together.

// Conceptual vulnerable implementation
function getFile(dir: string, id: string) {
  // No validation that 'id' is just a filename
  return path.join(dir, id);
}

The Fix: The patch introduced in version 7.9.4 (and Remix 2.17.2) doesn't just blindly join paths. It likely validates that the ID does not contain path separators or resolves the path to ensure it's still contained within the root directory. A robust fix usually looks something like this:

// Secure implementation pattern
function getFile(dir: string, id: string) {
  if (id.includes(path.sep) || id.includes("..")) {
     throw new Error("Invalid session ID");
  }
  return path.join(dir, id);
}

The fix forces the application to treat the Session ID as a strictly alphanumeric token, effectively neutralizing the traversal characters.

The Exploit: crafting the crumbs

Exploiting this requires a specific configuration: Unsigned Cookies. If the application uses createCookieSessionStorage with a secrets array, the exploit fails because the server will try to verify the signature of our malicious payload, fail, and discard it before it ever reaches the file system logic. But if secrets is missing... it's game on.

Step 1: The Payload We want to traverse out of the ./sessions directory. Let's say we want to write a file to /tmp/pwned. Our raw Session ID would be: ../../../../tmp/pwned

Step 2: Encoding React Router and Remix don't store raw strings in cookies; they typically Base64 encode them. We need to mimic their encoding scheme (standard Base64 or URL-safe Base64 depending on the exact version/helper used).

# Generate the Base64 payload
# The quotes are important because the cookie value is JSON serialized string
echo -en '"../../../../tmp/pwned"' | base64
# Output: ILi4vLi4vLi4vLi4vdG1wL3B3bmVkIg==

Step 3: The Attack We simply inject this into the browser or curl request.

curl -v http://localhost:3000/login \
  -H "Cookie: session=ILi4vLi4vLi4vLi4vdG1wL3B3bmVkIg=="
  -d "username=admin&password=password"

The Result: When the server processes the login and attempts to set the session data (e.g., { userId: 1 }), it serializes that object to JSON and writes it to the path derived from our ID. The server effectively executes: fs.writeFile('/tmp/pwned', '{"userId":1}')

We have achieved Arbitrary File Write. While we can't easily control the content (it's JSON serialized session data), we can create files or corrupt existing files that the web user has permission to write to.

The Impact: Why 9.1?

You might be thinking, "So I can write a JSON file to /tmp. Big deal." But a CVSS score of 9.1 (Critical) isn't handed out for minor inconveniences. The impact here is surprisingly broad.

1. Arbitrary File Write (Integrity): If the application runs with high privileges (bad practice, but common), an attacker could overwrite critical configuration files. Even with low privileges, an attacker could potentially overwrite files used by other parts of the application, leading to application instability or behavior modification.

2. Denial of Service (Availability): An attacker could script the creation of millions of files (inode exhaustion) or overwrite critical operational locks, crashing the service.

3. Arbitrary File Read (Confidentiality - with caveats): If the attacker points the traversal to a file that happens to be valid JSON (or if the parser is lenient), the application might load that file into the session object. The attacker could then potentially retrieve that data if the application reflects session details back in the UI (e.g., "Welcome, [User Name]"). While less likely than the write vector, it's a valid path for exfiltration.

The Fix: Sign Your Cookies!

The remediation is two-fold. First, update your dependencies. The maintainers patched this in @react-router/node version 7.9.4 and @remix-run/node version 2.17.2. The patch ensures that session IDs are sanitized before hitting the disk.

Second, and more importantly: Never run sessions without signatures.

Even with the patch, unsigned cookies allow clients to pick their own session IDs. This can lead to Session Fixation attacks. Always provide a secrets array when creating your storage:

const { getSession, commitSession, destroySession } = createFileSessionStorage({
  dir: "./sessions",
  cookie: {
    name: "__session",
    // The secret sauce (literally)
    secrets: ["s3cr3t-k3y-that-should-be-in-env"],
    secure: true,
  },
});

When secrets are present, the server signs the cookie value. If an attacker modifies the cookie to include ../../, the signature validation will fail, and the request will be rejected before the file system is ever touched. It's cryptographic access control for your storage layer.

Official Patches

Remix/React RouterOfficial GHSA Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H
EPSS Probability
0.04%
Top 89% most exploited

Affected Systems

@react-router/node@remix-run/node@remix-run/deno

Affected Versions Detail

Product
Affected Versions
Fixed Version
@react-router/node
Remix
7.0.0 - 7.9.37.9.4
@remix-run/node
Remix
< 2.17.22.17.2
@remix-run/deno
Remix
< 2.17.22.17.2
AttributeDetail
CWE IDCWE-22
CVSS Score9.1 (Critical)
Attack VectorNetwork
Attack ComplexityLow
Privileges RequiredNone
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1005Data from Local System
Collection
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

Known Exploits & Detection

GitHubVendor Advisory and reproduction steps

Vulnerability Timeline

Vulnerability reported to Shopify/Remix team
2026-01-08
CVE-2025-61686 Published
2026-01-10
Patched versions released (7.9.4 / 2.17.2)
2026-01-10

References & Sources

  • [1]GHSA Advisory
  • [2]NIST NVD Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.