Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
vulnerability-in-Remix-React-Router-CVE-2025-31137- — CTF挑战复现了Remix/React Router Express中的CVE-2025-31137。学习利用服务器端漏洞来找到管理员flag。 | Kitploit
工具/GitHubGitHub/pouriam23/vulnerability-in-remix-react-router-cve-2025-31137-
漏洞分析漏洞利用Web应用程序漏洞利用Web安全CTF学习与教育
GitHubpouriam23/vulnerability-in-remix-react-router-cve-2025-31137-

vulnerability-in-Remix-React-Router-CVE-2025-31137-

CTF挑战复现了Remix/React Router Express中的CVE-2025-31137。学习利用服务器端漏洞来找到管理员flag。

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
查看仓库
141年前尚未审核
分享

概述

在阅读了 @zhero___ 在其个人博客文章中的分析后,我决定构建这个 CTF 来学习其工作原理,之后我决定将其分享给任何想学习如何利用此漏洞的人。我尝试使用 [email protected] 版本构建 CTF,但当我检查 @remix-run/express 时,注意到他们已修补了代码,于是我从他们的 GitHub 复制粘贴了存在漏洞的代码,因此你必须按照下面的说明修改 @remix-run/express 包的代码。

目标

你必须在管理员页面中找到 flag,应用程序的其他部分无法正常工作,所以只需专注于管理员页面并找到 flag。同时,我建议你阅读这篇精彩的文章:"https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path",了解研究人员如何发现这个漏洞,你也可以阅读代码,了解其工作原理。

快速开始

按照以下步骤设置项目:

1. 克隆仓库

root@kitploit:~
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. 安装依赖

确保已安装 pnpm,然后运行:

root@kitploit:~
pnpm install

3. 将 Remix Express 服务器代码替换为存在漏洞的版本(zhero 发现漏洞时的版本)

将以下文件的内容替换掉:

root@kitploit:~
/my-remix-app/node_modules/@remix-run/express/dist/server.js

替换为下面的代码,并将文件名重命名为:

root@kitploit:~
server.ts

📄 server.ts

root@kitploit:~
// 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();
  }
}
下载工具