zen-ai-pentest 2.2.0__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api/auth.py +61 -7
- api/csrf_protection.py +286 -0
- api/main.py +77 -11
- api/rate_limiter.py +317 -0
- api/rate_limiter_v2.py +586 -0
- autonomous/ki_analysis_agent.py +1033 -0
- benchmarks/__init__.py +12 -142
- benchmarks/agent_performance.py +374 -0
- benchmarks/api_performance.py +479 -0
- benchmarks/scan_performance.py +272 -0
- modules/agent_coordinator.py +255 -0
- modules/api_key_manager.py +501 -0
- modules/benchmark.py +706 -0
- modules/cve_updater.py +303 -0
- modules/false_positive_filter.py +149 -0
- modules/output_formats.py +1088 -0
- modules/risk_scoring.py +206 -0
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/METADATA +134 -289
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/RECORD +23 -9
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/WHEEL +0 -0
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/entry_points.txt +0 -0
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {zen_ai_pentest-2.2.0.dist-info → zen_ai_pentest-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KI-gesteuerter Autonomous Analysis Agent für Zen AI Pentest
|
|
3
|
+
|
|
4
|
+
Integriert kimi-cli für intelligente Analyse mit:
|
|
5
|
+
- ReAct Pattern: Reason → Act → Observe → Reflect
|
|
6
|
+
- State Machine: IDLE → PLANNING → EXECUTING → OBSERVING → REFLECTING → COMPLETED
|
|
7
|
+
- Memory System: Short-term, long-term, and context window management
|
|
8
|
+
- Tool Orchestration: Automatic selection and execution of 20+ pentesting tools
|
|
9
|
+
- Self-Correction: Retry logic and adaptive planning
|
|
10
|
+
- Human-in-the-Loop: Optional pause for critical decisions
|
|
11
|
+
|
|
12
|
+
Author: Zen AI Pentest Framework v2.1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import subprocess
|
|
19
|
+
import uuid
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from enum import Enum, auto
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
26
|
+
|
|
27
|
+
# Configure logging
|
|
28
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AgentState(Enum):
|
|
33
|
+
"""State Machine für den Agenten-Loop."""
|
|
34
|
+
IDLE = auto()
|
|
35
|
+
PLANNING = auto()
|
|
36
|
+
EXECUTING = auto()
|
|
37
|
+
OBSERVING = auto()
|
|
38
|
+
REFLECTING = auto()
|
|
39
|
+
COMPLETED = auto()
|
|
40
|
+
ERROR = auto()
|
|
41
|
+
PAUSED = auto()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AnalysisPhase(Enum):
|
|
45
|
+
"""Phasen der Sicherheitsanalyse."""
|
|
46
|
+
RECONNAISSANCE = "reconnaissance"
|
|
47
|
+
SCANNING = "scanning"
|
|
48
|
+
ENUMERATION = "enumeration"
|
|
49
|
+
VULNERABILITY_ANALYSIS = "vulnerability_analysis"
|
|
50
|
+
EXPLOITATION = "exploitation"
|
|
51
|
+
POST_EXPLOITATION = "post_exploitation"
|
|
52
|
+
REPORTING = "reporting"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ReActStep:
|
|
57
|
+
"""Ein einzelner ReAct Step: Thought → Action → Observation."""
|
|
58
|
+
step_number: int
|
|
59
|
+
thought: str
|
|
60
|
+
action: str
|
|
61
|
+
action_params: Dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
observation: str = ""
|
|
63
|
+
reflection: str = ""
|
|
64
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
65
|
+
success: bool = True
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
68
|
+
return {
|
|
69
|
+
'step': self.step_number,
|
|
70
|
+
'thought': self.thought,
|
|
71
|
+
'action': self.action,
|
|
72
|
+
'observation': self.observation,
|
|
73
|
+
'reflection': self.reflection,
|
|
74
|
+
'timestamp': self.timestamp.isoformat(),
|
|
75
|
+
'success': self.success
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class AgentMemory:
|
|
81
|
+
"""Multi-Layer Memory System für den Agenten."""
|
|
82
|
+
session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
83
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
84
|
+
goal: str = ""
|
|
85
|
+
target: str = ""
|
|
86
|
+
scope: Dict[str, Any] = field(default_factory=dict)
|
|
87
|
+
|
|
88
|
+
# Short-term Memory (Session-basiert, flüchtig)
|
|
89
|
+
short_term: List[Dict[str, Any]] = field(default_factory=list)
|
|
90
|
+
max_short_term: int = 100
|
|
91
|
+
|
|
92
|
+
# Long-term Memory (persistiert auf Disk)
|
|
93
|
+
long_term_file: Optional[Path] = None
|
|
94
|
+
|
|
95
|
+
# Context Window für LLM/KI (begrenzte Größe)
|
|
96
|
+
context_window: List[ReActStep] = field(default_factory=list)
|
|
97
|
+
max_context_window: int = 10
|
|
98
|
+
|
|
99
|
+
# Findings und Ergebnisse
|
|
100
|
+
findings: List[Dict[str, Any]] = field(default_factory=list)
|
|
101
|
+
execution_history: List[Dict[str, Any]] = field(default_factory=list)
|
|
102
|
+
|
|
103
|
+
def __post_init__(self):
|
|
104
|
+
if self.long_term_file is None:
|
|
105
|
+
logs_dir = Path("logs/ki_agent_sessions")
|
|
106
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
self.long_term_file = logs_dir / f"session_{self.session_id}.json"
|
|
108
|
+
|
|
109
|
+
def add_react_step(self, step: ReActStep) -> None:
|
|
110
|
+
"""Fügt einen ReAct Step zum Context Window hinzu."""
|
|
111
|
+
self.context_window.append(step)
|
|
112
|
+
if len(self.context_window) > self.max_context_window:
|
|
113
|
+
self.context_window = self.context_window[-self.max_context_window:]
|
|
114
|
+
self._persist_long_term()
|
|
115
|
+
|
|
116
|
+
def add_finding(self, finding: Dict[str, Any]) -> None:
|
|
117
|
+
"""Fügt einen Security Finding hinzu."""
|
|
118
|
+
finding['timestamp'] = datetime.now().isoformat()
|
|
119
|
+
finding['id'] = str(uuid.uuid4())
|
|
120
|
+
self.findings.append(finding)
|
|
121
|
+
self._persist_long_term()
|
|
122
|
+
|
|
123
|
+
def add_to_short_term(self, entry: Dict[str, Any]) -> None:
|
|
124
|
+
"""Fügt Eintrag zum Kurzzeit-Memory hinzu."""
|
|
125
|
+
entry['timestamp'] = datetime.now().isoformat()
|
|
126
|
+
self.short_term.append(entry)
|
|
127
|
+
if len(self.short_term) > self.max_short_term:
|
|
128
|
+
self.short_term = self.short_term[-self.max_short_term:]
|
|
129
|
+
|
|
130
|
+
def get_context_for_ki(self) -> str:
|
|
131
|
+
"""Erstellt formatierten Kontext für KI-Analyse."""
|
|
132
|
+
context_parts = [
|
|
133
|
+
f"=== KI Analysis Context ===",
|
|
134
|
+
f"Goal: {self.goal}",
|
|
135
|
+
f"Target: {self.target}",
|
|
136
|
+
f"Session: {self.session_id}",
|
|
137
|
+
f"Findings: {len(self.findings)}",
|
|
138
|
+
f"\n=== Recent ReAct Steps ==="
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
for step in self.context_window[-5:]:
|
|
142
|
+
context_parts.append(f"\n[Step {step.step_number}] {step.timestamp.strftime('%H:%M:%S')}")
|
|
143
|
+
context_parts.append(f" Thought: {step.thought[:150]}...")
|
|
144
|
+
context_parts.append(f" Action: {step.action}")
|
|
145
|
+
context_parts.append(f" Success: {step.success}")
|
|
146
|
+
|
|
147
|
+
if self.findings:
|
|
148
|
+
context_parts.append(f"\n=== Key Findings ===")
|
|
149
|
+
for f in self.findings[-5:]:
|
|
150
|
+
severity = f.get('severity', 'unknown')
|
|
151
|
+
name = f.get('name', 'Unknown')
|
|
152
|
+
context_parts.append(f" [{severity.upper()}] {name}")
|
|
153
|
+
|
|
154
|
+
return "\n".join(context_parts)
|
|
155
|
+
|
|
156
|
+
def _persist_long_term(self) -> None:
|
|
157
|
+
"""Speichert wichtige Daten persistent."""
|
|
158
|
+
try:
|
|
159
|
+
data = {
|
|
160
|
+
'session_id': self.session_id,
|
|
161
|
+
'created_at': self.created_at.isoformat(),
|
|
162
|
+
'goal': self.goal,
|
|
163
|
+
'target': self.target,
|
|
164
|
+
'findings': self.findings,
|
|
165
|
+
'react_history': [s.to_dict() for s in self.context_window],
|
|
166
|
+
'last_updated': datetime.now().isoformat()
|
|
167
|
+
}
|
|
168
|
+
with open(self.long_term_file, 'w', encoding='utf-8') as f:
|
|
169
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.warning(f"Could not persist memory: {e}")
|
|
172
|
+
|
|
173
|
+
def load_session(self, session_id: str) -> bool:
|
|
174
|
+
"""Lädt eine vorherige Session."""
|
|
175
|
+
try:
|
|
176
|
+
file_path = Path(f"logs/ki_agent_sessions/session_{session_id}.json")
|
|
177
|
+
if file_path.exists():
|
|
178
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
179
|
+
data = json.load(f)
|
|
180
|
+
self.goal = data.get('goal', '')
|
|
181
|
+
self.target = data.get('target', '')
|
|
182
|
+
self.findings = data.get('findings', [])
|
|
183
|
+
logger.info(f"Session {session_id} loaded with {len(self.findings)} findings")
|
|
184
|
+
return True
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"Failed to load session: {e}")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class KIAnalyzer:
|
|
191
|
+
"""KI-basierter Analyzer mittels kimi-cli Integration."""
|
|
192
|
+
|
|
193
|
+
def __init__(self, model: str = "kimi"):
|
|
194
|
+
self.model = model
|
|
195
|
+
self.logger = logging.getLogger("KIAnalyzer")
|
|
196
|
+
|
|
197
|
+
async def analyze(self, prompt: str, context: str = "", max_tokens: int = 2000) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Führt KI-Analyse durch mittels kimi-cli.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
prompt: Die Hauptanfrage
|
|
203
|
+
context: Zusätzlicher Kontext
|
|
204
|
+
max_tokens: Maximale Antwortlänge
|
|
205
|
+
"""
|
|
206
|
+
full_prompt = f"""{context}
|
|
207
|
+
|
|
208
|
+
=== ANALYSIS REQUEST ===
|
|
209
|
+
{prompt}
|
|
210
|
+
|
|
211
|
+
Provide a structured, actionable response. Be concise but thorough."""
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
# Versuche kimi-cli zu verwenden
|
|
215
|
+
cmd = ["kimi", "-c", full_prompt]
|
|
216
|
+
|
|
217
|
+
process = await asyncio.create_subprocess_exec(
|
|
218
|
+
*cmd,
|
|
219
|
+
stdout=asyncio.subprocess.PIPE,
|
|
220
|
+
stderr=asyncio.subprocess.PIPE
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
stdout, stderr = await asyncio.wait_for(
|
|
224
|
+
process.communicate(),
|
|
225
|
+
timeout=60.0
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if process.returncode == 0:
|
|
229
|
+
response = stdout.decode('utf-8', errors='ignore').strip()
|
|
230
|
+
self.logger.info(f"KI analysis completed ({len(response)} chars)")
|
|
231
|
+
return response
|
|
232
|
+
else:
|
|
233
|
+
error = stderr.decode('utf-8', errors='ignore')[:200]
|
|
234
|
+
self.logger.warning(f"KI cli failed: {error}")
|
|
235
|
+
return self._fallback_analysis(prompt, context)
|
|
236
|
+
|
|
237
|
+
except asyncio.TimeoutError:
|
|
238
|
+
self.logger.warning("KI analysis timed out, using fallback")
|
|
239
|
+
return self._fallback_analysis(prompt, context)
|
|
240
|
+
except FileNotFoundError:
|
|
241
|
+
self.logger.warning("kimi-cli not found, using fallback analysis")
|
|
242
|
+
return self._fallback_analysis(prompt, context)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
self.logger.error(f"KI analysis error: {e}")
|
|
245
|
+
return self._fallback_analysis(prompt, context)
|
|
246
|
+
|
|
247
|
+
def _fallback_analysis(self, prompt: str, context: str) -> str:
|
|
248
|
+
"""Fallback-Analyse wenn KI nicht verfügbar."""
|
|
249
|
+
return f"""[FALLBACK ANALYSIS]
|
|
250
|
+
Based on available data:
|
|
251
|
+
|
|
252
|
+
1. CONTEXT SUMMARY:
|
|
253
|
+
{context[:500]}...
|
|
254
|
+
|
|
255
|
+
2. ANALYSIS:
|
|
256
|
+
The request "{prompt[:100]}..." requires further investigation.
|
|
257
|
+
|
|
258
|
+
3. RECOMMENDATIONS:
|
|
259
|
+
- Review scan results manually
|
|
260
|
+
- Cross-reference findings with CVE database
|
|
261
|
+
- Validate critical vulnerabilities
|
|
262
|
+
- Document all findings for reporting
|
|
263
|
+
|
|
264
|
+
[Note: Full KI analysis unavailable - kimi-cli not installed or timeout]"""
|
|
265
|
+
|
|
266
|
+
async def plan_next_steps(self, memory: AgentMemory, phase: AnalysisPhase) -> List[Dict[str, Any]]:
|
|
267
|
+
"""
|
|
268
|
+
Nutzt KI zur Planung der nächsten Schritte.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Liste von geplanten Aktionen
|
|
272
|
+
"""
|
|
273
|
+
context = memory.get_context_for_ki()
|
|
274
|
+
|
|
275
|
+
prompt = f"""Based on the current security assessment context, plan the next actions for phase: {phase.value}
|
|
276
|
+
|
|
277
|
+
Current Status:
|
|
278
|
+
- Goal: {memory.goal}
|
|
279
|
+
- Target: {memory.target}
|
|
280
|
+
- Findings so far: {len(memory.findings)}
|
|
281
|
+
|
|
282
|
+
Provide 3-5 concrete next steps in this JSON format:
|
|
283
|
+
[
|
|
284
|
+
{{
|
|
285
|
+
"action": "scan_ports|check_vuln|enumerate|exploit_check|report",
|
|
286
|
+
"tool": "nmap|nikto|gobuster|custom",
|
|
287
|
+
"target": "specific target",
|
|
288
|
+
"parameters": {{"port": "80,443", "depth": "deep"}},
|
|
289
|
+
"priority": 1-5,
|
|
290
|
+
"reasoning": "why this step"
|
|
291
|
+
}}
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
Respond ONLY with the JSON array."""
|
|
295
|
+
|
|
296
|
+
response = await self.analyze(prompt, context)
|
|
297
|
+
|
|
298
|
+
# Versuche JSON zu extrahieren
|
|
299
|
+
try:
|
|
300
|
+
# Finde JSON im Response
|
|
301
|
+
start = response.find('[')
|
|
302
|
+
end = response.rfind(']') + 1
|
|
303
|
+
if start >= 0 and end > start:
|
|
304
|
+
json_str = response[start:end]
|
|
305
|
+
plan = json.loads(json_str)
|
|
306
|
+
self.logger.info(f"KI planned {len(plan)} steps")
|
|
307
|
+
return plan
|
|
308
|
+
except Exception as e:
|
|
309
|
+
self.logger.warning(f"Could not parse KI plan: {e}")
|
|
310
|
+
|
|
311
|
+
# Fallback Plan
|
|
312
|
+
return self._generate_fallback_plan(phase, memory.target)
|
|
313
|
+
|
|
314
|
+
def _generate_fallback_plan(self, phase: AnalysisPhase, target: str) -> List[Dict[str, Any]]:
|
|
315
|
+
"""Generiert Fallback-Plan wenn KI-Planung fehlschlägt."""
|
|
316
|
+
plans = {
|
|
317
|
+
AnalysisPhase.RECONNAISSANCE: [
|
|
318
|
+
{"action": "dns_enum", "tool": "dig", "target": target, "priority": 1},
|
|
319
|
+
{"action": "whois", "tool": "whois", "target": target, "priority": 2},
|
|
320
|
+
],
|
|
321
|
+
AnalysisPhase.SCANNING: [
|
|
322
|
+
{"action": "port_scan", "tool": "nmap", "target": target, "parameters": {"ports": "top100"}, "priority": 1},
|
|
323
|
+
{"action": "service_scan", "tool": "nmap", "target": target, "parameters": {"flags": "-sV"}, "priority": 2},
|
|
324
|
+
],
|
|
325
|
+
AnalysisPhase.VULNERABILITY_ANALYSIS: [
|
|
326
|
+
{"action": "vuln_scan", "tool": "nuclei", "target": target, "priority": 1},
|
|
327
|
+
{"action": "web_scan", "tool": "nikto", "target": target, "priority": 2},
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
return plans.get(phase, [{"action": "analyze", "tool": "manual", "target": target, "priority": 1}])
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class ToolExecutor:
|
|
334
|
+
"""Orchestration für 20+ Pentesting Tools."""
|
|
335
|
+
|
|
336
|
+
TOOLS = {
|
|
337
|
+
'nmap': {
|
|
338
|
+
'category': 'scanner',
|
|
339
|
+
'description': 'Network port scanner',
|
|
340
|
+
'command': 'nmap',
|
|
341
|
+
'safe': True
|
|
342
|
+
},
|
|
343
|
+
'nikto': {
|
|
344
|
+
'category': 'web',
|
|
345
|
+
'description': 'Web vulnerability scanner',
|
|
346
|
+
'command': 'nikto',
|
|
347
|
+
'safe': True
|
|
348
|
+
},
|
|
349
|
+
'gobuster': {
|
|
350
|
+
'category': 'web',
|
|
351
|
+
'description': 'Directory/file brute forcer',
|
|
352
|
+
'command': 'gobuster',
|
|
353
|
+
'safe': True
|
|
354
|
+
},
|
|
355
|
+
'nuclei': {
|
|
356
|
+
'category': 'vuln',
|
|
357
|
+
'description': 'Fast vulnerability scanner',
|
|
358
|
+
'command': 'nuclei',
|
|
359
|
+
'safe': True
|
|
360
|
+
},
|
|
361
|
+
'subfinder': {
|
|
362
|
+
'category': 'recon',
|
|
363
|
+
'description': 'Subdomain discovery',
|
|
364
|
+
'command': 'subfinder',
|
|
365
|
+
'safe': True
|
|
366
|
+
},
|
|
367
|
+
'dnsrecon': {
|
|
368
|
+
'category': 'recon',
|
|
369
|
+
'description': 'DNS enumeration',
|
|
370
|
+
'command': 'dnsrecon',
|
|
371
|
+
'safe': True
|
|
372
|
+
},
|
|
373
|
+
'sslscan': {
|
|
374
|
+
'category': 'scanner',
|
|
375
|
+
'description': 'SSL/TLS scanner',
|
|
376
|
+
'command': 'sslscan',
|
|
377
|
+
'safe': True
|
|
378
|
+
},
|
|
379
|
+
'testssl': {
|
|
380
|
+
'category': 'scanner',
|
|
381
|
+
'description': 'Comprehensive SSL tests',
|
|
382
|
+
'command': 'testssl.sh',
|
|
383
|
+
'safe': True
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
def __init__(self, max_retries: int = 3):
|
|
388
|
+
self.max_retries = max_retries
|
|
389
|
+
self.logger = logging.getLogger("ToolExecutor")
|
|
390
|
+
self.execution_history: List[Dict[str, Any]] = []
|
|
391
|
+
|
|
392
|
+
async def execute(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
393
|
+
"""
|
|
394
|
+
Führt eine Aktion mit Self-Correction aus.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
action: Action Dict mit tool, target, parameters
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Ergebnis der Ausführung
|
|
401
|
+
"""
|
|
402
|
+
tool_name = action.get('tool', 'nmap')
|
|
403
|
+
target = action.get('target', '')
|
|
404
|
+
parameters = action.get('parameters', {})
|
|
405
|
+
|
|
406
|
+
# Retry Logic
|
|
407
|
+
for attempt in range(1, self.max_retries + 1):
|
|
408
|
+
try:
|
|
409
|
+
self.logger.info(f"Executing {tool_name} (attempt {attempt}/{self.max_retries})")
|
|
410
|
+
|
|
411
|
+
result = await self._execute_tool(tool_name, target, parameters)
|
|
412
|
+
|
|
413
|
+
if result['success']:
|
|
414
|
+
self.execution_history.append({
|
|
415
|
+
'tool': tool_name,
|
|
416
|
+
'target': target,
|
|
417
|
+
'attempt': attempt,
|
|
418
|
+
'success': True,
|
|
419
|
+
'timestamp': datetime.now().isoformat()
|
|
420
|
+
})
|
|
421
|
+
return result
|
|
422
|
+
|
|
423
|
+
# Wenn nicht erfolgreich, warte und versuche es erneut
|
|
424
|
+
if attempt < self.max_retries:
|
|
425
|
+
wait_time = 2 ** attempt # Exponential backoff
|
|
426
|
+
self.logger.info(f"Retrying in {wait_time}s...")
|
|
427
|
+
await asyncio.sleep(wait_time)
|
|
428
|
+
|
|
429
|
+
except Exception as e:
|
|
430
|
+
self.logger.error(f"Execution error: {e}")
|
|
431
|
+
if attempt == self.max_retries:
|
|
432
|
+
return {
|
|
433
|
+
'success': False,
|
|
434
|
+
'error': str(e),
|
|
435
|
+
'tool': tool_name,
|
|
436
|
+
'target': target
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return {'success': False, 'error': 'Max retries exceeded'}
|
|
440
|
+
|
|
441
|
+
async def _execute_tool(self, tool: str, target: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
442
|
+
"""Führt ein spezifisches Tool aus."""
|
|
443
|
+
|
|
444
|
+
# Simulierte Tool-Ausführung (für Demo)
|
|
445
|
+
if tool == 'nmap':
|
|
446
|
+
return await self._simulate_nmap(target, params)
|
|
447
|
+
elif tool == 'nikto':
|
|
448
|
+
return await self._simulate_nikto(target, params)
|
|
449
|
+
elif tool == 'subfinder':
|
|
450
|
+
return await self._simulate_subfinder(target, params)
|
|
451
|
+
else:
|
|
452
|
+
# Generic simulation
|
|
453
|
+
await asyncio.sleep(0.5)
|
|
454
|
+
return {
|
|
455
|
+
'success': True,
|
|
456
|
+
'tool': tool,
|
|
457
|
+
'target': target,
|
|
458
|
+
'findings': [
|
|
459
|
+
{'type': 'info', 'message': f'{tool} scan completed on {target}'}
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async def _simulate_nmap(self, target: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
464
|
+
"""Simuliert Nmap Scan."""
|
|
465
|
+
await asyncio.sleep(1.0)
|
|
466
|
+
|
|
467
|
+
ports = params.get('ports', 'top100')
|
|
468
|
+
flags = params.get('flags', '-sS')
|
|
469
|
+
|
|
470
|
+
# Simulierte Ergebnisse basierend auf Target
|
|
471
|
+
if '192.168.1.1' in target:
|
|
472
|
+
open_ports = [22, 80, 443, 53, 139, 445]
|
|
473
|
+
elif 'localhost' in target or '127.0.0.1' in target:
|
|
474
|
+
open_ports = [22, 80, 3306, 5432, 8080]
|
|
475
|
+
else:
|
|
476
|
+
open_ports = [80, 443]
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
'success': True,
|
|
480
|
+
'tool': 'nmap',
|
|
481
|
+
'target': target,
|
|
482
|
+
'command': f'nmap {flags} -p {ports} {target}',
|
|
483
|
+
'open_ports': open_ports,
|
|
484
|
+
'findings': [
|
|
485
|
+
{'port': p, 'state': 'open', 'service': self._get_service(p)}
|
|
486
|
+
for p in open_ports
|
|
487
|
+
]
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async def _simulate_nikto(self, target: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
491
|
+
"""Simuliert Nikto Web Scan."""
|
|
492
|
+
await asyncio.sleep(0.8)
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
'success': True,
|
|
496
|
+
'tool': 'nikto',
|
|
497
|
+
'target': target,
|
|
498
|
+
'findings': [
|
|
499
|
+
{'type': 'header', 'issue': 'X-Frame-Options missing', 'severity': 'medium'},
|
|
500
|
+
{'type': 'header', 'issue': 'Content-Security-Policy missing', 'severity': 'low'},
|
|
501
|
+
{'type': 'info', 'issue': 'Server banner detected', 'severity': 'info'}
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async def _simulate_subfinder(self, target: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
506
|
+
"""Simuliert Subfinder."""
|
|
507
|
+
await asyncio.sleep(0.6)
|
|
508
|
+
|
|
509
|
+
subdomains = [
|
|
510
|
+
f'www.{target}',
|
|
511
|
+
f'mail.{target}',
|
|
512
|
+
f'ftp.{target}',
|
|
513
|
+
f'admin.{target}',
|
|
514
|
+
f'api.{target}'
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
'success': True,
|
|
519
|
+
'tool': 'subfinder',
|
|
520
|
+
'target': target,
|
|
521
|
+
'subdomains': subdomains,
|
|
522
|
+
'count': len(subdomains)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
def _get_service(self, port: int) -> str:
|
|
526
|
+
"""Mapped Port zu Service."""
|
|
527
|
+
services = {
|
|
528
|
+
22: 'ssh', 23: 'telnet', 25: 'smtp', 53: 'dns',
|
|
529
|
+
80: 'http', 110: 'pop3', 143: 'imap', 443: 'https',
|
|
530
|
+
445: 'smb', 3306: 'mysql', 3389: 'rdp', 5432: 'postgresql',
|
|
531
|
+
8080: 'http-proxy', 8443: 'https-alt'
|
|
532
|
+
}
|
|
533
|
+
return services.get(port, 'unknown')
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class KIAutonomousAgent:
|
|
537
|
+
"""
|
|
538
|
+
Hauptklasse für den KI-gesteuerten Autonomous Agent.
|
|
539
|
+
|
|
540
|
+
Implementiert:
|
|
541
|
+
- ReAct Pattern für strukturierte Analyse
|
|
542
|
+
- State Machine für kontrollierte Ausführung
|
|
543
|
+
- Memory Management (Short-term, Long-term, Context)
|
|
544
|
+
- Tool Orchestration mit 20+ Tools
|
|
545
|
+
- Self-Correction mit Retry-Logik
|
|
546
|
+
- Human-in-the-Loop für kritische Entscheidungen
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
def __init__(
|
|
550
|
+
self,
|
|
551
|
+
goal: str = "",
|
|
552
|
+
target: str = "",
|
|
553
|
+
human_in_loop: bool = False,
|
|
554
|
+
pause_on_critical: bool = True,
|
|
555
|
+
max_iterations: int = 50
|
|
556
|
+
):
|
|
557
|
+
self.memory = AgentMemory(goal=goal, target=target)
|
|
558
|
+
self.ki = KIAnalyzer()
|
|
559
|
+
self.executor = ToolExecutor(max_retries=3)
|
|
560
|
+
|
|
561
|
+
self.state = AgentState.IDLE
|
|
562
|
+
self.current_phase = AnalysisPhase.RECONNAISSANCE
|
|
563
|
+
self.step_counter = 0
|
|
564
|
+
self.max_iterations = max_iterations
|
|
565
|
+
|
|
566
|
+
# Human-in-the-Loop Settings
|
|
567
|
+
self.human_in_loop = human_in_loop
|
|
568
|
+
self.pause_on_critical = pause_on_critical
|
|
569
|
+
self.paused_for_input = False
|
|
570
|
+
|
|
571
|
+
# Callbacks
|
|
572
|
+
self.on_state_change: Optional[Callable[[AgentState, AgentState], None]] = None
|
|
573
|
+
self.on_step_complete: Optional[Callable[[ReActStep], None]] = None
|
|
574
|
+
self.on_finding: Optional[Callable[[Dict[str, Any]], None]] = None
|
|
575
|
+
self.on_human_prompt: Optional[Callable[[str], None]] = None
|
|
576
|
+
|
|
577
|
+
self.logger = logging.getLogger("KIAutonomousAgent")
|
|
578
|
+
|
|
579
|
+
def set_callbacks(
|
|
580
|
+
self,
|
|
581
|
+
state_change: Optional[Callable] = None,
|
|
582
|
+
step_complete: Optional[Callable] = None,
|
|
583
|
+
finding: Optional[Callable] = None,
|
|
584
|
+
human_prompt: Optional[Callable] = None
|
|
585
|
+
):
|
|
586
|
+
"""Setzt Callback-Funktionen."""
|
|
587
|
+
self.on_state_change = state_change
|
|
588
|
+
self.on_step_complete = step_complete
|
|
589
|
+
self.on_finding = finding
|
|
590
|
+
self.on_human_prompt = human_prompt
|
|
591
|
+
|
|
592
|
+
async def run(self) -> Dict[str, Any]:
|
|
593
|
+
"""
|
|
594
|
+
Haupt-Loop des Autonomous Agents.
|
|
595
|
+
|
|
596
|
+
Führt den ReAct Pattern aus:
|
|
597
|
+
REASON → ACT → OBSERVE → REFLECT → (REPEAT)
|
|
598
|
+
"""
|
|
599
|
+
self.logger.info(f"Starting KI Autonomous Agent for target: {self.memory.target}")
|
|
600
|
+
self._transition_to(AgentState.PLANNING)
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
while self.step_counter < self.max_iterations:
|
|
604
|
+
self.step_counter += 1
|
|
605
|
+
|
|
606
|
+
# === REASON: Planung ===
|
|
607
|
+
self._transition_to(AgentState.PLANNING)
|
|
608
|
+
step = await self._reason()
|
|
609
|
+
|
|
610
|
+
# Human-in-the-Loop Check
|
|
611
|
+
if self.human_in_loop and self._requires_human_approval(step):
|
|
612
|
+
self._transition_to(AgentState.PAUSED)
|
|
613
|
+
approved = await self._request_human_approval(step)
|
|
614
|
+
if not approved:
|
|
615
|
+
step.reflection = "Skipped by human operator"
|
|
616
|
+
step.success = False
|
|
617
|
+
self.memory.add_react_step(step)
|
|
618
|
+
continue
|
|
619
|
+
self._transition_to(AgentState.EXECUTING)
|
|
620
|
+
|
|
621
|
+
# === ACT: Ausführung ===
|
|
622
|
+
self._transition_to(AgentState.EXECUTING)
|
|
623
|
+
observation = await self._act(step)
|
|
624
|
+
step.observation = observation
|
|
625
|
+
|
|
626
|
+
# === OBSERVE: Analyse ===
|
|
627
|
+
self._transition_to(AgentState.OBSERVING)
|
|
628
|
+
findings = await self._observe(step)
|
|
629
|
+
|
|
630
|
+
# Füge Findings hinzu
|
|
631
|
+
for finding in findings:
|
|
632
|
+
self.memory.add_finding(finding)
|
|
633
|
+
if self.on_finding:
|
|
634
|
+
self.on_finding(finding)
|
|
635
|
+
|
|
636
|
+
# === REFLECT: Evaluation ===
|
|
637
|
+
self._transition_to(AgentState.REFLECTING)
|
|
638
|
+
reflection = await self._reflect(step, findings)
|
|
639
|
+
step.reflection = reflection
|
|
640
|
+
|
|
641
|
+
# Speichere Step
|
|
642
|
+
self.memory.add_react_step(step)
|
|
643
|
+
if self.on_step_complete:
|
|
644
|
+
self.on_step_complete(step)
|
|
645
|
+
|
|
646
|
+
# Prüfe auf Fertigstellung
|
|
647
|
+
if self._is_complete():
|
|
648
|
+
self._transition_to(AgentState.COMPLETED)
|
|
649
|
+
break
|
|
650
|
+
|
|
651
|
+
# Phase-Transition
|
|
652
|
+
self._update_phase()
|
|
653
|
+
|
|
654
|
+
# Kleine Pause zwischen Steps
|
|
655
|
+
await asyncio.sleep(0.5)
|
|
656
|
+
|
|
657
|
+
return self._generate_report()
|
|
658
|
+
|
|
659
|
+
except Exception as e:
|
|
660
|
+
self.logger.error(f"Agent error: {e}")
|
|
661
|
+
self._transition_to(AgentState.ERROR)
|
|
662
|
+
return {
|
|
663
|
+
'success': False,
|
|
664
|
+
'error': str(e),
|
|
665
|
+
'session_id': self.memory.session_id,
|
|
666
|
+
'steps_completed': self.step_counter
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async def _reason(self) -> ReActStep:
|
|
670
|
+
"""
|
|
671
|
+
REASON Phase: Nutzt KI zur Planung des nächsten Steps.
|
|
672
|
+
"""
|
|
673
|
+
context = self.memory.get_context_for_ki()
|
|
674
|
+
|
|
675
|
+
prompt = f"""Analyze the current situation and determine the next action.
|
|
676
|
+
|
|
677
|
+
Current Phase: {self.current_phase.value}
|
|
678
|
+
Step: {self.step_counter + 1}
|
|
679
|
+
|
|
680
|
+
Provide your response in this format:
|
|
681
|
+
THOUGHT: [Your reasoning about what to do next]
|
|
682
|
+
ACTION: [The specific action to take - scan|enumerate|analyze|exploit_check]
|
|
683
|
+
PARAMS: [JSON with parameters like {{"target": "x", "tool": "nmap"}}]
|
|
684
|
+
|
|
685
|
+
Be specific and actionable."""
|
|
686
|
+
|
|
687
|
+
response = await self.ki.analyze(prompt, context)
|
|
688
|
+
|
|
689
|
+
# Parse Response
|
|
690
|
+
thought = ""
|
|
691
|
+
action = "scan"
|
|
692
|
+
params = {}
|
|
693
|
+
|
|
694
|
+
if "THOUGHT:" in response:
|
|
695
|
+
thought = response.split("THOUGHT:")[1].split("\n")[0].strip()
|
|
696
|
+
if "ACTION:" in response:
|
|
697
|
+
action = response.split("ACTION:")[1].split("\n")[0].strip().lower()
|
|
698
|
+
if "PARAMS:" in response:
|
|
699
|
+
try:
|
|
700
|
+
params_text = response.split("PARAMS:")[1].split("\n")[0].strip()
|
|
701
|
+
params = json.loads(params_text)
|
|
702
|
+
except:
|
|
703
|
+
params = {"target": self.memory.target, "tool": "nmap"}
|
|
704
|
+
|
|
705
|
+
# Fallback wenn KI keine gute Antwort gibt
|
|
706
|
+
if not thought:
|
|
707
|
+
thought = f"Continuing {self.current_phase.value} phase with standard scan"
|
|
708
|
+
action = "scan"
|
|
709
|
+
params = {"target": self.memory.target, "tool": "nmap", "ports": "top100"}
|
|
710
|
+
|
|
711
|
+
return ReActStep(
|
|
712
|
+
step_number=self.step_counter + 1,
|
|
713
|
+
thought=thought,
|
|
714
|
+
action=action,
|
|
715
|
+
action_params=params
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
async def _act(self, step: ReActStep) -> str:
|
|
719
|
+
"""
|
|
720
|
+
ACT Phase: Führt die geplante Aktion aus.
|
|
721
|
+
"""
|
|
722
|
+
try:
|
|
723
|
+
result = await self.executor.execute({
|
|
724
|
+
'tool': step.action_params.get('tool', 'nmap'),
|
|
725
|
+
'target': step.action_params.get('target', self.memory.target),
|
|
726
|
+
'parameters': step.action_params
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
step.success = result.get('success', False)
|
|
730
|
+
return json.dumps(result, indent=2)
|
|
731
|
+
|
|
732
|
+
except Exception as e:
|
|
733
|
+
step.success = False
|
|
734
|
+
return f"Execution error: {str(e)}"
|
|
735
|
+
|
|
736
|
+
async def _observe(self, step: ReActStep) -> List[Dict[str, Any]]:
|
|
737
|
+
"""
|
|
738
|
+
OBSERVE Phase: Analysiert die Ergebnisse.
|
|
739
|
+
"""
|
|
740
|
+
context = f"Action: {step.action}\nObservation: {step.observation[:500]}"
|
|
741
|
+
|
|
742
|
+
prompt = """Analyze the scan results and identify security findings.
|
|
743
|
+
|
|
744
|
+
Extract and classify any vulnerabilities, misconfigurations, or interesting findings.
|
|
745
|
+
|
|
746
|
+
Provide findings in this JSON format:
|
|
747
|
+
[
|
|
748
|
+
{
|
|
749
|
+
"name": "Finding name",
|
|
750
|
+
"severity": "critical|high|medium|low|info",
|
|
751
|
+
"type": "vulnerability|misconfiguration|information",
|
|
752
|
+
"description": "Brief description",
|
|
753
|
+
"evidence": "Specific evidence from results"
|
|
754
|
+
}
|
|
755
|
+
]
|
|
756
|
+
|
|
757
|
+
If no findings, return empty array []."""
|
|
758
|
+
|
|
759
|
+
response = await self.ki.analyze(prompt, context)
|
|
760
|
+
|
|
761
|
+
# Versuche JSON zu extrahieren
|
|
762
|
+
try:
|
|
763
|
+
start = response.find('[')
|
|
764
|
+
end = response.rfind(']') + 1
|
|
765
|
+
if start >= 0 and end > start:
|
|
766
|
+
findings = json.loads(response[start:end])
|
|
767
|
+
self.logger.info(f"Identified {len(findings)} findings")
|
|
768
|
+
return findings
|
|
769
|
+
except Exception as e:
|
|
770
|
+
self.logger.warning(f"Could not parse findings: {e}")
|
|
771
|
+
|
|
772
|
+
return []
|
|
773
|
+
|
|
774
|
+
async def _reflect(self, step: ReActStep, findings: List[Dict[str, Any]]) -> str:
|
|
775
|
+
"""
|
|
776
|
+
REFLECT Phase: Evaluiert den Fortschritt.
|
|
777
|
+
"""
|
|
778
|
+
context = self.memory.get_context_for_ki()
|
|
779
|
+
|
|
780
|
+
prompt = f"""Evaluate the progress of the security assessment.
|
|
781
|
+
|
|
782
|
+
Step {step.step_number} completed:
|
|
783
|
+
- Action: {step.action}
|
|
784
|
+
- Success: {step.success}
|
|
785
|
+
- Findings this step: {len(findings)}
|
|
786
|
+
- Total findings: {len(self.memory.findings)}
|
|
787
|
+
|
|
788
|
+
Questions to answer:
|
|
789
|
+
1. Was this step successful? What did we learn?
|
|
790
|
+
2. Are we making progress toward the goal?
|
|
791
|
+
3. Should we adjust our approach?
|
|
792
|
+
4. Are we ready to move to the next phase ({self._get_next_phase().value})?
|
|
793
|
+
|
|
794
|
+
Provide a brief reflection (2-3 sentences)."""
|
|
795
|
+
|
|
796
|
+
reflection = await self.ki.analyze(prompt, context)
|
|
797
|
+
return reflection[:500]
|
|
798
|
+
|
|
799
|
+
def _requires_human_approval(self, step: ReActStep) -> bool:
|
|
800
|
+
"""Prüft ob menschliche Freigabe nötig ist."""
|
|
801
|
+
if not self.pause_on_critical:
|
|
802
|
+
return False
|
|
803
|
+
|
|
804
|
+
# Kritische Aktionen erfordern Freigabe
|
|
805
|
+
critical_actions = ['exploit', 'brute_force', 'intrusive', 'modify']
|
|
806
|
+
return any(ca in step.action.lower() for ca in critical_actions)
|
|
807
|
+
|
|
808
|
+
async def _request_human_approval(self, step: ReActStep) -> bool:
|
|
809
|
+
"""Fragt menschliche Freigabe an."""
|
|
810
|
+
message = f"""
|
|
811
|
+
+==============================================================+
|
|
812
|
+
| HUMAN-IN-THE-LOOP: Approval Required |
|
|
813
|
+
+==============================================================+
|
|
814
|
+
Step {step.step_number}: {step.action.upper()}
|
|
815
|
+
Thought: {step.thought[:100]}...
|
|
816
|
+
Params: {json.dumps(step.action_params)}
|
|
817
|
+
+==============================================================+
|
|
818
|
+
This action may be CRITICAL or DESTRUCTIVE.
|
|
819
|
+
Approve? (yes/no):
|
|
820
|
+
+==============================================================+
|
|
821
|
+
"""
|
|
822
|
+
if self.on_human_prompt:
|
|
823
|
+
self.on_human_prompt(message)
|
|
824
|
+
|
|
825
|
+
# In echter Implementierung: Warte auf Eingabe
|
|
826
|
+
# Für Demo: Auto-approve nach Verzögerung
|
|
827
|
+
self.logger.info("Waiting for human approval...")
|
|
828
|
+
await asyncio.sleep(2)
|
|
829
|
+
return True # Simulierte Freigabe
|
|
830
|
+
|
|
831
|
+
def _transition_to(self, new_state: AgentState):
|
|
832
|
+
"""Wechselt den Agenten-Zustand."""
|
|
833
|
+
old_state = self.state
|
|
834
|
+
self.state = new_state
|
|
835
|
+
|
|
836
|
+
if old_state != new_state:
|
|
837
|
+
self.logger.info(f"State transition: {old_state.name} -> {new_state.name}")
|
|
838
|
+
if self.on_state_change:
|
|
839
|
+
self.on_state_change(old_state, new_state)
|
|
840
|
+
|
|
841
|
+
def _update_phase(self):
|
|
842
|
+
"""Aktualisiert die Analyse-Phase basierend auf Fortschritt."""
|
|
843
|
+
phase_order = [
|
|
844
|
+
AnalysisPhase.RECONNAISSANCE,
|
|
845
|
+
AnalysisPhase.SCANNING,
|
|
846
|
+
AnalysisPhase.ENUMERATION,
|
|
847
|
+
AnalysisPhase.VULNERABILITY_ANALYSIS,
|
|
848
|
+
AnalysisPhase.EXPLOITATION,
|
|
849
|
+
AnalysisPhase.POST_EXPLOITATION,
|
|
850
|
+
AnalysisPhase.REPORTING
|
|
851
|
+
]
|
|
852
|
+
|
|
853
|
+
# Einfache Phase-Transition basierend auf Step-Count
|
|
854
|
+
if self.step_counter % 5 == 0:
|
|
855
|
+
current_idx = phase_order.index(self.current_phase)
|
|
856
|
+
if current_idx < len(phase_order) - 1:
|
|
857
|
+
self.current_phase = phase_order[current_idx + 1]
|
|
858
|
+
self.logger.info(f"Advanced to phase: {self.current_phase.value}")
|
|
859
|
+
|
|
860
|
+
def _get_next_phase(self) -> AnalysisPhase:
|
|
861
|
+
"""Gibt die nächste Phase zurück."""
|
|
862
|
+
phase_order = list(AnalysisPhase)
|
|
863
|
+
current_idx = phase_order.index(self.current_phase)
|
|
864
|
+
if current_idx < len(phase_order) - 1:
|
|
865
|
+
return phase_order[current_idx + 1]
|
|
866
|
+
return self.current_phase
|
|
867
|
+
|
|
868
|
+
def _is_complete(self) -> bool:
|
|
869
|
+
"""Prüft ob das Ziel erreicht ist."""
|
|
870
|
+
# Mindestens 10 Steps und in REPORTING Phase
|
|
871
|
+
return self.step_counter >= 10 and self.current_phase == AnalysisPhase.REPORTING
|
|
872
|
+
|
|
873
|
+
def _generate_report(self) -> Dict[str, Any]:
|
|
874
|
+
"""Generiert finalen Bericht."""
|
|
875
|
+
report = {
|
|
876
|
+
'session_id': self.memory.session_id,
|
|
877
|
+
'goal': self.memory.goal,
|
|
878
|
+
'target': self.memory.target,
|
|
879
|
+
'completed_at': datetime.now().isoformat(),
|
|
880
|
+
'total_steps': self.step_counter,
|
|
881
|
+
'final_state': self.state.name,
|
|
882
|
+
'findings_count': len(self.memory.findings),
|
|
883
|
+
'findings_by_severity': self._categorize_findings(),
|
|
884
|
+
'execution_summary': {
|
|
885
|
+
'phases_completed': self.current_phase.value,
|
|
886
|
+
'tools_used': list(set(h.get('tool', 'unknown') for h in self.executor.execution_history)),
|
|
887
|
+
'success_rate': self._calculate_success_rate()
|
|
888
|
+
},
|
|
889
|
+
'findings': self.memory.findings,
|
|
890
|
+
'react_history': [s.to_dict() for s in self.memory.context_window]
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
# Speichere Report
|
|
894
|
+
report_dir = Path("logs/ki_agent_reports")
|
|
895
|
+
report_dir.mkdir(parents=True, exist_ok=True)
|
|
896
|
+
report_file = report_dir / f"report_{self.memory.session_id}.json"
|
|
897
|
+
|
|
898
|
+
with open(report_file, 'w', encoding='utf-8') as f:
|
|
899
|
+
json.dump(report, f, indent=2, ensure_ascii=False)
|
|
900
|
+
|
|
901
|
+
self.logger.info(f"Report saved to: {report_file}")
|
|
902
|
+
return report
|
|
903
|
+
|
|
904
|
+
def _categorize_findings(self) -> Dict[str, int]:
|
|
905
|
+
"""Kategorisiert Findings nach Schwere."""
|
|
906
|
+
categories = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0, 'info': 0}
|
|
907
|
+
for f in self.memory.findings:
|
|
908
|
+
sev = f.get('severity', 'info').lower()
|
|
909
|
+
if sev in categories:
|
|
910
|
+
categories[sev] += 1
|
|
911
|
+
return categories
|
|
912
|
+
|
|
913
|
+
def _calculate_success_rate(self) -> float:
|
|
914
|
+
"""Berechnet Erfolgsrate der Ausführungen."""
|
|
915
|
+
if not self.executor.execution_history:
|
|
916
|
+
return 0.0
|
|
917
|
+
successful = sum(1 for h in self.executor.execution_history if h.get('success'))
|
|
918
|
+
return (successful / len(self.executor.execution_history)) * 100
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
# =============================================================================
|
|
922
|
+
# CLI Interface
|
|
923
|
+
# =============================================================================
|
|
924
|
+
|
|
925
|
+
async def run_ki_agent(
|
|
926
|
+
target: str,
|
|
927
|
+
goal: str = "Comprehensive security assessment",
|
|
928
|
+
human_in_loop: bool = False,
|
|
929
|
+
verbose: bool = True
|
|
930
|
+
):
|
|
931
|
+
"""
|
|
932
|
+
Führt den KI Autonomous Agent aus.
|
|
933
|
+
|
|
934
|
+
Args:
|
|
935
|
+
target: Ziel-IP oder Domain
|
|
936
|
+
goal: Analyse-Ziel
|
|
937
|
+
human_in_loop: Menschliche Freigabe für kritische Aktionen
|
|
938
|
+
verbose: Detaillierte Ausgabe
|
|
939
|
+
"""
|
|
940
|
+
print("""
|
|
941
|
+
+==================================================================+
|
|
942
|
+
| ZEN AI PENTEST - KI AUTONOMOUS AGENT v2.1 |
|
|
943
|
+
| ReAct Pattern | State Machine | Memory System |
|
|
944
|
+
+==================================================================+
|
|
945
|
+
""")
|
|
946
|
+
|
|
947
|
+
print(f"[CONFIG] Target: {target}")
|
|
948
|
+
print(f"[CONFIG] Goal: {goal}")
|
|
949
|
+
print(f"[CONFIG] Human-in-the-Loop: {'Enabled' if human_in_loop else 'Disabled'}")
|
|
950
|
+
print(f"[CONFIG] KI Backend: kimi-cli (fallback: built-in)\n")
|
|
951
|
+
|
|
952
|
+
# Erstelle Agent
|
|
953
|
+
agent = KIAutonomousAgent(
|
|
954
|
+
goal=goal,
|
|
955
|
+
target=target,
|
|
956
|
+
human_in_loop=human_in_loop,
|
|
957
|
+
max_iterations=20
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
# Callbacks für Live-Updates
|
|
961
|
+
def on_step(step: ReActStep):
|
|
962
|
+
if verbose:
|
|
963
|
+
print(f"\n[STEP {step.step_number}] {step.action.upper()}")
|
|
964
|
+
print(f" Thought: {step.thought[:80]}...")
|
|
965
|
+
print(f" Success: {'[OK]' if step.success else '[FAIL]'}")
|
|
966
|
+
|
|
967
|
+
def on_finding(finding: Dict[str, Any]):
|
|
968
|
+
sev = finding.get('severity', 'info').upper()
|
|
969
|
+
name = finding.get('name', 'Unknown')
|
|
970
|
+
print(f" [FINDING] [{sev}] {name}")
|
|
971
|
+
|
|
972
|
+
def on_state_change(old: AgentState, new: AgentState):
|
|
973
|
+
print(f" [STATE] {old.name} -> {new.name}")
|
|
974
|
+
|
|
975
|
+
agent.set_callbacks(
|
|
976
|
+
step_complete=on_step,
|
|
977
|
+
finding=on_finding,
|
|
978
|
+
state_change=on_state_change
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
# Führe Agent aus
|
|
982
|
+
start_time = datetime.now()
|
|
983
|
+
report = await agent.run()
|
|
984
|
+
duration = (datetime.now() - start_time).total_seconds()
|
|
985
|
+
|
|
986
|
+
# Zeige Ergebnis
|
|
987
|
+
print("\n" + "="*60)
|
|
988
|
+
print("ASSESSMENT COMPLETE")
|
|
989
|
+
print("="*60)
|
|
990
|
+
print(f"Duration: {duration:.1f}s")
|
|
991
|
+
print(f"Steps: {report['total_steps']}")
|
|
992
|
+
print(f"Findings: {report['findings_count']}")
|
|
993
|
+
print(f"Success Rate: {report['execution_summary']['success_rate']:.1f}%")
|
|
994
|
+
|
|
995
|
+
print("\nFindings by Severity:")
|
|
996
|
+
for sev, count in report['findings_by_severity'].items():
|
|
997
|
+
if count > 0:
|
|
998
|
+
print(f" [{sev.upper()}] {count}")
|
|
999
|
+
|
|
1000
|
+
print(f"\nReport saved: logs/ki_agent_reports/report_{report['session_id']}.json")
|
|
1001
|
+
|
|
1002
|
+
return report
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
if __name__ == "__main__":
|
|
1006
|
+
import argparse
|
|
1007
|
+
|
|
1008
|
+
parser = argparse.ArgumentParser(description="KI Autonomous Agent for Zen AI Pentest")
|
|
1009
|
+
parser.add_argument("target", help="Target IP or domain")
|
|
1010
|
+
parser.add_argument("--goal", default="Comprehensive security assessment", help="Assessment goal")
|
|
1011
|
+
parser.add_argument("--human-in-loop", action="store_true", help="Enable human approval for critical actions")
|
|
1012
|
+
parser.add_argument("--quiet", action="store_true", help="Minimal output")
|
|
1013
|
+
|
|
1014
|
+
args = parser.parse_args()
|
|
1015
|
+
|
|
1016
|
+
try:
|
|
1017
|
+
result = asyncio.run(run_ki_agent(
|
|
1018
|
+
target=args.target,
|
|
1019
|
+
goal=args.goal,
|
|
1020
|
+
human_in_loop=args.human_in_loop,
|
|
1021
|
+
verbose=not args.quiet
|
|
1022
|
+
))
|
|
1023
|
+
|
|
1024
|
+
if result.get('success') is False:
|
|
1025
|
+
print(f"\nError: {result.get('error', 'Unknown error')}")
|
|
1026
|
+
exit(1)
|
|
1027
|
+
|
|
1028
|
+
except KeyboardInterrupt:
|
|
1029
|
+
print("\n\n[!] Interrupted by user")
|
|
1030
|
+
exit(130)
|
|
1031
|
+
except Exception as e:
|
|
1032
|
+
print(f"\n[!] Error: {e}")
|
|
1033
|
+
exit(1)
|