支持本站 — 捐款将帮助我们持续运营

目标: 1000 元,已筹: 1000

100.0%

POC详情: 53803e78cf6e672f5a81a479aeec19d759e54a62

来源
关联漏洞
标题:react-router 环境问题漏洞 (CVE-2025-31137)
Description:react-router是Remix开源的一个 React 的声明式路由。 react-router 7.0.0版本至7.4.0版本存在环境问题漏洞,该漏洞源于Remix或React Router的Express适配器允许通过URL路径名伪造请求URL。
介绍
## overview
after reading write up of @zhero___ in his personal blogpost i decide to build this CTF to learn how things work and after that i decide to share it with anybody who wants to learn how exploit this vulnerability , i try to make CTF with remix@2.16.0 version but when i check @remix-run/express i notice that they patch the code and i copy paste the vulnereable code from their github so you must change the code of @remix-run/express package as i say below 

## Goal 
you must find the flag in admin page and other part of application are dosn't functional so only focus on admin page and find the flag also i recommend you read this amazing writeup "https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path" and learn how researcher find this bug , also you can read the code and find out how things work

## Getting Started

Follow these steps to set up the project:

### 1. Clone the repository

```bash
git clone https://github.com/pouriam23/vulnerability-in-Remix-React-Router-CVE-2025-31137-.git
cd vulnerability-in-Remix-React-Router-CVE-2025-31137-
```

### 2. Install dependencies

Make sure you have [pnpm](https://pnpm.io/) installed, then run:

```bash
pnpm install
```

### 3. change Remix Express Server code to vulnerable version ( when zhero find bug )  

Replace the contents of the following file:

```
/my-remix-app/node_modules/@remix-run/express/dist/server.js
```

with the code below and rename the file to:

```
server.ts
```

### 📄 server.ts

```ts
// IDK why this is needed when it's in the tsconfig..........
// YAY PROJECT REFERENCES!
/// <reference lib="DOM.Iterable" />

import type * as express from "express";
import type { AppLoadContext, ServerBuild } from "@remix-run/node";
import {
  createRequestHandler as createRemixRequestHandler,
  createReadableStreamFromReadable,
  writeReadableStreamToWritable,
} from "@remix-run/node";

/**
 * A function that returns the value to use as `context` in route `loader` and
 * `action` functions.
 *
 * You can think of this as an escape hatch that allows you to pass
 * environment/platform-specific values through to your loader/action, such as
 * values that are generated by Express middleware like `req.session`.
 */
export type GetLoadContextFunction = (
  req: express.Request,
  res: express.Response
) => Promise<AppLoadContext> | AppLoadContext;

export type RequestHandler = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) => Promise<void>;

/**
 * Returns a request handler for Express that serves the response using Remix.
 */
export function createRequestHandler({
  build,
  getLoadContext,
  mode = process.env.NODE_ENV,
}: {
  build: ServerBuild | (() => Promise<ServerBuild>);
  getLoadContext?: GetLoadContextFunction;
  mode?: string;
}): RequestHandler {
  let handleRequest = createRemixRequestHandler(build, mode);

  return async (
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    try {
      let request = createRemixRequest(req, res);
      let loadContext = await getLoadContext?.(req, res);

      let response = await handleRequest(request, loadContext);

      await sendRemixResponse(res, response);
    } catch (error: unknown) {
      next(error);
    }
  };
}

export function createRemixHeaders(
  requestHeaders: express.Request["headers"]
): Headers {
  let headers = new Headers();

  for (let [key, values] of Object.entries(requestHeaders)) {
    if (values) {
      if (Array.isArray(values)) {
        for (let value of values) {
          headers.append(key, value);
        }
      } else {
        headers.set(key, values);
      }
    }
  }

  return headers;
}

export function createRemixRequest(
  req: express.Request,
  res: express.Response
): Request {
  let [, hostnamePort] = req.get("X-Forwarded-Host")?.split(":") ?? [];
  let [, hostPort] = req.get("host")?.split(":") ?? [];
  let port = hostnamePort || hostPort;
  let resolvedHost = `${req.hostname}${port ? `:${port}` : ""}`;
  let url = new URL(`${req.protocol}://${resolvedHost}${req.originalUrl}`);

  let controller: AbortController | null = new AbortController();
  let init: RequestInit = {
    method: req.method,
    headers: createRemixHeaders(req.headers),
    signal: controller.signal,
  };

  if (req.method !== "GET" && req.method !== "HEAD") {
    init.body = createReadableStreamFromReadable(req);
    (init as { duplex: "half" }).duplex = "half";
  }

  res.on("finish", () => (controller = null));
  res.on("close", () => controller?.abort());

  return new Request(url.href, init);
}

export async function sendRemixResponse(
  res: express.Response,
  nodeResponse: Response
): Promise<void> {
  res.statusMessage = nodeResponse.statusText;
  res.status(nodeResponse.status);

  for (let [key, value] of nodeResponse.headers.entries()) {
    res.append(key, value);
  }

  if (nodeResponse.headers.get("Content-Type")?.match(/text\/event-stream/i)) {
    res.flushHeaders();
  }

  if (nodeResponse.body) {
    await writeReadableStreamToWritable(nodeResponse.body, res);
  } else {
    res.end();
  }
}
```


文件快照

[4.0K] /data/pocs/53803e78cf6e672f5a81a479aeec19d759e54a62 ├── [4.0K] app │   ├── [ 561] entry.client.tsx │   ├── [3.9K] entry.server.tsx │   ├── [ 991] root.tsx │   ├── [4.0K] routes │   │   └── [5.9K] _index.tsx │   └── [ 180] tailwind.css ├── [1.4K] package.json ├── [ 81] postcss.config.js ├── [4.0K] public │   ├── [ 17K] favicon.ico │   ├── [ 78K] logo-dark.png │   └── [5.8K] logo-light.png ├── [5.0K] README.md ├── [1.4K] server.js ├── [ 467] tailwind.config.ts ├── [ 734] tsconfig.json └── [ 519] vite.config.ts 3 directories, 15 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮件到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对 POC 代码进行快照,为了长期维护,请考虑为本地 POC 付费/捐赠,感谢您的支持。