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,775 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Vulnerability Chaining Engine
|
|
3
|
+
|
|
4
|
+
Analyzes findings to identify attack chains - sequences of vulnerabilities
|
|
5
|
+
that can be combined for greater impact.
|
|
6
|
+
|
|
7
|
+
Example Chains:
|
|
8
|
+
- SSRF → Internal Service Access → Sensitive Data Exposure
|
|
9
|
+
- SQL Injection → Authentication Bypass → Privilege Escalation
|
|
10
|
+
- XSS → Session Hijacking → Account Takeover
|
|
11
|
+
- File Upload → RCE → Full Server Compromise
|
|
12
|
+
|
|
13
|
+
This module helps pentesters demonstrate real-world attack impact
|
|
14
|
+
by showing how multiple "medium" findings can combine into "critical" risks.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Any
|
|
23
|
+
import hashlib
|
|
24
|
+
|
|
25
|
+
# Works with dict-based findings, not typed Finding class
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# Vulnerability Types (for chain rules)
|
|
33
|
+
# ============================================================================
|
|
34
|
+
|
|
35
|
+
class VulnerabilityType(Enum):
|
|
36
|
+
"""Types of vulnerabilities for chain detection"""
|
|
37
|
+
# Injection
|
|
38
|
+
SQL_INJECTION = "sql_injection"
|
|
39
|
+
XSS_STORED = "xss_stored"
|
|
40
|
+
XSS_REFLECTED = "xss_reflected"
|
|
41
|
+
COMMAND_INJECTION = "command_injection"
|
|
42
|
+
LDAP_INJECTION = "ldap_injection"
|
|
43
|
+
XPATH_INJECTION = "xpath_injection"
|
|
44
|
+
|
|
45
|
+
# Authentication/Authorization
|
|
46
|
+
AUTH_BYPASS = "auth_bypass"
|
|
47
|
+
BROKEN_AUTH = "broken_authentication"
|
|
48
|
+
PRIVILEGE_ESCALATION = "privilege_escalation"
|
|
49
|
+
IDOR = "idor"
|
|
50
|
+
|
|
51
|
+
# Information Disclosure
|
|
52
|
+
INFORMATION_DISCLOSURE = "information_disclosure"
|
|
53
|
+
PATH_TRAVERSAL = "path_traversal"
|
|
54
|
+
SOURCE_CODE_DISCLOSURE = "source_code_disclosure"
|
|
55
|
+
|
|
56
|
+
# Server-Side
|
|
57
|
+
SSRF = "ssrf"
|
|
58
|
+
RCE = "rce"
|
|
59
|
+
FILE_UPLOAD = "file_upload"
|
|
60
|
+
FILE_INCLUSION = "file_inclusion"
|
|
61
|
+
LFI = "lfi"
|
|
62
|
+
RFI = "rfi"
|
|
63
|
+
DESERIALIZATION = "deserialization"
|
|
64
|
+
INSECURE_DESERIALIZATION = "insecure_deserialization"
|
|
65
|
+
XXE = "xxe"
|
|
66
|
+
|
|
67
|
+
# Client-Side
|
|
68
|
+
CSRF = "csrf"
|
|
69
|
+
CLICKJACKING = "clickjacking"
|
|
70
|
+
OPEN_REDIRECT = "open_redirect"
|
|
71
|
+
|
|
72
|
+
# Cryptographic
|
|
73
|
+
WEAK_CRYPTO = "weak_cryptography"
|
|
74
|
+
HARDCODED_SECRETS = "hardcoded_secrets"
|
|
75
|
+
|
|
76
|
+
# Configuration
|
|
77
|
+
MISCONFIGURATION = "misconfiguration"
|
|
78
|
+
DEFAULT_CREDENTIALS = "default_credentials"
|
|
79
|
+
EXPOSED_ADMIN = "exposed_admin_panel"
|
|
80
|
+
|
|
81
|
+
# Other
|
|
82
|
+
DOS = "denial_of_service"
|
|
83
|
+
BUSINESS_LOGIC = "business_logic"
|
|
84
|
+
UNKNOWN = "unknown"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ChainType(Enum):
|
|
88
|
+
"""Types of vulnerability chains"""
|
|
89
|
+
DATA_EXFILTRATION = "data_exfiltration"
|
|
90
|
+
ACCOUNT_TAKEOVER = "account_takeover"
|
|
91
|
+
PRIVILEGE_ESCALATION = "privilege_escalation"
|
|
92
|
+
REMOTE_CODE_EXECUTION = "remote_code_execution"
|
|
93
|
+
INTERNAL_NETWORK_ACCESS = "internal_network_access"
|
|
94
|
+
DENIAL_OF_SERVICE = "denial_of_service"
|
|
95
|
+
SUPPLY_CHAIN = "supply_chain"
|
|
96
|
+
LATERAL_MOVEMENT = "lateral_movement"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ChainImpact(Enum):
|
|
100
|
+
"""Business impact levels"""
|
|
101
|
+
CATASTROPHIC = "catastrophic" # Full compromise, data breach
|
|
102
|
+
SEVERE = "severe" # Significant access, major data exposure
|
|
103
|
+
SIGNIFICANT = "significant" # Limited access, some data exposure
|
|
104
|
+
MODERATE = "moderate" # Potential for escalation
|
|
105
|
+
MINIMAL = "minimal" # Low impact chain
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class ChainLink:
|
|
110
|
+
"""A single step in an attack chain"""
|
|
111
|
+
finding: Finding
|
|
112
|
+
step_number: int
|
|
113
|
+
action: str # What the attacker does at this step
|
|
114
|
+
outcome: str # What they achieve
|
|
115
|
+
prerequisites: list[str] = field(default_factory=list)
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict[str, Any]:
|
|
118
|
+
return {
|
|
119
|
+
"step": self.step_number,
|
|
120
|
+
"vulnerability": self.finding.title,
|
|
121
|
+
"severity": self.finding.severity.value,
|
|
122
|
+
"url": self.finding.url,
|
|
123
|
+
"action": self.action,
|
|
124
|
+
"outcome": self.outcome,
|
|
125
|
+
"prerequisites": self.prerequisites,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class AttackChain:
|
|
131
|
+
"""A complete attack chain combining multiple vulnerabilities"""
|
|
132
|
+
chain_id: str
|
|
133
|
+
name: str
|
|
134
|
+
chain_type: ChainType
|
|
135
|
+
links: list[ChainLink]
|
|
136
|
+
|
|
137
|
+
# Impact assessment
|
|
138
|
+
combined_severity: Severity
|
|
139
|
+
impact: ChainImpact
|
|
140
|
+
business_impact: str
|
|
141
|
+
|
|
142
|
+
# Metadata
|
|
143
|
+
confidence: float # 0.0 to 1.0
|
|
144
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
145
|
+
|
|
146
|
+
# Narrative
|
|
147
|
+
attack_narrative: str = ""
|
|
148
|
+
remediation_priority: str = ""
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def length(self) -> int:
|
|
152
|
+
return len(self.links)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def entry_point(self) -> Finding:
|
|
156
|
+
return self.links[0].finding if self.links else None
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def final_outcome(self) -> str:
|
|
160
|
+
return self.links[-1].outcome if self.links else ""
|
|
161
|
+
|
|
162
|
+
def get_cvss_amplification(self) -> float:
|
|
163
|
+
"""
|
|
164
|
+
Calculate how much the chain amplifies individual CVSS scores.
|
|
165
|
+
Chains often have impact greater than sum of parts.
|
|
166
|
+
"""
|
|
167
|
+
if not self.links:
|
|
168
|
+
return 1.0
|
|
169
|
+
|
|
170
|
+
individual_max = max(
|
|
171
|
+
(l.finding.cvss_score or 0) for l in self.links
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Chain severity bonus based on type
|
|
175
|
+
chain_bonus = {
|
|
176
|
+
ChainType.REMOTE_CODE_EXECUTION: 2.0,
|
|
177
|
+
ChainType.DATA_EXFILTRATION: 1.8,
|
|
178
|
+
ChainType.ACCOUNT_TAKEOVER: 1.7,
|
|
179
|
+
ChainType.PRIVILEGE_ESCALATION: 1.6,
|
|
180
|
+
ChainType.INTERNAL_NETWORK_ACCESS: 1.5,
|
|
181
|
+
ChainType.LATERAL_MOVEMENT: 1.4,
|
|
182
|
+
ChainType.SUPPLY_CHAIN: 1.9,
|
|
183
|
+
ChainType.DENIAL_OF_SERVICE: 1.2,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return chain_bonus.get(self.chain_type, 1.3)
|
|
187
|
+
|
|
188
|
+
def to_dict(self) -> dict[str, Any]:
|
|
189
|
+
return {
|
|
190
|
+
"chain_id": self.chain_id,
|
|
191
|
+
"name": self.name,
|
|
192
|
+
"chain_type": self.chain_type.value,
|
|
193
|
+
"length": self.length,
|
|
194
|
+
"combined_severity": self.combined_severity.value,
|
|
195
|
+
"impact": self.impact.value,
|
|
196
|
+
"business_impact": self.business_impact,
|
|
197
|
+
"confidence": self.confidence,
|
|
198
|
+
"cvss_amplification": self.get_cvss_amplification(),
|
|
199
|
+
"links": [link.to_dict() for link in self.links],
|
|
200
|
+
"attack_narrative": self.attack_narrative,
|
|
201
|
+
"remediation_priority": self.remediation_priority,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ============================================================================
|
|
206
|
+
# Chain Detection Rules
|
|
207
|
+
# ============================================================================
|
|
208
|
+
|
|
209
|
+
# Define which vulnerability types can lead to others
|
|
210
|
+
CHAIN_RULES: dict[VulnerabilityType, list[tuple[VulnerabilityType, str, str]]] = {
|
|
211
|
+
# SSRF can lead to...
|
|
212
|
+
VulnerabilityType.SSRF: [
|
|
213
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
214
|
+
"Use SSRF to access internal endpoints",
|
|
215
|
+
"Read internal service data"),
|
|
216
|
+
(VulnerabilityType.RCE,
|
|
217
|
+
"SSRF to internal admin panel with RCE",
|
|
218
|
+
"Execute commands on internal systems"),
|
|
219
|
+
(VulnerabilityType.SQL_INJECTION,
|
|
220
|
+
"SSRF to internal database interface",
|
|
221
|
+
"Query internal databases"),
|
|
222
|
+
],
|
|
223
|
+
|
|
224
|
+
# SQL Injection can lead to...
|
|
225
|
+
VulnerabilityType.SQL_INJECTION: [
|
|
226
|
+
(VulnerabilityType.AUTH_BYPASS,
|
|
227
|
+
"Extract credentials from database",
|
|
228
|
+
"Authenticate as any user"),
|
|
229
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
230
|
+
"Dump database tables",
|
|
231
|
+
"Access all stored data"),
|
|
232
|
+
(VulnerabilityType.RCE,
|
|
233
|
+
"SQL injection to xp_cmdshell or file write",
|
|
234
|
+
"Execute system commands"),
|
|
235
|
+
(VulnerabilityType.PRIVILEGE_ESCALATION,
|
|
236
|
+
"Modify user roles in database",
|
|
237
|
+
"Elevate to admin privileges"),
|
|
238
|
+
],
|
|
239
|
+
|
|
240
|
+
# XSS can lead to...
|
|
241
|
+
VulnerabilityType.XSS_STORED: [
|
|
242
|
+
(VulnerabilityType.AUTH_BYPASS,
|
|
243
|
+
"Steal session cookies via XSS",
|
|
244
|
+
"Hijack user sessions"),
|
|
245
|
+
(VulnerabilityType.CSRF,
|
|
246
|
+
"Use XSS to perform actions as victim",
|
|
247
|
+
"Execute privileged actions"),
|
|
248
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
249
|
+
"Exfiltrate page data via XSS",
|
|
250
|
+
"Steal sensitive displayed information"),
|
|
251
|
+
],
|
|
252
|
+
VulnerabilityType.XSS_REFLECTED: [
|
|
253
|
+
(VulnerabilityType.AUTH_BYPASS,
|
|
254
|
+
"Phish credentials via reflected XSS",
|
|
255
|
+
"Capture user credentials"),
|
|
256
|
+
],
|
|
257
|
+
|
|
258
|
+
# File Upload can lead to...
|
|
259
|
+
VulnerabilityType.FILE_UPLOAD: [
|
|
260
|
+
(VulnerabilityType.RCE,
|
|
261
|
+
"Upload web shell",
|
|
262
|
+
"Execute arbitrary code"),
|
|
263
|
+
(VulnerabilityType.FILE_INCLUSION,
|
|
264
|
+
"Upload malicious include file",
|
|
265
|
+
"Include and execute uploaded code"),
|
|
266
|
+
],
|
|
267
|
+
|
|
268
|
+
# File Inclusion can lead to...
|
|
269
|
+
VulnerabilityType.FILE_INCLUSION: [
|
|
270
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
271
|
+
"Read sensitive configuration files",
|
|
272
|
+
"Access credentials and secrets"),
|
|
273
|
+
(VulnerabilityType.RCE,
|
|
274
|
+
"Include remote file or log poisoning",
|
|
275
|
+
"Execute arbitrary code"),
|
|
276
|
+
],
|
|
277
|
+
|
|
278
|
+
# Auth Bypass can lead to...
|
|
279
|
+
VulnerabilityType.AUTH_BYPASS: [
|
|
280
|
+
(VulnerabilityType.PRIVILEGE_ESCALATION,
|
|
281
|
+
"Access admin functionality",
|
|
282
|
+
"Perform administrative actions"),
|
|
283
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
284
|
+
"Access protected user data",
|
|
285
|
+
"View all user information"),
|
|
286
|
+
(VulnerabilityType.IDOR,
|
|
287
|
+
"Access other users' resources",
|
|
288
|
+
"Modify or steal user data"),
|
|
289
|
+
],
|
|
290
|
+
|
|
291
|
+
# IDOR can lead to...
|
|
292
|
+
VulnerabilityType.IDOR: [
|
|
293
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
294
|
+
"Enumerate and access all records",
|
|
295
|
+
"Mass data extraction"),
|
|
296
|
+
(VulnerabilityType.PRIVILEGE_ESCALATION,
|
|
297
|
+
"Modify own role/permissions",
|
|
298
|
+
"Elevate account privileges"),
|
|
299
|
+
],
|
|
300
|
+
|
|
301
|
+
# Open Redirect can lead to...
|
|
302
|
+
VulnerabilityType.OPEN_REDIRECT: [
|
|
303
|
+
(VulnerabilityType.AUTH_BYPASS,
|
|
304
|
+
"Redirect OAuth flow to attacker",
|
|
305
|
+
"Steal authentication tokens"),
|
|
306
|
+
(VulnerabilityType.XSS_REFLECTED,
|
|
307
|
+
"Chain with XSS via redirect",
|
|
308
|
+
"Execute JavaScript in context"),
|
|
309
|
+
],
|
|
310
|
+
|
|
311
|
+
# XXE can lead to...
|
|
312
|
+
VulnerabilityType.XXE: [
|
|
313
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
314
|
+
"Read local files via XXE",
|
|
315
|
+
"Access server files"),
|
|
316
|
+
(VulnerabilityType.SSRF,
|
|
317
|
+
"XXE to make server-side requests",
|
|
318
|
+
"Access internal network"),
|
|
319
|
+
(VulnerabilityType.RCE,
|
|
320
|
+
"XXE with expect:// wrapper",
|
|
321
|
+
"Execute system commands"),
|
|
322
|
+
],
|
|
323
|
+
|
|
324
|
+
# Command Injection leads to RCE
|
|
325
|
+
VulnerabilityType.COMMAND_INJECTION: [
|
|
326
|
+
(VulnerabilityType.RCE,
|
|
327
|
+
"Execute arbitrary commands",
|
|
328
|
+
"Full system compromise"),
|
|
329
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
330
|
+
"Read files and environment",
|
|
331
|
+
"Access secrets and configuration"),
|
|
332
|
+
],
|
|
333
|
+
|
|
334
|
+
# Weak Crypto can lead to...
|
|
335
|
+
VulnerabilityType.WEAK_CRYPTO: [
|
|
336
|
+
(VulnerabilityType.AUTH_BYPASS,
|
|
337
|
+
"Crack or forge authentication tokens",
|
|
338
|
+
"Authenticate as any user"),
|
|
339
|
+
(VulnerabilityType.INFORMATION_DISCLOSURE,
|
|
340
|
+
"Decrypt sensitive data",
|
|
341
|
+
"Access protected information"),
|
|
342
|
+
],
|
|
343
|
+
|
|
344
|
+
# Insecure Deserialization
|
|
345
|
+
VulnerabilityType.INSECURE_DESERIALIZATION: [
|
|
346
|
+
(VulnerabilityType.RCE,
|
|
347
|
+
"Craft malicious serialized object",
|
|
348
|
+
"Execute arbitrary code"),
|
|
349
|
+
],
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class VulnerabilityChainer:
|
|
354
|
+
"""
|
|
355
|
+
Analyzes findings to identify attack chains.
|
|
356
|
+
|
|
357
|
+
This class takes a list of individual findings and identifies
|
|
358
|
+
how they can be combined into more impactful attack scenarios.
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
chainer = VulnerabilityChainer()
|
|
362
|
+
chains = chainer.find_chains(findings)
|
|
363
|
+
for chain in chains:
|
|
364
|
+
print(f"Attack Chain: {chain.name}")
|
|
365
|
+
print(f"Impact: {chain.impact.value}")
|
|
366
|
+
for link in chain.links:
|
|
367
|
+
print(f" Step {link.step_number}: {link.action}")
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def __init__(self, max_chain_length: int = 5):
|
|
371
|
+
self.max_chain_length = max_chain_length
|
|
372
|
+
self._chain_counter = 0
|
|
373
|
+
|
|
374
|
+
def find_chains(
|
|
375
|
+
self,
|
|
376
|
+
findings: list[Finding],
|
|
377
|
+
min_confidence: float = 0.5,
|
|
378
|
+
) -> list[AttackChain]:
|
|
379
|
+
"""
|
|
380
|
+
Find all possible attack chains in the findings.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
findings: List of vulnerability findings
|
|
384
|
+
min_confidence: Minimum confidence threshold for chains
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
List of identified attack chains, sorted by impact
|
|
388
|
+
"""
|
|
389
|
+
if not findings:
|
|
390
|
+
return []
|
|
391
|
+
|
|
392
|
+
chains = []
|
|
393
|
+
|
|
394
|
+
# Group findings by vulnerability type
|
|
395
|
+
by_type: dict[VulnerabilityType, list[Finding]] = {}
|
|
396
|
+
for f in findings:
|
|
397
|
+
if f.vuln_type not in by_type:
|
|
398
|
+
by_type[f.vuln_type] = []
|
|
399
|
+
by_type[f.vuln_type].append(f)
|
|
400
|
+
|
|
401
|
+
# Find chains starting from each finding
|
|
402
|
+
for finding in findings:
|
|
403
|
+
found_chains = self._find_chains_from(finding, by_type, [])
|
|
404
|
+
chains.extend(found_chains)
|
|
405
|
+
|
|
406
|
+
# Deduplicate and filter
|
|
407
|
+
unique_chains = self._deduplicate_chains(chains)
|
|
408
|
+
filtered_chains = [c for c in unique_chains if c.confidence >= min_confidence]
|
|
409
|
+
|
|
410
|
+
# Sort by impact (most severe first)
|
|
411
|
+
impact_order = [
|
|
412
|
+
ChainImpact.CATASTROPHIC,
|
|
413
|
+
ChainImpact.SEVERE,
|
|
414
|
+
ChainImpact.SIGNIFICANT,
|
|
415
|
+
ChainImpact.MODERATE,
|
|
416
|
+
ChainImpact.MINIMAL,
|
|
417
|
+
]
|
|
418
|
+
filtered_chains.sort(key=lambda c: impact_order.index(c.impact))
|
|
419
|
+
|
|
420
|
+
logger.info(f"Found {len(filtered_chains)} attack chains")
|
|
421
|
+
return filtered_chains
|
|
422
|
+
|
|
423
|
+
def _find_chains_from(
|
|
424
|
+
self,
|
|
425
|
+
start: Finding,
|
|
426
|
+
by_type: dict[VulnerabilityType, list[Finding]],
|
|
427
|
+
current_path: list[Finding],
|
|
428
|
+
) -> list[AttackChain]:
|
|
429
|
+
"""Recursively find chains starting from a finding"""
|
|
430
|
+
chains = []
|
|
431
|
+
|
|
432
|
+
# Prevent cycles and limit depth
|
|
433
|
+
if start in current_path or len(current_path) >= self.max_chain_length:
|
|
434
|
+
return chains
|
|
435
|
+
|
|
436
|
+
current_path = current_path + [start]
|
|
437
|
+
|
|
438
|
+
# Get possible next steps from chain rules
|
|
439
|
+
next_types = CHAIN_RULES.get(start.vuln_type, [])
|
|
440
|
+
|
|
441
|
+
for next_type, action, outcome in next_types:
|
|
442
|
+
# Find findings that match the next type
|
|
443
|
+
matching = by_type.get(next_type, [])
|
|
444
|
+
|
|
445
|
+
for next_finding in matching:
|
|
446
|
+
# Check if findings are related (same host/path)
|
|
447
|
+
if self._findings_related(start, next_finding):
|
|
448
|
+
# Recurse to find longer chains
|
|
449
|
+
deeper_chains = self._find_chains_from(
|
|
450
|
+
next_finding, by_type, current_path
|
|
451
|
+
)
|
|
452
|
+
chains.extend(deeper_chains)
|
|
453
|
+
|
|
454
|
+
# Even without a matching finding, if we have 2+ steps, record the chain
|
|
455
|
+
if len(current_path) >= 2:
|
|
456
|
+
chain = self._build_chain(current_path)
|
|
457
|
+
if chain:
|
|
458
|
+
chains.append(chain)
|
|
459
|
+
|
|
460
|
+
return chains
|
|
461
|
+
|
|
462
|
+
def _findings_related(self, f1: Finding, f2: Finding) -> bool:
|
|
463
|
+
"""Check if two findings are related (could be chained)"""
|
|
464
|
+
# Same host
|
|
465
|
+
try:
|
|
466
|
+
from urllib.parse import urlparse
|
|
467
|
+
host1 = urlparse(f1.url).netloc
|
|
468
|
+
host2 = urlparse(f2.url).netloc
|
|
469
|
+
if host1 and host2 and host1 == host2:
|
|
470
|
+
return True
|
|
471
|
+
except Exception:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
# Same parameter
|
|
475
|
+
if f1.parameter and f2.parameter and f1.parameter == f2.parameter:
|
|
476
|
+
return True
|
|
477
|
+
|
|
478
|
+
# If same source scanner found both, likely related
|
|
479
|
+
if f1.source == f2.source:
|
|
480
|
+
return True
|
|
481
|
+
|
|
482
|
+
return False
|
|
483
|
+
|
|
484
|
+
def _build_chain(self, findings: list[Finding]) -> AttackChain | None:
|
|
485
|
+
"""Build an AttackChain from a list of findings"""
|
|
486
|
+
if len(findings) < 2:
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
self._chain_counter += 1
|
|
490
|
+
chain_id = f"chain_{self._chain_counter:04d}"
|
|
491
|
+
|
|
492
|
+
# Build links
|
|
493
|
+
links = []
|
|
494
|
+
for i, finding in enumerate(findings):
|
|
495
|
+
# Get action/outcome from chain rules
|
|
496
|
+
action, outcome = self._get_step_details(
|
|
497
|
+
finding,
|
|
498
|
+
findings[i + 1] if i + 1 < len(findings) else None
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
link = ChainLink(
|
|
502
|
+
finding=finding,
|
|
503
|
+
step_number=i + 1,
|
|
504
|
+
action=action,
|
|
505
|
+
outcome=outcome,
|
|
506
|
+
prerequisites=[f"Step {j+1} completed" for j in range(i)],
|
|
507
|
+
)
|
|
508
|
+
links.append(link)
|
|
509
|
+
|
|
510
|
+
# Determine chain type and impact
|
|
511
|
+
chain_type = self._classify_chain_type(findings)
|
|
512
|
+
impact = self._assess_impact(findings, chain_type)
|
|
513
|
+
combined_severity = self._calculate_combined_severity(findings, chain_type)
|
|
514
|
+
|
|
515
|
+
# Generate narrative
|
|
516
|
+
narrative = self._generate_narrative(links, chain_type)
|
|
517
|
+
|
|
518
|
+
# Calculate confidence
|
|
519
|
+
confidence = self._calculate_confidence(findings)
|
|
520
|
+
|
|
521
|
+
return AttackChain(
|
|
522
|
+
chain_id=chain_id,
|
|
523
|
+
name=self._generate_chain_name(chain_type, findings),
|
|
524
|
+
chain_type=chain_type,
|
|
525
|
+
links=links,
|
|
526
|
+
combined_severity=combined_severity,
|
|
527
|
+
impact=impact,
|
|
528
|
+
business_impact=self._describe_business_impact(chain_type, impact),
|
|
529
|
+
confidence=confidence,
|
|
530
|
+
attack_narrative=narrative,
|
|
531
|
+
remediation_priority=self._suggest_remediation(links),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def _get_step_details(
|
|
535
|
+
self,
|
|
536
|
+
current: Finding,
|
|
537
|
+
next_finding: Finding | None,
|
|
538
|
+
) -> tuple[str, str]:
|
|
539
|
+
"""Get action and outcome for a chain step"""
|
|
540
|
+
rules = CHAIN_RULES.get(current.vuln_type, [])
|
|
541
|
+
|
|
542
|
+
if next_finding:
|
|
543
|
+
for next_type, action, outcome in rules:
|
|
544
|
+
if next_finding.vuln_type == next_type:
|
|
545
|
+
return action, outcome
|
|
546
|
+
|
|
547
|
+
# Default based on vulnerability type
|
|
548
|
+
defaults = {
|
|
549
|
+
VulnerabilityType.SQL_INJECTION: ("Execute SQL injection", "Access database"),
|
|
550
|
+
VulnerabilityType.XSS_STORED: ("Inject stored XSS payload", "Execute JavaScript in user browsers"),
|
|
551
|
+
VulnerabilityType.SSRF: ("Exploit SSRF vulnerability", "Make server-side requests"),
|
|
552
|
+
VulnerabilityType.RCE: ("Execute remote code", "Full system access"),
|
|
553
|
+
VulnerabilityType.AUTH_BYPASS: ("Bypass authentication", "Access as authenticated user"),
|
|
554
|
+
VulnerabilityType.IDOR: ("Access unauthorized resources", "View/modify other users' data"),
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return defaults.get(
|
|
558
|
+
current.vuln_type,
|
|
559
|
+
(f"Exploit {current.vuln_type.value}", "Advance attack")
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
def _classify_chain_type(self, findings: list[Finding]) -> ChainType:
|
|
563
|
+
"""Determine the type of attack chain"""
|
|
564
|
+
types = {f.vuln_type for f in findings}
|
|
565
|
+
|
|
566
|
+
# Check for RCE chain
|
|
567
|
+
if VulnerabilityType.RCE in types or VulnerabilityType.COMMAND_INJECTION in types:
|
|
568
|
+
return ChainType.REMOTE_CODE_EXECUTION
|
|
569
|
+
|
|
570
|
+
# Check for data exfiltration
|
|
571
|
+
if VulnerabilityType.SQL_INJECTION in types and VulnerabilityType.INFORMATION_DISCLOSURE in types:
|
|
572
|
+
return ChainType.DATA_EXFILTRATION
|
|
573
|
+
|
|
574
|
+
# Check for account takeover
|
|
575
|
+
if VulnerabilityType.AUTH_BYPASS in types or (
|
|
576
|
+
VulnerabilityType.XSS_STORED in types and VulnerabilityType.CSRF in types
|
|
577
|
+
):
|
|
578
|
+
return ChainType.ACCOUNT_TAKEOVER
|
|
579
|
+
|
|
580
|
+
# Check for privilege escalation
|
|
581
|
+
if VulnerabilityType.PRIVILEGE_ESCALATION in types or (
|
|
582
|
+
VulnerabilityType.IDOR in types and VulnerabilityType.AUTH_BYPASS in types
|
|
583
|
+
):
|
|
584
|
+
return ChainType.PRIVILEGE_ESCALATION
|
|
585
|
+
|
|
586
|
+
# Check for internal network access
|
|
587
|
+
if VulnerabilityType.SSRF in types:
|
|
588
|
+
return ChainType.INTERNAL_NETWORK_ACCESS
|
|
589
|
+
|
|
590
|
+
# Default based on most severe finding
|
|
591
|
+
return ChainType.DATA_EXFILTRATION
|
|
592
|
+
|
|
593
|
+
def _assess_impact(
|
|
594
|
+
self,
|
|
595
|
+
findings: list[Finding],
|
|
596
|
+
chain_type: ChainType,
|
|
597
|
+
) -> ChainImpact:
|
|
598
|
+
"""Assess the business impact of the chain"""
|
|
599
|
+
severities = [f.severity for f in findings]
|
|
600
|
+
|
|
601
|
+
# Catastrophic if RCE or multiple criticals
|
|
602
|
+
if chain_type == ChainType.REMOTE_CODE_EXECUTION:
|
|
603
|
+
return ChainImpact.CATASTROPHIC
|
|
604
|
+
if severities.count(Severity.CRITICAL) >= 2:
|
|
605
|
+
return ChainImpact.CATASTROPHIC
|
|
606
|
+
|
|
607
|
+
# Severe if data exfil or account takeover with high severity
|
|
608
|
+
if chain_type in [ChainType.DATA_EXFILTRATION, ChainType.ACCOUNT_TAKEOVER]:
|
|
609
|
+
if Severity.HIGH in severities or Severity.CRITICAL in severities:
|
|
610
|
+
return ChainImpact.SEVERE
|
|
611
|
+
|
|
612
|
+
# Significant if privilege escalation
|
|
613
|
+
if chain_type == ChainType.PRIVILEGE_ESCALATION:
|
|
614
|
+
return ChainImpact.SIGNIFICANT
|
|
615
|
+
|
|
616
|
+
# Based on highest severity in chain
|
|
617
|
+
if Severity.CRITICAL in severities:
|
|
618
|
+
return ChainImpact.SEVERE
|
|
619
|
+
if Severity.HIGH in severities:
|
|
620
|
+
return ChainImpact.SIGNIFICANT
|
|
621
|
+
if Severity.MEDIUM in severities:
|
|
622
|
+
return ChainImpact.MODERATE
|
|
623
|
+
|
|
624
|
+
return ChainImpact.MINIMAL
|
|
625
|
+
|
|
626
|
+
def _calculate_combined_severity(
|
|
627
|
+
self,
|
|
628
|
+
findings: list[Finding],
|
|
629
|
+
chain_type: ChainType,
|
|
630
|
+
) -> Severity:
|
|
631
|
+
"""Calculate the combined severity of the chain"""
|
|
632
|
+
# Chain severity is often higher than individual findings
|
|
633
|
+
max_severity = max(f.severity for f in findings)
|
|
634
|
+
|
|
635
|
+
# Escalate severity for high-impact chain types
|
|
636
|
+
escalation_types = [
|
|
637
|
+
ChainType.REMOTE_CODE_EXECUTION,
|
|
638
|
+
ChainType.DATA_EXFILTRATION,
|
|
639
|
+
ChainType.ACCOUNT_TAKEOVER,
|
|
640
|
+
]
|
|
641
|
+
|
|
642
|
+
if chain_type in escalation_types:
|
|
643
|
+
severity_order = [Severity.INFO, Severity.LOW, Severity.MEDIUM, Severity.HIGH, Severity.CRITICAL]
|
|
644
|
+
current_idx = severity_order.index(max_severity)
|
|
645
|
+
escalated_idx = min(current_idx + 1, len(severity_order) - 1)
|
|
646
|
+
return severity_order[escalated_idx]
|
|
647
|
+
|
|
648
|
+
return max_severity
|
|
649
|
+
|
|
650
|
+
def _calculate_confidence(self, findings: list[Finding]) -> float:
|
|
651
|
+
"""Calculate confidence in the chain validity"""
|
|
652
|
+
# Base confidence on finding confirmations
|
|
653
|
+
confirmed_count = sum(1 for f in findings if f.confirmed)
|
|
654
|
+
base_confidence = confirmed_count / len(findings)
|
|
655
|
+
|
|
656
|
+
# Boost for AI-validated findings
|
|
657
|
+
ai_findings = [f for f in findings if f.ai_confidence]
|
|
658
|
+
if ai_findings:
|
|
659
|
+
ai_boost = sum(f.ai_confidence for f in ai_findings) / len(ai_findings)
|
|
660
|
+
base_confidence = (base_confidence + ai_boost) / 2
|
|
661
|
+
|
|
662
|
+
# Reduce confidence for longer chains (more assumptions)
|
|
663
|
+
length_penalty = max(0, (len(findings) - 2) * 0.1)
|
|
664
|
+
|
|
665
|
+
return max(0.3, min(1.0, base_confidence - length_penalty))
|
|
666
|
+
|
|
667
|
+
def _generate_chain_name(
|
|
668
|
+
self,
|
|
669
|
+
chain_type: ChainType,
|
|
670
|
+
findings: list[Finding],
|
|
671
|
+
) -> str:
|
|
672
|
+
"""Generate a descriptive name for the chain"""
|
|
673
|
+
type_names = {
|
|
674
|
+
ChainType.REMOTE_CODE_EXECUTION: "Remote Code Execution Chain",
|
|
675
|
+
ChainType.DATA_EXFILTRATION: "Data Exfiltration Chain",
|
|
676
|
+
ChainType.ACCOUNT_TAKEOVER: "Account Takeover Chain",
|
|
677
|
+
ChainType.PRIVILEGE_ESCALATION: "Privilege Escalation Chain",
|
|
678
|
+
ChainType.INTERNAL_NETWORK_ACCESS: "Internal Network Access Chain",
|
|
679
|
+
ChainType.LATERAL_MOVEMENT: "Lateral Movement Chain",
|
|
680
|
+
ChainType.DENIAL_OF_SERVICE: "Denial of Service Chain",
|
|
681
|
+
ChainType.SUPPLY_CHAIN: "Supply Chain Attack",
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
base_name = type_names.get(chain_type, "Attack Chain")
|
|
685
|
+
entry = findings[0].vuln_type.value.replace("_", " ").title()
|
|
686
|
+
|
|
687
|
+
return f"{base_name} via {entry}"
|
|
688
|
+
|
|
689
|
+
def _generate_narrative(
|
|
690
|
+
self,
|
|
691
|
+
links: list[ChainLink],
|
|
692
|
+
chain_type: ChainType,
|
|
693
|
+
) -> str:
|
|
694
|
+
"""Generate a human-readable attack narrative"""
|
|
695
|
+
lines = [
|
|
696
|
+
"## Attack Narrative\n",
|
|
697
|
+
"An attacker could exploit this chain of vulnerabilities as follows:\n",
|
|
698
|
+
]
|
|
699
|
+
|
|
700
|
+
for link in links:
|
|
701
|
+
lines.append(
|
|
702
|
+
f"**Step {link.step_number}:** {link.action}\n"
|
|
703
|
+
f"- Vulnerability: {link.finding.title}\n"
|
|
704
|
+
f"- URL: `{link.finding.url}`\n"
|
|
705
|
+
f"- Outcome: {link.outcome}\n"
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
lines.append(f"\n**Final Impact:** {links[-1].outcome}")
|
|
709
|
+
|
|
710
|
+
return "\n".join(lines)
|
|
711
|
+
|
|
712
|
+
def _describe_business_impact(
|
|
713
|
+
self,
|
|
714
|
+
chain_type: ChainType,
|
|
715
|
+
impact: ChainImpact,
|
|
716
|
+
) -> str:
|
|
717
|
+
"""Describe the business impact of the chain"""
|
|
718
|
+
descriptions = {
|
|
719
|
+
(ChainType.REMOTE_CODE_EXECUTION, ChainImpact.CATASTROPHIC):
|
|
720
|
+
"Complete server compromise. Attacker can access all data, install backdoors, "
|
|
721
|
+
"pivot to internal networks, and cause widespread damage.",
|
|
722
|
+
|
|
723
|
+
(ChainType.DATA_EXFILTRATION, ChainImpact.SEVERE):
|
|
724
|
+
"Mass data breach potential. Sensitive customer data, credentials, and "
|
|
725
|
+
"proprietary information could be extracted.",
|
|
726
|
+
|
|
727
|
+
(ChainType.ACCOUNT_TAKEOVER, ChainImpact.SEVERE):
|
|
728
|
+
"Any user account can be compromised. Attackers could access admin accounts, "
|
|
729
|
+
"steal user data, or perform actions as victims.",
|
|
730
|
+
|
|
731
|
+
(ChainType.PRIVILEGE_ESCALATION, ChainImpact.SIGNIFICANT):
|
|
732
|
+
"Unauthorized access to privileged functionality. Attackers could modify "
|
|
733
|
+
"configurations, access restricted data, or compromise other users.",
|
|
734
|
+
|
|
735
|
+
(ChainType.INTERNAL_NETWORK_ACCESS, ChainImpact.SEVERE):
|
|
736
|
+
"Access to internal network services. Attackers could reach databases, "
|
|
737
|
+
"admin panels, and other sensitive internal resources.",
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return descriptions.get(
|
|
741
|
+
(chain_type, impact),
|
|
742
|
+
f"Potential for {impact.value} impact through {chain_type.value} attack."
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
def _suggest_remediation(self, links: list[ChainLink]) -> str:
|
|
746
|
+
"""Suggest remediation priority based on chain analysis"""
|
|
747
|
+
# Breaking the first link breaks the chain
|
|
748
|
+
first = links[0].finding
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
f"**Priority:** Fix the entry point first.\n"
|
|
752
|
+
f"Remediating '{first.title}' at {first.url} will break this entire attack chain.\n"
|
|
753
|
+
f"This should be addressed with {first.severity.value.upper()} priority."
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
def _deduplicate_chains(self, chains: list[AttackChain]) -> list[AttackChain]:
|
|
757
|
+
"""Remove duplicate or subset chains"""
|
|
758
|
+
if not chains:
|
|
759
|
+
return []
|
|
760
|
+
|
|
761
|
+
# Create fingerprint for each chain
|
|
762
|
+
def chain_fingerprint(chain: AttackChain) -> str:
|
|
763
|
+
finding_ids = sorted(f.finding.fingerprint for f in chain.links)
|
|
764
|
+
return hashlib.md5("|".join(finding_ids).encode()).hexdigest()
|
|
765
|
+
|
|
766
|
+
seen = set()
|
|
767
|
+
unique = []
|
|
768
|
+
|
|
769
|
+
for chain in chains:
|
|
770
|
+
fp = chain_fingerprint(chain)
|
|
771
|
+
if fp not in seen:
|
|
772
|
+
seen.add(fp)
|
|
773
|
+
unique.append(chain)
|
|
774
|
+
|
|
775
|
+
return unique
|