
CTF चुनौती जो Remix/React Router Express में CVE-2025-31137 की प्रतिकृति बनाती है। प्रशासक फ़्लैग खोजने के लिए सर्वर-साइड भेद्यता का शोषण करना सीखें।
@zhero___ के व्यक्तिगत ब्लॉगपोस्ट में उनके राइटअप को पढ़ने के बाद मैंने इस CTF को बनाने का निर्णय लिया ताकि यह जान सकूं कि चीजें कैसे काम करती हैं, और उसके बाद मैंने इसे उन सभी के साथ साझा करने का फैसला किया जो इस कमजोरी का शोषण करना सीखना चाहते हैं। मैंने [email protected] संस्करण के साथ CTF बनाने की कोशिश की, लेकिन जब मैंने @remix-run/express की जांच की, तो मैंने देखा कि उन्होंने कोड को पैच कर दिया है, और मैंने उनके GitHub से कमजोर कोड को कॉपी-पेस्ट किया, इसलिए आपको @remix-run/express पैकेज के कोड को नीचे बताए अनुसार बदलना होगा।
आपको व्यवस्थापक पृष्ठ में झंडा ढूंढना होगा, और एप्लिकेशन के अन्य भाग कार्यात्मक नहीं हैं, इसलिए केवल व्यवस्थापक पृष्ठ पर ध्यान केंद्रित करें और झंडा ढूंढें। साथ ही, मैं यह अद्भुत राइटअप "https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path" पढ़ने की सलाह देता हूं जानने के लिए कि शोधकर्ता ने यह बग कैसे पाया, और आप कोड भी पढ़ सकते हैं और पता लगा सकते हैं कि चीजें कैसे काम करती हैं।
प्रोजेक्ट को सेट अप करने के लिए इन चरणों का पालन करें:
git clone https://github.com/pouriam23/vulnerability-in-Remix-React-Router-CVE-2025-31137-.git
cd vulnerability-in-Remix-React-Router-CVE-2025-31137-
सुनिश्चित करें कि आपके पास pnpm स्थापित है, फिर चलाएं:
pnpm install
निम्नलिखित फ़ाइल की सामग्री को बदलें:
/my-remix-app/node_modules/@remix-run/express/dist/server.js
नीचे दिए गए कोड से, और फ़ाइल का नाम बदलकर:
server.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();
}
}