aiptx 2.0.2__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.
Potentially problematic release.
This version of aiptx might be problematic. Click here for more details.
- 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 +24 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +960 -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 +321 -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 +288 -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 +85 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -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/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/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 +2284 -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 +44 -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/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/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 +201 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aiptx-2.0.2.dist-info/METADATA +324 -0
- aiptx-2.0.2.dist-info/RECORD +165 -0
- aiptx-2.0.2.dist-info/WHEEL +5 -0
- aiptx-2.0.2.dist-info/entry_points.txt +7 -0
- aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT AI-Powered Triage System
|
|
3
|
+
|
|
4
|
+
Uses LLM intelligence to prioritize vulnerability findings based on:
|
|
5
|
+
- Real-world exploitability (not just CVSS scores)
|
|
6
|
+
- Business context and impact
|
|
7
|
+
- Attack surface exposure
|
|
8
|
+
- Ease of exploitation
|
|
9
|
+
- Potential for chaining
|
|
10
|
+
|
|
11
|
+
This helps pentesters focus on the most impactful findings first.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
# Import VulnerabilityType from chaining module
|
|
25
|
+
from aipt_v2.intelligence.chaining import VulnerabilityType
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Exploitability(Enum):
|
|
32
|
+
"""How easy is this to exploit in the real world"""
|
|
33
|
+
TRIVIAL = "trivial" # Script kiddie level, public exploits
|
|
34
|
+
EASY = "easy" # Basic skills required
|
|
35
|
+
MODERATE = "moderate" # Some expertise needed
|
|
36
|
+
DIFFICULT = "difficult" # Advanced skills, custom exploit
|
|
37
|
+
THEORETICAL = "theoretical" # Requires specific conditions
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BusinessCriticality(Enum):
|
|
41
|
+
"""Business impact if exploited"""
|
|
42
|
+
CRITICAL = "critical" # Business-ending, massive breach
|
|
43
|
+
HIGH = "high" # Significant financial/reputational damage
|
|
44
|
+
MEDIUM = "medium" # Notable impact, recoverable
|
|
45
|
+
LOW = "low" # Minor impact
|
|
46
|
+
MINIMAL = "minimal" # Negligible business impact
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class RiskAssessment:
|
|
51
|
+
"""Detailed risk assessment for a finding"""
|
|
52
|
+
finding: Finding
|
|
53
|
+
|
|
54
|
+
# AI-assessed factors
|
|
55
|
+
exploitability: Exploitability
|
|
56
|
+
business_criticality: BusinessCriticality
|
|
57
|
+
|
|
58
|
+
# Scores (0-100)
|
|
59
|
+
exploitability_score: int
|
|
60
|
+
impact_score: int
|
|
61
|
+
priority_score: int # Combined score for ranking
|
|
62
|
+
|
|
63
|
+
# AI reasoning
|
|
64
|
+
exploitability_reasoning: str
|
|
65
|
+
impact_reasoning: str
|
|
66
|
+
attack_scenario: str
|
|
67
|
+
|
|
68
|
+
# Recommendations
|
|
69
|
+
remediation_priority: int # 1-5, 1 being highest
|
|
70
|
+
quick_win: bool # Can be fixed quickly?
|
|
71
|
+
requires_immediate_action: bool
|
|
72
|
+
|
|
73
|
+
# Metadata
|
|
74
|
+
assessed_at: datetime = field(default_factory=datetime.utcnow)
|
|
75
|
+
confidence: float = 0.8
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict[str, Any]:
|
|
78
|
+
return {
|
|
79
|
+
"finding_title": self.finding.title,
|
|
80
|
+
"finding_url": self.finding.url,
|
|
81
|
+
"original_severity": self.finding.severity.value,
|
|
82
|
+
"exploitability": self.exploitability.value,
|
|
83
|
+
"business_criticality": self.business_criticality.value,
|
|
84
|
+
"exploitability_score": self.exploitability_score,
|
|
85
|
+
"impact_score": self.impact_score,
|
|
86
|
+
"priority_score": self.priority_score,
|
|
87
|
+
"exploitability_reasoning": self.exploitability_reasoning,
|
|
88
|
+
"impact_reasoning": self.impact_reasoning,
|
|
89
|
+
"attack_scenario": self.attack_scenario,
|
|
90
|
+
"remediation_priority": self.remediation_priority,
|
|
91
|
+
"quick_win": self.quick_win,
|
|
92
|
+
"requires_immediate_action": self.requires_immediate_action,
|
|
93
|
+
"confidence": self.confidence,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class TriageResult:
|
|
99
|
+
"""Complete triage result for all findings"""
|
|
100
|
+
assessments: list[RiskAssessment]
|
|
101
|
+
prioritized_findings: list[Finding]
|
|
102
|
+
|
|
103
|
+
# Summary statistics
|
|
104
|
+
critical_count: int
|
|
105
|
+
immediate_action_count: int
|
|
106
|
+
quick_wins: list[Finding]
|
|
107
|
+
|
|
108
|
+
# AI summary
|
|
109
|
+
executive_summary: str
|
|
110
|
+
top_recommendations: list[str]
|
|
111
|
+
|
|
112
|
+
# Metadata
|
|
113
|
+
total_findings: int
|
|
114
|
+
triaged_at: datetime = field(default_factory=datetime.utcnow)
|
|
115
|
+
|
|
116
|
+
def get_top_priority(self, n: int = 10) -> list[RiskAssessment]:
|
|
117
|
+
"""Get top N priority findings"""
|
|
118
|
+
sorted_assessments = sorted(
|
|
119
|
+
self.assessments,
|
|
120
|
+
key=lambda a: a.priority_score,
|
|
121
|
+
reverse=True
|
|
122
|
+
)
|
|
123
|
+
return sorted_assessments[:n]
|
|
124
|
+
|
|
125
|
+
def to_dict(self) -> dict[str, Any]:
|
|
126
|
+
return {
|
|
127
|
+
"total_findings": self.total_findings,
|
|
128
|
+
"critical_count": self.critical_count,
|
|
129
|
+
"immediate_action_count": self.immediate_action_count,
|
|
130
|
+
"quick_wins_count": len(self.quick_wins),
|
|
131
|
+
"executive_summary": self.executive_summary,
|
|
132
|
+
"top_recommendations": self.top_recommendations,
|
|
133
|
+
"assessments": [a.to_dict() for a in self.assessments],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ============================================================================
|
|
138
|
+
# Exploitability Rules (heuristic-based, can be enhanced with LLM)
|
|
139
|
+
# ============================================================================
|
|
140
|
+
|
|
141
|
+
EXPLOITABILITY_RULES = {
|
|
142
|
+
# Trivial - public exploits widely available
|
|
143
|
+
VulnerabilityType.SQL_INJECTION: {
|
|
144
|
+
"base": Exploitability.EASY,
|
|
145
|
+
"score": 85,
|
|
146
|
+
"tools": ["sqlmap", "manual"],
|
|
147
|
+
},
|
|
148
|
+
VulnerabilityType.XSS_REFLECTED: {
|
|
149
|
+
"base": Exploitability.TRIVIAL,
|
|
150
|
+
"score": 90,
|
|
151
|
+
"tools": ["browser", "burp"],
|
|
152
|
+
},
|
|
153
|
+
VulnerabilityType.XSS_STORED: {
|
|
154
|
+
"base": Exploitability.TRIVIAL,
|
|
155
|
+
"score": 95,
|
|
156
|
+
"tools": ["browser"],
|
|
157
|
+
},
|
|
158
|
+
VulnerabilityType.COMMAND_INJECTION: {
|
|
159
|
+
"base": Exploitability.EASY,
|
|
160
|
+
"score": 90,
|
|
161
|
+
"tools": ["curl", "manual"],
|
|
162
|
+
},
|
|
163
|
+
VulnerabilityType.FILE_INCLUSION: {
|
|
164
|
+
"base": Exploitability.EASY,
|
|
165
|
+
"score": 80,
|
|
166
|
+
"tools": ["curl", "burp"],
|
|
167
|
+
},
|
|
168
|
+
VulnerabilityType.IDOR: {
|
|
169
|
+
"base": Exploitability.TRIVIAL,
|
|
170
|
+
"score": 95,
|
|
171
|
+
"tools": ["browser", "burp"],
|
|
172
|
+
},
|
|
173
|
+
VulnerabilityType.OPEN_REDIRECT: {
|
|
174
|
+
"base": Exploitability.TRIVIAL,
|
|
175
|
+
"score": 90,
|
|
176
|
+
"tools": ["browser"],
|
|
177
|
+
},
|
|
178
|
+
VulnerabilityType.DEFAULT_CREDENTIALS: {
|
|
179
|
+
"base": Exploitability.TRIVIAL,
|
|
180
|
+
"score": 100,
|
|
181
|
+
"tools": ["browser"],
|
|
182
|
+
},
|
|
183
|
+
VulnerabilityType.SSRF: {
|
|
184
|
+
"base": Exploitability.MODERATE,
|
|
185
|
+
"score": 70,
|
|
186
|
+
"tools": ["burp", "custom"],
|
|
187
|
+
},
|
|
188
|
+
VulnerabilityType.XXE: {
|
|
189
|
+
"base": Exploitability.MODERATE,
|
|
190
|
+
"score": 65,
|
|
191
|
+
"tools": ["burp", "custom"],
|
|
192
|
+
},
|
|
193
|
+
VulnerabilityType.INSECURE_DESERIALIZATION: {
|
|
194
|
+
"base": Exploitability.DIFFICULT,
|
|
195
|
+
"score": 50,
|
|
196
|
+
"tools": ["ysoserial", "custom"],
|
|
197
|
+
},
|
|
198
|
+
VulnerabilityType.RCE: {
|
|
199
|
+
"base": Exploitability.EASY, # If found, usually exploitable
|
|
200
|
+
"score": 85,
|
|
201
|
+
"tools": ["various"],
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Impact multipliers based on vulnerability type
|
|
206
|
+
IMPACT_MULTIPLIERS = {
|
|
207
|
+
VulnerabilityType.RCE: 1.0,
|
|
208
|
+
VulnerabilityType.SQL_INJECTION: 0.95,
|
|
209
|
+
VulnerabilityType.COMMAND_INJECTION: 0.95,
|
|
210
|
+
VulnerabilityType.AUTH_BYPASS: 0.9,
|
|
211
|
+
VulnerabilityType.PRIVILEGE_ESCALATION: 0.9,
|
|
212
|
+
VulnerabilityType.INSECURE_DESERIALIZATION: 0.85,
|
|
213
|
+
VulnerabilityType.XXE: 0.8,
|
|
214
|
+
VulnerabilityType.SSRF: 0.8,
|
|
215
|
+
VulnerabilityType.FILE_INCLUSION: 0.75,
|
|
216
|
+
VulnerabilityType.XSS_STORED: 0.7,
|
|
217
|
+
VulnerabilityType.IDOR: 0.7,
|
|
218
|
+
VulnerabilityType.XSS_REFLECTED: 0.5,
|
|
219
|
+
VulnerabilityType.OPEN_REDIRECT: 0.3,
|
|
220
|
+
VulnerabilityType.INFORMATION_DISCLOSURE: 0.4,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class AITriage:
|
|
225
|
+
"""
|
|
226
|
+
AI-Powered Vulnerability Triage System
|
|
227
|
+
|
|
228
|
+
Analyzes findings using a combination of:
|
|
229
|
+
1. Rule-based heuristics (fast, deterministic)
|
|
230
|
+
2. LLM analysis (deep, contextual) - when available
|
|
231
|
+
3. Business context awareness
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
triage = AITriage()
|
|
235
|
+
result = await triage.analyze(findings)
|
|
236
|
+
for assessment in result.get_top_priority(5):
|
|
237
|
+
print(f"Priority {assessment.remediation_priority}: {assessment.finding.title}")
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self,
|
|
242
|
+
use_llm: bool = True,
|
|
243
|
+
llm_provider: str = "anthropic",
|
|
244
|
+
llm_model: str = "claude-3-haiku-20240307",
|
|
245
|
+
):
|
|
246
|
+
self.use_llm = use_llm
|
|
247
|
+
self.llm_provider = llm_provider
|
|
248
|
+
self.llm_model = llm_model
|
|
249
|
+
self._llm_client = None
|
|
250
|
+
|
|
251
|
+
async def analyze(
|
|
252
|
+
self,
|
|
253
|
+
findings: list[Finding],
|
|
254
|
+
business_context: str = "",
|
|
255
|
+
target_type: str = "web_application",
|
|
256
|
+
) -> TriageResult:
|
|
257
|
+
"""
|
|
258
|
+
Analyze and prioritize all findings.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
findings: List of vulnerability findings
|
|
262
|
+
business_context: Optional context about the target business
|
|
263
|
+
target_type: Type of target (web_application, api, mobile, etc.)
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
TriageResult with prioritized findings
|
|
267
|
+
"""
|
|
268
|
+
if not findings:
|
|
269
|
+
return TriageResult(
|
|
270
|
+
assessments=[],
|
|
271
|
+
prioritized_findings=[],
|
|
272
|
+
critical_count=0,
|
|
273
|
+
immediate_action_count=0,
|
|
274
|
+
quick_wins=[],
|
|
275
|
+
executive_summary="No findings to analyze.",
|
|
276
|
+
top_recommendations=[],
|
|
277
|
+
total_findings=0,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
logger.info(f"Starting AI triage of {len(findings)} findings")
|
|
281
|
+
|
|
282
|
+
# Assess each finding
|
|
283
|
+
assessments = []
|
|
284
|
+
for finding in findings:
|
|
285
|
+
assessment = await self._assess_finding(
|
|
286
|
+
finding, business_context, target_type
|
|
287
|
+
)
|
|
288
|
+
assessments.append(assessment)
|
|
289
|
+
|
|
290
|
+
# Sort by priority score
|
|
291
|
+
assessments.sort(key=lambda a: a.priority_score, reverse=True)
|
|
292
|
+
prioritized = [a.finding for a in assessments]
|
|
293
|
+
|
|
294
|
+
# Identify critical and immediate action items
|
|
295
|
+
critical = [a for a in assessments if a.business_criticality == BusinessCriticality.CRITICAL]
|
|
296
|
+
immediate = [a for a in assessments if a.requires_immediate_action]
|
|
297
|
+
quick_wins = [a.finding for a in assessments if a.quick_win]
|
|
298
|
+
|
|
299
|
+
# Generate executive summary
|
|
300
|
+
summary = self._generate_executive_summary(assessments, business_context)
|
|
301
|
+
recommendations = self._generate_recommendations(assessments)
|
|
302
|
+
|
|
303
|
+
return TriageResult(
|
|
304
|
+
assessments=assessments,
|
|
305
|
+
prioritized_findings=prioritized,
|
|
306
|
+
critical_count=len(critical),
|
|
307
|
+
immediate_action_count=len(immediate),
|
|
308
|
+
quick_wins=quick_wins[:10], # Top 10 quick wins
|
|
309
|
+
executive_summary=summary,
|
|
310
|
+
top_recommendations=recommendations,
|
|
311
|
+
total_findings=len(findings),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
async def _assess_finding(
|
|
315
|
+
self,
|
|
316
|
+
finding: Finding,
|
|
317
|
+
business_context: str,
|
|
318
|
+
target_type: str,
|
|
319
|
+
) -> RiskAssessment:
|
|
320
|
+
"""Assess a single finding"""
|
|
321
|
+
# Get base exploitability from rules
|
|
322
|
+
exploitability, exploitability_score = self._assess_exploitability(finding)
|
|
323
|
+
|
|
324
|
+
# Assess business impact
|
|
325
|
+
business_criticality, impact_score = self._assess_business_impact(
|
|
326
|
+
finding, business_context, target_type
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Calculate priority score (weighted combination)
|
|
330
|
+
priority_score = int(
|
|
331
|
+
(exploitability_score * 0.4) +
|
|
332
|
+
(impact_score * 0.4) +
|
|
333
|
+
(self._severity_to_score(finding.severity) * 0.2)
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Generate reasoning (use LLM if available, otherwise heuristic)
|
|
337
|
+
if self.use_llm and self._can_use_llm():
|
|
338
|
+
reasoning = await self._llm_assess(finding, business_context)
|
|
339
|
+
exploitability_reasoning = reasoning.get("exploitability", "")
|
|
340
|
+
impact_reasoning = reasoning.get("impact", "")
|
|
341
|
+
attack_scenario = reasoning.get("scenario", "")
|
|
342
|
+
else:
|
|
343
|
+
exploitability_reasoning = self._heuristic_exploitability_reason(finding)
|
|
344
|
+
impact_reasoning = self._heuristic_impact_reason(finding, business_context)
|
|
345
|
+
attack_scenario = self._generate_attack_scenario(finding)
|
|
346
|
+
|
|
347
|
+
# Determine remediation priority (1-5)
|
|
348
|
+
remediation_priority = self._calculate_remediation_priority(
|
|
349
|
+
priority_score, exploitability, business_criticality
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Check if quick win
|
|
353
|
+
quick_win = self._is_quick_win(finding)
|
|
354
|
+
|
|
355
|
+
# Check if requires immediate action
|
|
356
|
+
immediate = (
|
|
357
|
+
business_criticality == BusinessCriticality.CRITICAL or
|
|
358
|
+
(exploitability in [Exploitability.TRIVIAL, Exploitability.EASY] and
|
|
359
|
+
finding.severity in [Severity.CRITICAL, Severity.HIGH])
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return RiskAssessment(
|
|
363
|
+
finding=finding,
|
|
364
|
+
exploitability=exploitability,
|
|
365
|
+
business_criticality=business_criticality,
|
|
366
|
+
exploitability_score=exploitability_score,
|
|
367
|
+
impact_score=impact_score,
|
|
368
|
+
priority_score=priority_score,
|
|
369
|
+
exploitability_reasoning=exploitability_reasoning,
|
|
370
|
+
impact_reasoning=impact_reasoning,
|
|
371
|
+
attack_scenario=attack_scenario,
|
|
372
|
+
remediation_priority=remediation_priority,
|
|
373
|
+
quick_win=quick_win,
|
|
374
|
+
requires_immediate_action=immediate,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _assess_exploitability(
|
|
378
|
+
self,
|
|
379
|
+
finding: Finding,
|
|
380
|
+
) -> tuple[Exploitability, int]:
|
|
381
|
+
"""Assess how exploitable a finding is"""
|
|
382
|
+
rules = EXPLOITABILITY_RULES.get(finding.vuln_type)
|
|
383
|
+
|
|
384
|
+
if rules:
|
|
385
|
+
base = rules["base"]
|
|
386
|
+
score = rules["score"]
|
|
387
|
+
else:
|
|
388
|
+
# Default based on severity
|
|
389
|
+
severity_map = {
|
|
390
|
+
Severity.CRITICAL: (Exploitability.EASY, 80),
|
|
391
|
+
Severity.HIGH: (Exploitability.MODERATE, 60),
|
|
392
|
+
Severity.MEDIUM: (Exploitability.MODERATE, 50),
|
|
393
|
+
Severity.LOW: (Exploitability.DIFFICULT, 30),
|
|
394
|
+
Severity.INFO: (Exploitability.THEORETICAL, 10),
|
|
395
|
+
}
|
|
396
|
+
base, score = severity_map.get(
|
|
397
|
+
finding.severity,
|
|
398
|
+
(Exploitability.MODERATE, 50)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Adjust score based on additional factors
|
|
402
|
+
if finding.confirmed:
|
|
403
|
+
score = min(100, score + 10)
|
|
404
|
+
if finding.exploited:
|
|
405
|
+
score = min(100, score + 15)
|
|
406
|
+
if finding.poc_command:
|
|
407
|
+
score = min(100, score + 10)
|
|
408
|
+
|
|
409
|
+
return base, score
|
|
410
|
+
|
|
411
|
+
def _assess_business_impact(
|
|
412
|
+
self,
|
|
413
|
+
finding: Finding,
|
|
414
|
+
business_context: str,
|
|
415
|
+
target_type: str,
|
|
416
|
+
) -> tuple[BusinessCriticality, int]:
|
|
417
|
+
"""Assess business impact of a finding"""
|
|
418
|
+
# Base impact from vulnerability type
|
|
419
|
+
multiplier = IMPACT_MULTIPLIERS.get(finding.vuln_type, 0.5)
|
|
420
|
+
|
|
421
|
+
# Base score from severity
|
|
422
|
+
base_score = self._severity_to_score(finding.severity)
|
|
423
|
+
impact_score = int(base_score * multiplier)
|
|
424
|
+
|
|
425
|
+
# Adjust for business context keywords
|
|
426
|
+
high_value_keywords = [
|
|
427
|
+
"payment", "financial", "pii", "healthcare", "hipaa",
|
|
428
|
+
"pci", "credentials", "admin", "authentication", "api",
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
context_lower = (business_context + finding.url + finding.description).lower()
|
|
432
|
+
for keyword in high_value_keywords:
|
|
433
|
+
if keyword in context_lower:
|
|
434
|
+
impact_score = min(100, impact_score + 5)
|
|
435
|
+
|
|
436
|
+
# Determine criticality
|
|
437
|
+
if impact_score >= 85:
|
|
438
|
+
criticality = BusinessCriticality.CRITICAL
|
|
439
|
+
elif impact_score >= 70:
|
|
440
|
+
criticality = BusinessCriticality.HIGH
|
|
441
|
+
elif impact_score >= 50:
|
|
442
|
+
criticality = BusinessCriticality.MEDIUM
|
|
443
|
+
elif impact_score >= 30:
|
|
444
|
+
criticality = BusinessCriticality.LOW
|
|
445
|
+
else:
|
|
446
|
+
criticality = BusinessCriticality.MINIMAL
|
|
447
|
+
|
|
448
|
+
return criticality, impact_score
|
|
449
|
+
|
|
450
|
+
def _severity_to_score(self, severity: Severity) -> int:
|
|
451
|
+
"""Convert severity to numeric score"""
|
|
452
|
+
scores = {
|
|
453
|
+
Severity.CRITICAL: 100,
|
|
454
|
+
Severity.HIGH: 80,
|
|
455
|
+
Severity.MEDIUM: 50,
|
|
456
|
+
Severity.LOW: 25,
|
|
457
|
+
Severity.INFO: 10,
|
|
458
|
+
}
|
|
459
|
+
return scores.get(severity, 50)
|
|
460
|
+
|
|
461
|
+
def _calculate_remediation_priority(
|
|
462
|
+
self,
|
|
463
|
+
priority_score: int,
|
|
464
|
+
exploitability: Exploitability,
|
|
465
|
+
criticality: BusinessCriticality,
|
|
466
|
+
) -> int:
|
|
467
|
+
"""Calculate remediation priority (1-5, 1 highest)"""
|
|
468
|
+
if priority_score >= 85 or criticality == BusinessCriticality.CRITICAL:
|
|
469
|
+
return 1
|
|
470
|
+
if priority_score >= 70 or criticality == BusinessCriticality.HIGH:
|
|
471
|
+
return 2
|
|
472
|
+
if priority_score >= 50:
|
|
473
|
+
return 3
|
|
474
|
+
if priority_score >= 30:
|
|
475
|
+
return 4
|
|
476
|
+
return 5
|
|
477
|
+
|
|
478
|
+
def _is_quick_win(self, finding: Finding) -> bool:
|
|
479
|
+
"""Determine if a finding is a quick win to fix"""
|
|
480
|
+
quick_win_types = [
|
|
481
|
+
VulnerabilityType.DEFAULT_CREDENTIALS,
|
|
482
|
+
VulnerabilityType.MISCONFIGURATION,
|
|
483
|
+
VulnerabilityType.DIRECTORY_LISTING,
|
|
484
|
+
VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
485
|
+
VulnerabilityType.OPEN_REDIRECT,
|
|
486
|
+
VulnerabilityType.CORS_MISCONFIGURATION,
|
|
487
|
+
]
|
|
488
|
+
return finding.vuln_type in quick_win_types
|
|
489
|
+
|
|
490
|
+
def _heuristic_exploitability_reason(self, finding: Finding) -> str:
|
|
491
|
+
"""Generate exploitability reasoning without LLM"""
|
|
492
|
+
rules = EXPLOITABILITY_RULES.get(finding.vuln_type)
|
|
493
|
+
|
|
494
|
+
if rules:
|
|
495
|
+
tools = ", ".join(rules.get("tools", ["manual"]))
|
|
496
|
+
return (
|
|
497
|
+
f"This {finding.vuln_type.value} vulnerability is considered "
|
|
498
|
+
f"{rules['base'].value} to exploit. Common tools include: {tools}. "
|
|
499
|
+
f"{'Confirmed and exploited in testing.' if finding.exploited else ''}"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
return f"Exploitability assessment based on {finding.severity.value} severity."
|
|
503
|
+
|
|
504
|
+
def _heuristic_impact_reason(
|
|
505
|
+
self,
|
|
506
|
+
finding: Finding,
|
|
507
|
+
business_context: str,
|
|
508
|
+
) -> str:
|
|
509
|
+
"""Generate impact reasoning without LLM"""
|
|
510
|
+
impacts = {
|
|
511
|
+
VulnerabilityType.RCE: "allows complete server compromise and data access",
|
|
512
|
+
VulnerabilityType.SQL_INJECTION: "could lead to full database compromise",
|
|
513
|
+
VulnerabilityType.AUTH_BYPASS: "enables unauthorized access to protected resources",
|
|
514
|
+
VulnerabilityType.IDOR: "allows access to other users' data",
|
|
515
|
+
VulnerabilityType.XSS_STORED: "can compromise any user who views affected pages",
|
|
516
|
+
VulnerabilityType.SSRF: "may expose internal network and services",
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
impact = impacts.get(
|
|
520
|
+
finding.vuln_type,
|
|
521
|
+
f"could have {finding.severity.value} impact on the application"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
return f"This vulnerability {impact}."
|
|
525
|
+
|
|
526
|
+
def _generate_attack_scenario(self, finding: Finding) -> str:
|
|
527
|
+
"""Generate a realistic attack scenario"""
|
|
528
|
+
scenarios = {
|
|
529
|
+
VulnerabilityType.SQL_INJECTION: (
|
|
530
|
+
"1. Attacker identifies SQL injection point\n"
|
|
531
|
+
"2. Uses sqlmap or manual techniques to extract data\n"
|
|
532
|
+
"3. Dumps user credentials, PII, or business data\n"
|
|
533
|
+
"4. May escalate to OS command execution via xp_cmdshell"
|
|
534
|
+
),
|
|
535
|
+
VulnerabilityType.XSS_STORED: (
|
|
536
|
+
"1. Attacker injects malicious JavaScript\n"
|
|
537
|
+
"2. Script executes when other users view the page\n"
|
|
538
|
+
"3. Steals session cookies or credentials\n"
|
|
539
|
+
"4. Performs actions as the victim user"
|
|
540
|
+
),
|
|
541
|
+
VulnerabilityType.SSRF: (
|
|
542
|
+
"1. Attacker crafts request to internal resources\n"
|
|
543
|
+
"2. Accesses internal admin panels or databases\n"
|
|
544
|
+
"3. Reads cloud metadata (AWS keys, etc.)\n"
|
|
545
|
+
"4. Pivots to attack internal network"
|
|
546
|
+
),
|
|
547
|
+
VulnerabilityType.RCE: (
|
|
548
|
+
"1. Attacker achieves code execution\n"
|
|
549
|
+
"2. Establishes reverse shell or backdoor\n"
|
|
550
|
+
"3. Dumps credentials and sensitive data\n"
|
|
551
|
+
"4. Moves laterally through network"
|
|
552
|
+
),
|
|
553
|
+
VulnerabilityType.IDOR: (
|
|
554
|
+
"1. Attacker manipulates object references\n"
|
|
555
|
+
"2. Accesses other users' records\n"
|
|
556
|
+
"3. Extracts or modifies sensitive data\n"
|
|
557
|
+
"4. May enumerate all records in system"
|
|
558
|
+
),
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return scenarios.get(
|
|
562
|
+
finding.vuln_type,
|
|
563
|
+
f"Attacker exploits {finding.vuln_type.value} at {finding.url}"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
def _can_use_llm(self) -> bool:
|
|
567
|
+
"""Check if LLM is available"""
|
|
568
|
+
# Check for API key
|
|
569
|
+
if self.llm_provider == "anthropic":
|
|
570
|
+
return bool(os.getenv("ANTHROPIC_API_KEY"))
|
|
571
|
+
if self.llm_provider == "openai":
|
|
572
|
+
return bool(os.getenv("OPENAI_API_KEY"))
|
|
573
|
+
return False
|
|
574
|
+
|
|
575
|
+
async def _llm_assess(
|
|
576
|
+
self,
|
|
577
|
+
finding: Finding,
|
|
578
|
+
business_context: str,
|
|
579
|
+
) -> dict[str, str]:
|
|
580
|
+
"""Use LLM to assess finding (when available)"""
|
|
581
|
+
# This would integrate with litellm or direct API calls
|
|
582
|
+
# For now, return empty to fall back to heuristics
|
|
583
|
+
return {}
|
|
584
|
+
|
|
585
|
+
def _generate_executive_summary(
|
|
586
|
+
self,
|
|
587
|
+
assessments: list[RiskAssessment],
|
|
588
|
+
business_context: str,
|
|
589
|
+
) -> str:
|
|
590
|
+
"""Generate executive summary of findings"""
|
|
591
|
+
if not assessments:
|
|
592
|
+
return "No vulnerabilities were identified during this assessment."
|
|
593
|
+
|
|
594
|
+
critical = sum(1 for a in assessments if a.business_criticality == BusinessCriticality.CRITICAL)
|
|
595
|
+
high = sum(1 for a in assessments if a.business_criticality == BusinessCriticality.HIGH)
|
|
596
|
+
immediate = sum(1 for a in assessments if a.requires_immediate_action)
|
|
597
|
+
|
|
598
|
+
lines = [
|
|
599
|
+
f"## Executive Summary\n",
|
|
600
|
+
f"This assessment identified **{len(assessments)} vulnerabilities** requiring attention.\n",
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
if critical > 0 or immediate > 0:
|
|
604
|
+
lines.append(
|
|
605
|
+
f"**URGENT:** {critical} critical and {immediate} immediate-action items "
|
|
606
|
+
f"require priority remediation.\n"
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
lines.append(f"\n### Risk Breakdown\n")
|
|
610
|
+
lines.append(f"- **Critical Impact:** {critical}")
|
|
611
|
+
lines.append(f"- **High Impact:** {high}")
|
|
612
|
+
lines.append(f"- **Requires Immediate Action:** {immediate}")
|
|
613
|
+
|
|
614
|
+
# Top risk
|
|
615
|
+
if assessments:
|
|
616
|
+
top = assessments[0]
|
|
617
|
+
lines.append(
|
|
618
|
+
f"\n### Highest Priority Finding\n"
|
|
619
|
+
f"**{top.finding.title}** at `{top.finding.url}`\n"
|
|
620
|
+
f"- Exploitability: {top.exploitability.value}\n"
|
|
621
|
+
f"- Business Impact: {top.business_criticality.value}\n"
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
return "\n".join(lines)
|
|
625
|
+
|
|
626
|
+
def _generate_recommendations(
|
|
627
|
+
self,
|
|
628
|
+
assessments: list[RiskAssessment],
|
|
629
|
+
) -> list[str]:
|
|
630
|
+
"""Generate top remediation recommendations"""
|
|
631
|
+
recommendations = []
|
|
632
|
+
|
|
633
|
+
# Group by priority
|
|
634
|
+
priority_1 = [a for a in assessments if a.remediation_priority == 1]
|
|
635
|
+
quick_wins = [a for a in assessments if a.quick_win]
|
|
636
|
+
|
|
637
|
+
if priority_1:
|
|
638
|
+
recommendations.append(
|
|
639
|
+
f"Address {len(priority_1)} critical-priority items immediately"
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
if quick_wins:
|
|
643
|
+
recommendations.append(
|
|
644
|
+
f"Resolve {len(quick_wins)} quick-win items to reduce attack surface"
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# Add specific recommendations based on finding types
|
|
648
|
+
vuln_types = {a.finding.vuln_type for a in assessments}
|
|
649
|
+
|
|
650
|
+
if VulnerabilityType.SQL_INJECTION in vuln_types:
|
|
651
|
+
recommendations.append(
|
|
652
|
+
"Implement parameterized queries to eliminate SQL injection"
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if VulnerabilityType.XSS_STORED in vuln_types or VulnerabilityType.XSS_REFLECTED in vuln_types:
|
|
656
|
+
recommendations.append(
|
|
657
|
+
"Deploy Content Security Policy and output encoding"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
if VulnerabilityType.IDOR in vuln_types:
|
|
661
|
+
recommendations.append(
|
|
662
|
+
"Implement proper authorization checks on all object access"
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if VulnerabilityType.DEFAULT_CREDENTIALS in vuln_types:
|
|
666
|
+
recommendations.append(
|
|
667
|
+
"Change all default credentials immediately"
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
return recommendations[:10] # Top 10
|