aiptx 2.0.7__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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Output Parser - Extract structured data from tool outputs
|
|
3
|
+
|
|
4
|
+
Uses regex patterns + LLM fallback for complex parsing.
|
|
5
|
+
|
|
6
|
+
Supports:
|
|
7
|
+
- nmap, masscan (port scanning)
|
|
8
|
+
- gobuster, ffuf (directory enumeration)
|
|
9
|
+
- nuclei (vulnerability scanning)
|
|
10
|
+
- hydra (credential brute-forcing)
|
|
11
|
+
- Custom patterns
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Finding:
|
|
22
|
+
"""A structured finding from tool output"""
|
|
23
|
+
type: str # port, service, vuln, credential, host, path, info
|
|
24
|
+
value: str
|
|
25
|
+
description: str
|
|
26
|
+
severity: str = "info" # info, low, medium, high, critical
|
|
27
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
source_tool: str = ""
|
|
29
|
+
raw_line: str = ""
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
32
|
+
return {
|
|
33
|
+
"type": self.type,
|
|
34
|
+
"value": self.value,
|
|
35
|
+
"description": self.description,
|
|
36
|
+
"severity": self.severity,
|
|
37
|
+
"metadata": self.metadata,
|
|
38
|
+
"source_tool": self.source_tool,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class OutputParser:
|
|
43
|
+
"""
|
|
44
|
+
Parse tool outputs into structured findings.
|
|
45
|
+
|
|
46
|
+
Uses regex patterns for known tools, with LLM fallback
|
|
47
|
+
for unstructured or unknown outputs.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Regex patterns for common tools
|
|
51
|
+
PATTERNS = {
|
|
52
|
+
# nmap patterns
|
|
53
|
+
"nmap_port": re.compile(
|
|
54
|
+
r"(\d+)/(tcp|udp)\s+(\w+)\s+(\S+)(?:\s+(.*))?",
|
|
55
|
+
re.MULTILINE
|
|
56
|
+
),
|
|
57
|
+
"nmap_host": re.compile(
|
|
58
|
+
r"Nmap scan report for\s+(\S+)(?:\s+\((\d+\.\d+\.\d+\.\d+)\))?",
|
|
59
|
+
re.MULTILINE
|
|
60
|
+
),
|
|
61
|
+
"nmap_os": re.compile(
|
|
62
|
+
r"OS details?:\s*(.+)",
|
|
63
|
+
re.MULTILINE
|
|
64
|
+
),
|
|
65
|
+
|
|
66
|
+
# masscan patterns
|
|
67
|
+
"masscan_port": re.compile(
|
|
68
|
+
r"Discovered open port\s+(\d+)/(tcp|udp)\s+on\s+(\S+)",
|
|
69
|
+
re.MULTILINE
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
# gobuster/ffuf patterns
|
|
73
|
+
"directory": re.compile(
|
|
74
|
+
r"(/\S+)\s+\(Status:\s*(\d+)\)",
|
|
75
|
+
re.MULTILINE
|
|
76
|
+
),
|
|
77
|
+
"ffuf_result": re.compile(
|
|
78
|
+
r'"url":\s*"([^"]+)".*?"status":\s*(\d+)',
|
|
79
|
+
re.MULTILINE
|
|
80
|
+
),
|
|
81
|
+
|
|
82
|
+
# nuclei patterns
|
|
83
|
+
"nuclei_vuln": re.compile(
|
|
84
|
+
r"\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)",
|
|
85
|
+
re.MULTILINE
|
|
86
|
+
),
|
|
87
|
+
|
|
88
|
+
# hydra patterns
|
|
89
|
+
"hydra_cred": re.compile(
|
|
90
|
+
r"\[(\d+)\]\[(\w+)\]\s+host:\s+(\S+)\s+login:\s+(\S+)\s+password:\s+(\S+)",
|
|
91
|
+
re.MULTILINE
|
|
92
|
+
),
|
|
93
|
+
|
|
94
|
+
# sqlmap patterns
|
|
95
|
+
"sqlmap_injectable": re.compile(
|
|
96
|
+
r"Parameter:\s+(\S+)\s+\(([^)]+)\)",
|
|
97
|
+
re.MULTILINE
|
|
98
|
+
),
|
|
99
|
+
"sqlmap_dbms": re.compile(
|
|
100
|
+
r"back-end DBMS:\s+(.+)",
|
|
101
|
+
re.MULTILINE
|
|
102
|
+
),
|
|
103
|
+
|
|
104
|
+
# generic patterns
|
|
105
|
+
"ip_address": re.compile(
|
|
106
|
+
r"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b"
|
|
107
|
+
),
|
|
108
|
+
"domain": re.compile(
|
|
109
|
+
r"\b([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}\b"
|
|
110
|
+
),
|
|
111
|
+
"email": re.compile(
|
|
112
|
+
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
|
|
113
|
+
),
|
|
114
|
+
"hash_md5": re.compile(
|
|
115
|
+
r"\b[a-fA-F0-9]{32}\b"
|
|
116
|
+
),
|
|
117
|
+
"hash_sha1": re.compile(
|
|
118
|
+
r"\b[a-fA-F0-9]{40}\b"
|
|
119
|
+
),
|
|
120
|
+
"hash_sha256": re.compile(
|
|
121
|
+
r"\b[a-fA-F0-9]{64}\b"
|
|
122
|
+
),
|
|
123
|
+
"cve": re.compile(
|
|
124
|
+
r"CVE-\d{4}-\d{4,}",
|
|
125
|
+
re.IGNORECASE
|
|
126
|
+
),
|
|
127
|
+
"url": re.compile(
|
|
128
|
+
r"https?://[^\s<>\"']+",
|
|
129
|
+
re.IGNORECASE
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Custom parsers for specific tools
|
|
134
|
+
_custom_parsers: Dict[str, Callable] = {}
|
|
135
|
+
|
|
136
|
+
def __init__(self, llm: Any = None):
|
|
137
|
+
self.llm = llm
|
|
138
|
+
|
|
139
|
+
def register_parser(self, tool_name: str, parser_func: Callable) -> None:
|
|
140
|
+
"""Register a custom parser for a tool"""
|
|
141
|
+
self._custom_parsers[tool_name.lower()] = parser_func
|
|
142
|
+
|
|
143
|
+
def parse(
|
|
144
|
+
self,
|
|
145
|
+
output: str,
|
|
146
|
+
tool_name: Optional[str] = None,
|
|
147
|
+
include_generic: bool = True,
|
|
148
|
+
) -> List[Finding]:
|
|
149
|
+
"""
|
|
150
|
+
Parse tool output into structured findings.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
output: Raw tool output
|
|
154
|
+
tool_name: Name of the tool (for tool-specific parsing)
|
|
155
|
+
include_generic: Include generic pattern matching
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of Finding objects
|
|
159
|
+
"""
|
|
160
|
+
findings = []
|
|
161
|
+
|
|
162
|
+
if not output:
|
|
163
|
+
return findings
|
|
164
|
+
|
|
165
|
+
# Check for custom parser
|
|
166
|
+
if tool_name and tool_name.lower() in self._custom_parsers:
|
|
167
|
+
custom_findings = self._custom_parsers[tool_name.lower()](output)
|
|
168
|
+
findings.extend(custom_findings)
|
|
169
|
+
elif tool_name:
|
|
170
|
+
# Tool-specific parsing
|
|
171
|
+
tool_findings = self._parse_tool_specific(output, tool_name.lower())
|
|
172
|
+
findings.extend(tool_findings)
|
|
173
|
+
|
|
174
|
+
# Generic pattern matching
|
|
175
|
+
if include_generic:
|
|
176
|
+
generic_findings = self._parse_generic(output)
|
|
177
|
+
findings.extend(generic_findings)
|
|
178
|
+
|
|
179
|
+
# Set source tool
|
|
180
|
+
for finding in findings:
|
|
181
|
+
if not finding.source_tool and tool_name:
|
|
182
|
+
finding.source_tool = tool_name
|
|
183
|
+
|
|
184
|
+
# Deduplicate
|
|
185
|
+
findings = self._deduplicate(findings)
|
|
186
|
+
|
|
187
|
+
return findings
|
|
188
|
+
|
|
189
|
+
def _parse_tool_specific(self, output: str, tool_name: str) -> List[Finding]:
|
|
190
|
+
"""Parse output based on known tool"""
|
|
191
|
+
findings = []
|
|
192
|
+
|
|
193
|
+
if tool_name in ["nmap", "nmap-scan"]:
|
|
194
|
+
findings.extend(self._parse_nmap(output))
|
|
195
|
+
|
|
196
|
+
elif tool_name == "masscan":
|
|
197
|
+
findings.extend(self._parse_masscan(output))
|
|
198
|
+
|
|
199
|
+
elif tool_name in ["gobuster", "ffuf", "dirb", "dirbuster", "feroxbuster"]:
|
|
200
|
+
findings.extend(self._parse_directory(output))
|
|
201
|
+
|
|
202
|
+
elif tool_name == "nuclei":
|
|
203
|
+
findings.extend(self._parse_nuclei(output))
|
|
204
|
+
|
|
205
|
+
elif tool_name == "hydra":
|
|
206
|
+
findings.extend(self._parse_hydra(output))
|
|
207
|
+
|
|
208
|
+
elif tool_name == "sqlmap":
|
|
209
|
+
findings.extend(self._parse_sqlmap(output))
|
|
210
|
+
|
|
211
|
+
elif tool_name in ["nikto", "wpscan", "whatweb"]:
|
|
212
|
+
findings.extend(self._parse_vuln_scanner(output))
|
|
213
|
+
|
|
214
|
+
return findings
|
|
215
|
+
|
|
216
|
+
def _parse_nmap(self, output: str) -> List[Finding]:
|
|
217
|
+
"""Parse nmap output"""
|
|
218
|
+
findings = []
|
|
219
|
+
|
|
220
|
+
# Parse hosts
|
|
221
|
+
for match in self.PATTERNS["nmap_host"].finditer(output):
|
|
222
|
+
hostname = match.group(1)
|
|
223
|
+
ip = match.group(2) or hostname
|
|
224
|
+
findings.append(Finding(
|
|
225
|
+
type="host",
|
|
226
|
+
value=ip,
|
|
227
|
+
description=f"Host discovered: {hostname} ({ip})",
|
|
228
|
+
metadata={"hostname": hostname, "ip": ip},
|
|
229
|
+
raw_line=match.group(0),
|
|
230
|
+
))
|
|
231
|
+
|
|
232
|
+
# Parse ports
|
|
233
|
+
for match in self.PATTERNS["nmap_port"].finditer(output):
|
|
234
|
+
port = match.group(1)
|
|
235
|
+
protocol = match.group(2)
|
|
236
|
+
state = match.group(3)
|
|
237
|
+
service = match.group(4)
|
|
238
|
+
version = match.group(5) or ""
|
|
239
|
+
|
|
240
|
+
if state == "open":
|
|
241
|
+
findings.append(Finding(
|
|
242
|
+
type="port",
|
|
243
|
+
value=f"{port}/{protocol}",
|
|
244
|
+
description=f"Open port {port}/{protocol}: {service} {version}".strip(),
|
|
245
|
+
severity="low" if service in ["http", "https", "ftp", "ssh"] else "info",
|
|
246
|
+
metadata={
|
|
247
|
+
"port": int(port),
|
|
248
|
+
"protocol": protocol,
|
|
249
|
+
"service": service,
|
|
250
|
+
"version": version,
|
|
251
|
+
},
|
|
252
|
+
raw_line=match.group(0),
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
# Parse OS detection
|
|
256
|
+
for match in self.PATTERNS["nmap_os"].finditer(output):
|
|
257
|
+
os_info = match.group(1)
|
|
258
|
+
findings.append(Finding(
|
|
259
|
+
type="info",
|
|
260
|
+
value=os_info,
|
|
261
|
+
description=f"OS detected: {os_info}",
|
|
262
|
+
metadata={"os": os_info},
|
|
263
|
+
))
|
|
264
|
+
|
|
265
|
+
return findings
|
|
266
|
+
|
|
267
|
+
def _parse_masscan(self, output: str) -> List[Finding]:
|
|
268
|
+
"""Parse masscan output"""
|
|
269
|
+
findings = []
|
|
270
|
+
|
|
271
|
+
for match in self.PATTERNS["masscan_port"].finditer(output):
|
|
272
|
+
port = match.group(1)
|
|
273
|
+
protocol = match.group(2)
|
|
274
|
+
ip = match.group(3)
|
|
275
|
+
|
|
276
|
+
findings.append(Finding(
|
|
277
|
+
type="port",
|
|
278
|
+
value=f"{ip}:{port}/{protocol}",
|
|
279
|
+
description=f"Open port {port}/{protocol} on {ip}",
|
|
280
|
+
metadata={"ip": ip, "port": int(port), "protocol": protocol},
|
|
281
|
+
))
|
|
282
|
+
|
|
283
|
+
return findings
|
|
284
|
+
|
|
285
|
+
def _parse_directory(self, output: str) -> List[Finding]:
|
|
286
|
+
"""Parse directory brute-force output"""
|
|
287
|
+
findings = []
|
|
288
|
+
|
|
289
|
+
# Standard format
|
|
290
|
+
for match in self.PATTERNS["directory"].finditer(output):
|
|
291
|
+
path = match.group(1)
|
|
292
|
+
status = match.group(2)
|
|
293
|
+
|
|
294
|
+
severity = "info"
|
|
295
|
+
if status in ["200", "301", "302"]:
|
|
296
|
+
severity = "low"
|
|
297
|
+
if any(kw in path.lower() for kw in ["admin", "backup", "config", "upload", "api", "debug"]):
|
|
298
|
+
severity = "medium"
|
|
299
|
+
|
|
300
|
+
findings.append(Finding(
|
|
301
|
+
type="path",
|
|
302
|
+
value=path,
|
|
303
|
+
description=f"Directory found: {path} (Status: {status})",
|
|
304
|
+
severity=severity,
|
|
305
|
+
metadata={"status_code": int(status)},
|
|
306
|
+
))
|
|
307
|
+
|
|
308
|
+
# ffuf JSON format
|
|
309
|
+
for match in self.PATTERNS["ffuf_result"].finditer(output):
|
|
310
|
+
url = match.group(1)
|
|
311
|
+
status = match.group(2)
|
|
312
|
+
|
|
313
|
+
findings.append(Finding(
|
|
314
|
+
type="path",
|
|
315
|
+
value=url,
|
|
316
|
+
description=f"Endpoint found: {url} (Status: {status})",
|
|
317
|
+
severity="low",
|
|
318
|
+
metadata={"status_code": int(status)},
|
|
319
|
+
))
|
|
320
|
+
|
|
321
|
+
return findings
|
|
322
|
+
|
|
323
|
+
def _parse_nuclei(self, output: str) -> List[Finding]:
|
|
324
|
+
"""Parse nuclei vulnerability scanner output"""
|
|
325
|
+
findings = []
|
|
326
|
+
|
|
327
|
+
for match in self.PATTERNS["nuclei_vuln"].finditer(output):
|
|
328
|
+
template_id = match.group(1)
|
|
329
|
+
severity = match.group(2).lower()
|
|
330
|
+
protocol = match.group(3)
|
|
331
|
+
target = match.group(4)
|
|
332
|
+
|
|
333
|
+
if severity not in ["info", "low", "medium", "high", "critical"]:
|
|
334
|
+
severity = "info"
|
|
335
|
+
|
|
336
|
+
findings.append(Finding(
|
|
337
|
+
type="vuln",
|
|
338
|
+
value=template_id,
|
|
339
|
+
description=f"Vulnerability: {template_id} on {target}",
|
|
340
|
+
severity=severity,
|
|
341
|
+
metadata={
|
|
342
|
+
"template": template_id,
|
|
343
|
+
"protocol": protocol,
|
|
344
|
+
"target": target,
|
|
345
|
+
},
|
|
346
|
+
))
|
|
347
|
+
|
|
348
|
+
return findings
|
|
349
|
+
|
|
350
|
+
def _parse_hydra(self, output: str) -> List[Finding]:
|
|
351
|
+
"""Parse hydra brute-force output"""
|
|
352
|
+
findings = []
|
|
353
|
+
|
|
354
|
+
for match in self.PATTERNS["hydra_cred"].finditer(output):
|
|
355
|
+
port = match.group(1)
|
|
356
|
+
service = match.group(2)
|
|
357
|
+
host = match.group(3)
|
|
358
|
+
username = match.group(4)
|
|
359
|
+
password = match.group(5)
|
|
360
|
+
|
|
361
|
+
findings.append(Finding(
|
|
362
|
+
type="credential",
|
|
363
|
+
value=f"{username}:{password}",
|
|
364
|
+
description=f"Valid credentials found for {service} on {host}:{port}",
|
|
365
|
+
severity="critical",
|
|
366
|
+
metadata={
|
|
367
|
+
"host": host,
|
|
368
|
+
"port": int(port),
|
|
369
|
+
"service": service,
|
|
370
|
+
"username": username,
|
|
371
|
+
"password": password,
|
|
372
|
+
},
|
|
373
|
+
))
|
|
374
|
+
|
|
375
|
+
return findings
|
|
376
|
+
|
|
377
|
+
def _parse_sqlmap(self, output: str) -> List[Finding]:
|
|
378
|
+
"""Parse sqlmap output"""
|
|
379
|
+
findings = []
|
|
380
|
+
|
|
381
|
+
# Injectable parameters
|
|
382
|
+
for match in self.PATTERNS["sqlmap_injectable"].finditer(output):
|
|
383
|
+
param = match.group(1)
|
|
384
|
+
injection_type = match.group(2)
|
|
385
|
+
|
|
386
|
+
findings.append(Finding(
|
|
387
|
+
type="vuln",
|
|
388
|
+
value=f"SQLi: {param}",
|
|
389
|
+
description=f"SQL Injection in parameter '{param}' ({injection_type})",
|
|
390
|
+
severity="high",
|
|
391
|
+
metadata={"parameter": param, "injection_type": injection_type},
|
|
392
|
+
))
|
|
393
|
+
|
|
394
|
+
# DBMS detection
|
|
395
|
+
for match in self.PATTERNS["sqlmap_dbms"].finditer(output):
|
|
396
|
+
dbms = match.group(1)
|
|
397
|
+
findings.append(Finding(
|
|
398
|
+
type="info",
|
|
399
|
+
value=dbms,
|
|
400
|
+
description=f"Backend DBMS: {dbms}",
|
|
401
|
+
metadata={"dbms": dbms},
|
|
402
|
+
))
|
|
403
|
+
|
|
404
|
+
return findings
|
|
405
|
+
|
|
406
|
+
def _parse_vuln_scanner(self, output: str) -> List[Finding]:
|
|
407
|
+
"""Parse generic vulnerability scanner output (nikto, wpscan)"""
|
|
408
|
+
findings = []
|
|
409
|
+
|
|
410
|
+
# Look for CVEs
|
|
411
|
+
for match in self.PATTERNS["cve"].finditer(output):
|
|
412
|
+
cve = match.group(0).upper()
|
|
413
|
+
findings.append(Finding(
|
|
414
|
+
type="vuln",
|
|
415
|
+
value=cve,
|
|
416
|
+
description=f"CVE detected: {cve}",
|
|
417
|
+
severity="high",
|
|
418
|
+
metadata={"cve": cve},
|
|
419
|
+
))
|
|
420
|
+
|
|
421
|
+
# Look for common vulnerability keywords
|
|
422
|
+
vuln_keywords = [
|
|
423
|
+
("SQL injection", "high", "sqli"),
|
|
424
|
+
("XSS", "medium", "xss"),
|
|
425
|
+
("Cross-Site Scripting", "medium", "xss"),
|
|
426
|
+
("CSRF", "medium", "csrf"),
|
|
427
|
+
("directory listing", "low", "info_disclosure"),
|
|
428
|
+
("information disclosure", "medium", "info_disclosure"),
|
|
429
|
+
("remote code execution", "critical", "rce"),
|
|
430
|
+
("RCE", "critical", "rce"),
|
|
431
|
+
("LFI", "high", "lfi"),
|
|
432
|
+
("Local File Inclusion", "high", "lfi"),
|
|
433
|
+
("RFI", "high", "rfi"),
|
|
434
|
+
("Remote File Inclusion", "high", "rfi"),
|
|
435
|
+
("SSRF", "high", "ssrf"),
|
|
436
|
+
("XXE", "high", "xxe"),
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
output_lower = output.lower()
|
|
440
|
+
for keyword, severity, vuln_type in vuln_keywords:
|
|
441
|
+
if keyword.lower() in output_lower:
|
|
442
|
+
findings.append(Finding(
|
|
443
|
+
type="vuln",
|
|
444
|
+
value=keyword,
|
|
445
|
+
description=f"Potential vulnerability: {keyword}",
|
|
446
|
+
severity=severity,
|
|
447
|
+
metadata={"vuln_type": vuln_type},
|
|
448
|
+
))
|
|
449
|
+
|
|
450
|
+
return findings
|
|
451
|
+
|
|
452
|
+
def _parse_generic(self, output: str) -> List[Finding]:
|
|
453
|
+
"""Extract generic patterns from any output"""
|
|
454
|
+
findings = []
|
|
455
|
+
|
|
456
|
+
# Extract CVEs
|
|
457
|
+
cves_found = set()
|
|
458
|
+
for match in self.PATTERNS["cve"].finditer(output):
|
|
459
|
+
cve = match.group(0).upper()
|
|
460
|
+
if cve not in cves_found:
|
|
461
|
+
cves_found.add(cve)
|
|
462
|
+
findings.append(Finding(
|
|
463
|
+
type="vuln",
|
|
464
|
+
value=cve,
|
|
465
|
+
description=f"CVE reference: {cve}",
|
|
466
|
+
severity="medium",
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
# Extract emails
|
|
470
|
+
emails_found = set()
|
|
471
|
+
for match in self.PATTERNS["email"].finditer(output):
|
|
472
|
+
email = match.group(0)
|
|
473
|
+
if email not in emails_found:
|
|
474
|
+
emails_found.add(email)
|
|
475
|
+
findings.append(Finding(
|
|
476
|
+
type="info",
|
|
477
|
+
value=email,
|
|
478
|
+
description=f"Email discovered: {email}",
|
|
479
|
+
))
|
|
480
|
+
|
|
481
|
+
return findings
|
|
482
|
+
|
|
483
|
+
def _deduplicate(self, findings: List[Finding]) -> List[Finding]:
|
|
484
|
+
"""Remove duplicate findings"""
|
|
485
|
+
seen = set()
|
|
486
|
+
unique = []
|
|
487
|
+
|
|
488
|
+
for finding in findings:
|
|
489
|
+
key = (finding.type, finding.value)
|
|
490
|
+
if key not in seen:
|
|
491
|
+
seen.add(key)
|
|
492
|
+
unique.append(finding)
|
|
493
|
+
|
|
494
|
+
return unique
|
|
495
|
+
|
|
496
|
+
def parse_with_llm(self, output: str, tool_name: str = "unknown") -> List[Finding]:
|
|
497
|
+
"""
|
|
498
|
+
Parse output using LLM for complex/unknown formats.
|
|
499
|
+
|
|
500
|
+
Falls back to regex if LLM not available.
|
|
501
|
+
"""
|
|
502
|
+
if not self.llm:
|
|
503
|
+
return self.parse(output, tool_name)
|
|
504
|
+
|
|
505
|
+
prompt = f"""Analyze this security tool output and extract findings.
|
|
506
|
+
|
|
507
|
+
Tool: {tool_name}
|
|
508
|
+
|
|
509
|
+
Output:
|
|
510
|
+
{output[:5000]}
|
|
511
|
+
|
|
512
|
+
Extract findings in this JSON format:
|
|
513
|
+
[
|
|
514
|
+
{{"type": "port|service|vuln|credential|host|path|info", "value": "identifier", "description": "brief description", "severity": "info|low|medium|high|critical"}}
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
Only return valid JSON array. If no findings, return []."""
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
response = self.llm.invoke([
|
|
521
|
+
{"role": "system", "content": "You are a security findings parser. Return only valid JSON."},
|
|
522
|
+
{"role": "user", "content": prompt},
|
|
523
|
+
], max_tokens=1000)
|
|
524
|
+
|
|
525
|
+
import json
|
|
526
|
+
content = response.content.strip()
|
|
527
|
+
if content.startswith("```"):
|
|
528
|
+
content = content.split("```")[1]
|
|
529
|
+
if content.startswith("json"):
|
|
530
|
+
content = content[4:]
|
|
531
|
+
|
|
532
|
+
raw_findings = json.loads(content)
|
|
533
|
+
return [
|
|
534
|
+
Finding(
|
|
535
|
+
type=f.get("type", "info"),
|
|
536
|
+
value=f.get("value", ""),
|
|
537
|
+
description=f.get("description", ""),
|
|
538
|
+
severity=f.get("severity", "info"),
|
|
539
|
+
source_tool=tool_name,
|
|
540
|
+
)
|
|
541
|
+
for f in raw_findings
|
|
542
|
+
]
|
|
543
|
+
except Exception:
|
|
544
|
+
return self.parse(output, tool_name)
|