Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
vulnerability-in-Remix-React-Router-CVE-2025-31137- — Remix/React Router Express에서 CVE-2025-31137을 재현한 CTF 챌린지입니다. 서버 측 취약점을 악용하여 관리자 플래그를 찾는 방법을 배우세요. | Kitploit
도구/GitHubGitHub/pouriam23/vulnerability-in-remix-react-router-cve-2025-31137-
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityCTFLearning & Education
GitHubpouriam23/vulnerability-in-remix-react-router-cve-2025-31137-

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

Remix/React Router Express에서 CVE-2025-31137을 재현한 CTF 챌린지입니다. 서버 측 취약점을 악용하여 관리자 플래그를 찾는 방법을 배우세요.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
11년 전아직 검토되지 않음

개요

@zhero___의 개인 블로그 게시물에 있는 writeup을 읽은 후, 어떻게 작동하는지 배우기 위해 이 CTF를 만들기로 결정했고, 그 후 이 취약점을 악용하는 방법을 배우고 싶은 사람과 공유하기로 했습니다. [email protected] 버전으로 CTF를 만들려고 했지만, @remix-run/express를 확인했을 때 코드가 패치된 것을 발견하고 GitHub에서 취약한 코드를 복사하여 붙여넣었으므로 아래 설명대로 @remix-run/express 패키지의 코드를 변경해야 합니다.

목표

admin 페이지에서 플래그를 찾아야 하며, 애플리케이션의 다른 부분은 작동하지 않으므로 admin 페이지에만 집중하여 플래그를 찾으세요. 또한 이 훌륭한 writeup "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();
  }
}
도구 다운로드