scythe-ttp 0.16.1__py3-none-any.whl → 0.18.1__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.
- scythe/auth/cookie_jwt.py +43 -14
- scythe/cli/main.py +192 -9
- scythe/core/executor.py +143 -2
- scythe/core/ttp.py +61 -3
- scythe/journeys/actions.py +151 -69
- scythe/journeys/executor.py +15 -0
- scythe/orchestrators/base.py +18 -0
- scythe/ttps/web/__init__.py +12 -0
- scythe/ttps/web/login_bruteforce.py +138 -7
- scythe/ttps/web/request_flooding.py +503 -0
- scythe/ttps/web/sql_injection.py +232 -15
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/METADATA +2 -1
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/RECORD +17 -16
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/WHEEL +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/entry_points.txt +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/licenses/LICENSE +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.18.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
from selenium.webdriver.common.by import By
|
|
2
|
+
from selenium.webdriver.remote.webdriver import WebDriver
|
|
3
|
+
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
|
4
|
+
from typing import Dict, Any, Optional, List, Generator
|
|
5
|
+
import requests
|
|
6
|
+
import time
|
|
7
|
+
import threading
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
|
+
import random
|
|
10
|
+
|
|
11
|
+
from ...core.ttp import TTP
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RequestFloodingTTP(TTP):
|
|
15
|
+
"""
|
|
16
|
+
A TTP that emulates DDoS and request flooding attacks to test application resilience.
|
|
17
|
+
|
|
18
|
+
This TTP tests an application's ability to withstand high-volume request attacks
|
|
19
|
+
and rate limiting mechanisms by sending multiple rapid requests to target endpoints.
|
|
20
|
+
|
|
21
|
+
Supports two execution modes:
|
|
22
|
+
- UI mode: Uses Selenium to repeatedly interact with web pages/forms
|
|
23
|
+
- API mode: Makes rapid HTTP requests to API endpoints
|
|
24
|
+
|
|
25
|
+
Attack patterns include:
|
|
26
|
+
- Volume flooding: High number of requests in short time
|
|
27
|
+
- Slowloris-style: Slow, prolonged connections
|
|
28
|
+
- Burst flooding: Intermittent bursts of high traffic
|
|
29
|
+
- Resource exhaustion: Targeting expensive operations
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self,
|
|
33
|
+
target_endpoints: List[str] = None,
|
|
34
|
+
request_count: int = 100,
|
|
35
|
+
requests_per_second: float = 10.0,
|
|
36
|
+
attack_pattern: str = 'volume',
|
|
37
|
+
concurrent_threads: int = 5,
|
|
38
|
+
payload_data: Optional[Dict[str, Any]] = None,
|
|
39
|
+
http_method: str = 'GET',
|
|
40
|
+
form_selector: str = None,
|
|
41
|
+
submit_selector: str = None,
|
|
42
|
+
expected_result: bool = False,
|
|
43
|
+
authentication=None,
|
|
44
|
+
execution_mode: str = 'api',
|
|
45
|
+
success_indicators: Optional[Dict[str, Any]] = None,
|
|
46
|
+
user_agents: Optional[List[str]] = None,
|
|
47
|
+
randomize_timing: bool = True):
|
|
48
|
+
"""
|
|
49
|
+
Initialize the Request Flooding TTP.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
target_endpoints: List of endpoint paths to target (e.g., ['/api/search', '/login'])
|
|
53
|
+
request_count: Total number of requests to send per endpoint
|
|
54
|
+
requests_per_second: Target rate of requests (used for timing calculations)
|
|
55
|
+
attack_pattern: Type of attack - 'volume', 'slowloris', 'burst', 'resource_exhaustion'
|
|
56
|
+
concurrent_threads: Number of concurrent threads to use for requests
|
|
57
|
+
payload_data: Data to send in request body (API mode) or form fields (UI mode)
|
|
58
|
+
http_method: HTTP method to use ('GET', 'POST', 'PUT', 'DELETE')
|
|
59
|
+
form_selector: CSS selector for form to repeatedly submit (UI mode)
|
|
60
|
+
submit_selector: CSS selector for submit button (UI mode)
|
|
61
|
+
expected_result: False = expect app to resist/rate-limit, True = expect success
|
|
62
|
+
authentication: Optional authentication mechanism
|
|
63
|
+
execution_mode: 'ui' or 'api'
|
|
64
|
+
success_indicators: Dict defining what constitutes successful flooding detection
|
|
65
|
+
user_agents: List of user agents to rotate through (helps bypass simple filtering)
|
|
66
|
+
randomize_timing: Whether to randomize request timing to appear more natural
|
|
67
|
+
"""
|
|
68
|
+
super().__init__(
|
|
69
|
+
name="Request Flooding / DDoS Test",
|
|
70
|
+
description=f"Tests application resilience against {attack_pattern} flooding attacks with {request_count} requests",
|
|
71
|
+
expected_result=expected_result,
|
|
72
|
+
authentication=authentication,
|
|
73
|
+
execution_mode=execution_mode
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Core configuration
|
|
77
|
+
self.target_endpoints = target_endpoints or ['/']
|
|
78
|
+
self.request_count = request_count
|
|
79
|
+
self.requests_per_second = requests_per_second
|
|
80
|
+
self.attack_pattern = attack_pattern.lower()
|
|
81
|
+
self.concurrent_threads = min(concurrent_threads, 20) # Cap to prevent system overload
|
|
82
|
+
self.payload_data = payload_data or {}
|
|
83
|
+
self.http_method = http_method.upper()
|
|
84
|
+
|
|
85
|
+
# UI mode configuration
|
|
86
|
+
self.form_selector = form_selector
|
|
87
|
+
self.submit_selector = submit_selector
|
|
88
|
+
|
|
89
|
+
# Attack sophistication
|
|
90
|
+
self.user_agents = user_agents or [
|
|
91
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
92
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
93
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
|
|
94
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101'
|
|
95
|
+
]
|
|
96
|
+
self.randomize_timing = randomize_timing
|
|
97
|
+
|
|
98
|
+
# Success/failure detection
|
|
99
|
+
self.success_indicators = success_indicators or {
|
|
100
|
+
'rate_limit_status_codes': [429, 503, 502],
|
|
101
|
+
'error_keywords': ['rate limit', 'too many requests', 'service unavailable'],
|
|
102
|
+
'max_response_time': 30.0, # Consider slow responses as potential DoS impact
|
|
103
|
+
'expected_success_rate': 0.1 # Expect most requests to be blocked if defenses work
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Attack results tracking
|
|
107
|
+
self.attack_results = {
|
|
108
|
+
'total_requests': 0,
|
|
109
|
+
'successful_requests': 0,
|
|
110
|
+
'failed_requests': 0,
|
|
111
|
+
'rate_limited_requests': 0,
|
|
112
|
+
'error_responses': 0,
|
|
113
|
+
'avg_response_time': 0.0,
|
|
114
|
+
'max_response_time': 0.0,
|
|
115
|
+
'responses_by_status': {},
|
|
116
|
+
'attack_effectiveness': 0.0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def get_payloads(self) -> Generator[Dict[str, Any], None, None]:
|
|
120
|
+
"""
|
|
121
|
+
Generates attack payloads based on the configured attack pattern.
|
|
122
|
+
Each payload contains timing and configuration data for the attack.
|
|
123
|
+
"""
|
|
124
|
+
base_delay = 1.0 / self.requests_per_second if self.requests_per_second > 0 else 0.1
|
|
125
|
+
|
|
126
|
+
for i in range(self.request_count):
|
|
127
|
+
payload = {
|
|
128
|
+
'request_id': i,
|
|
129
|
+
'endpoint': self.target_endpoints[i % len(self.target_endpoints)],
|
|
130
|
+
'data': self.payload_data.copy(),
|
|
131
|
+
'user_agent': random.choice(self.user_agents),
|
|
132
|
+
'delay': self._calculate_delay(i, base_delay),
|
|
133
|
+
'timeout': self._calculate_timeout(i)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Add attack-pattern specific modifications
|
|
137
|
+
if self.attack_pattern == 'burst':
|
|
138
|
+
# Create bursts every 10 requests
|
|
139
|
+
if i % 10 == 0:
|
|
140
|
+
payload['delay'] = 0.05 # Very fast burst
|
|
141
|
+
else:
|
|
142
|
+
payload['delay'] = base_delay * 3 # Slower between bursts
|
|
143
|
+
|
|
144
|
+
elif self.attack_pattern == 'slowloris':
|
|
145
|
+
payload['timeout'] = 60.0 # Very long timeout
|
|
146
|
+
payload['delay'] = base_delay * 2 # Slower rate but longer connections
|
|
147
|
+
|
|
148
|
+
elif self.attack_pattern == 'resource_exhaustion':
|
|
149
|
+
# Add resource-intensive parameters
|
|
150
|
+
payload['data'].update({
|
|
151
|
+
'limit': 10000, # Request large datasets
|
|
152
|
+
'search': '*', # Broad search terms
|
|
153
|
+
'recursive': True
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
yield payload
|
|
157
|
+
|
|
158
|
+
def _calculate_delay(self, request_index: int, base_delay: float) -> float:
|
|
159
|
+
"""Calculate delay between requests based on pattern and randomization."""
|
|
160
|
+
if not self.randomize_timing:
|
|
161
|
+
return base_delay
|
|
162
|
+
|
|
163
|
+
# Add randomization (±25% of base delay)
|
|
164
|
+
jitter = base_delay * 0.25 * (random.random() - 0.5) * 2
|
|
165
|
+
return max(0.01, base_delay + jitter)
|
|
166
|
+
|
|
167
|
+
def _calculate_timeout(self, request_index: int) -> float:
|
|
168
|
+
"""Calculate request timeout based on attack pattern."""
|
|
169
|
+
if self.attack_pattern == 'slowloris':
|
|
170
|
+
return 60.0
|
|
171
|
+
elif self.attack_pattern == 'resource_exhaustion':
|
|
172
|
+
return 30.0
|
|
173
|
+
else:
|
|
174
|
+
return 10.0
|
|
175
|
+
|
|
176
|
+
def execute_step(self, driver: WebDriver, payload: Dict[str, Any]) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Executes a single flooding request in UI mode.
|
|
179
|
+
For UI mode, this repeatedly submits forms or navigates to pages.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
endpoint = payload['endpoint']
|
|
183
|
+
delay = payload['delay']
|
|
184
|
+
|
|
185
|
+
# Wait for the calculated delay
|
|
186
|
+
if delay > 0:
|
|
187
|
+
time.sleep(delay)
|
|
188
|
+
|
|
189
|
+
# Navigate to the target endpoint
|
|
190
|
+
current_url = driver.current_url
|
|
191
|
+
base_url = current_url.split('?')[0].rstrip('/')
|
|
192
|
+
target_url = f"{base_url}{endpoint}"
|
|
193
|
+
|
|
194
|
+
start_time = time.time()
|
|
195
|
+
driver.get(target_url)
|
|
196
|
+
|
|
197
|
+
# If we have form selectors, submit the form
|
|
198
|
+
if self.form_selector and self.submit_selector:
|
|
199
|
+
try:
|
|
200
|
+
# Fill form with payload data
|
|
201
|
+
for field_name, field_value in payload['data'].items():
|
|
202
|
+
try:
|
|
203
|
+
field = driver.find_element(By.NAME, field_name)
|
|
204
|
+
field.clear()
|
|
205
|
+
field.send_keys(str(field_value))
|
|
206
|
+
except NoSuchElementException:
|
|
207
|
+
# Try by ID if name doesn't work
|
|
208
|
+
try:
|
|
209
|
+
field = driver.find_element(By.ID, field_name)
|
|
210
|
+
field.clear()
|
|
211
|
+
field.send_keys(str(field_value))
|
|
212
|
+
except NoSuchElementException:
|
|
213
|
+
continue # Skip this field if not found
|
|
214
|
+
|
|
215
|
+
# Submit the form
|
|
216
|
+
submit_btn = driver.find_element(By.CSS_SELECTOR, self.submit_selector)
|
|
217
|
+
submit_btn.click()
|
|
218
|
+
|
|
219
|
+
except NoSuchElementException:
|
|
220
|
+
pass # Continue even if form submission fails
|
|
221
|
+
|
|
222
|
+
# Record timing
|
|
223
|
+
response_time = time.time() - start_time
|
|
224
|
+
self._record_ui_result(response_time, driver.current_url)
|
|
225
|
+
|
|
226
|
+
except TimeoutException:
|
|
227
|
+
self._record_ui_result(payload['timeout'], None, timeout=True)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
self._record_ui_result(0.0, None, error=str(e))
|
|
230
|
+
|
|
231
|
+
def verify_result(self, driver: WebDriver) -> bool:
|
|
232
|
+
"""
|
|
233
|
+
Verifies the outcome of the flooding attack in UI mode.
|
|
234
|
+
Checks for rate limiting, error pages, or performance degradation.
|
|
235
|
+
"""
|
|
236
|
+
try:
|
|
237
|
+
page_source = driver.page_source.lower()
|
|
238
|
+
current_url = driver.current_url.lower()
|
|
239
|
+
|
|
240
|
+
# Check for rate limiting indicators
|
|
241
|
+
rate_limit_indicators = [
|
|
242
|
+
'rate limit', 'too many requests', 'service unavailable',
|
|
243
|
+
'temporarily unavailable', 'error 429', 'error 503',
|
|
244
|
+
'please wait', 'slow down', 'blocked'
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for indicator in rate_limit_indicators:
|
|
248
|
+
if indicator in page_source or indicator in current_url:
|
|
249
|
+
return not self.expected_result # Rate limiting found
|
|
250
|
+
|
|
251
|
+
# If no rate limiting found and we expected defenses, that's a failure
|
|
252
|
+
return self.expected_result
|
|
253
|
+
|
|
254
|
+
except Exception:
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
def execute_step_api(self, session: requests.Session, payload: Dict[str, Any], context: Dict[str, Any]) -> requests.Response:
|
|
258
|
+
"""
|
|
259
|
+
Executes a single flooding request in API mode.
|
|
260
|
+
This is where the actual HTTP flooding happens.
|
|
261
|
+
"""
|
|
262
|
+
from urllib.parse import urljoin
|
|
263
|
+
|
|
264
|
+
# Build the full URL
|
|
265
|
+
base_url = context.get('target_url', '')
|
|
266
|
+
if not base_url:
|
|
267
|
+
raise ValueError("target_url must be set in context for API mode")
|
|
268
|
+
|
|
269
|
+
endpoint = payload['endpoint']
|
|
270
|
+
url = urljoin(base_url, endpoint)
|
|
271
|
+
|
|
272
|
+
# Prepare headers
|
|
273
|
+
headers = {
|
|
274
|
+
'User-Agent': payload['user_agent']
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Merge auth headers from context
|
|
278
|
+
auth_headers = context.get('auth_headers', {})
|
|
279
|
+
if auth_headers:
|
|
280
|
+
headers.update(auth_headers)
|
|
281
|
+
|
|
282
|
+
# Wait for the calculated delay
|
|
283
|
+
delay = payload['delay']
|
|
284
|
+
if delay > 0:
|
|
285
|
+
time.sleep(delay)
|
|
286
|
+
|
|
287
|
+
# Honor existing rate limiting from previous requests
|
|
288
|
+
resume_at = context.get('rate_limit_resume_at')
|
|
289
|
+
if resume_at and time.time() < resume_at:
|
|
290
|
+
# Skip this request due to rate limiting
|
|
291
|
+
raise requests.exceptions.RequestException("Rate limited")
|
|
292
|
+
|
|
293
|
+
# Make the request
|
|
294
|
+
start_time = time.time()
|
|
295
|
+
try:
|
|
296
|
+
if self.http_method == 'GET':
|
|
297
|
+
response = session.get(
|
|
298
|
+
url,
|
|
299
|
+
params=payload['data'],
|
|
300
|
+
headers=headers,
|
|
301
|
+
timeout=payload['timeout']
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
response = session.request(
|
|
305
|
+
self.http_method,
|
|
306
|
+
url,
|
|
307
|
+
json=payload['data'],
|
|
308
|
+
headers=headers,
|
|
309
|
+
timeout=payload['timeout']
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Record the result
|
|
313
|
+
response_time = time.time() - start_time
|
|
314
|
+
self._record_api_result(response, response_time, context)
|
|
315
|
+
|
|
316
|
+
return response
|
|
317
|
+
|
|
318
|
+
except requests.exceptions.Timeout:
|
|
319
|
+
response_time = payload['timeout']
|
|
320
|
+
self._record_api_result(None, response_time, context, timeout=True)
|
|
321
|
+
raise
|
|
322
|
+
except Exception as e:
|
|
323
|
+
response_time = time.time() - start_time
|
|
324
|
+
self._record_api_result(None, response_time, context, error=str(e))
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
def verify_result_api(self, response: requests.Response, context: Dict[str, Any]) -> bool:
|
|
328
|
+
"""
|
|
329
|
+
Verifies if the flooding attack was effective or if defenses kicked in.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
response: The response from execute_step_api
|
|
333
|
+
context: Shared context dictionary
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
True if attack behavior detected (rate limiting, errors),
|
|
337
|
+
False if requests succeeded without defensive measures
|
|
338
|
+
"""
|
|
339
|
+
# Check if we have accumulated enough results to make a determination
|
|
340
|
+
total_requests = self.attack_results['total_requests']
|
|
341
|
+
|
|
342
|
+
# Early determination if we have enough data
|
|
343
|
+
if total_requests >= min(20, self.request_count // 2):
|
|
344
|
+
success_rate = self.attack_results['successful_requests'] / total_requests
|
|
345
|
+
rate_limit_rate = self.attack_results['rate_limited_requests'] / total_requests
|
|
346
|
+
|
|
347
|
+
# If we expected defenses (expected_result=False)
|
|
348
|
+
if not self.expected_result:
|
|
349
|
+
# Good defense: high rate limiting, low success rate
|
|
350
|
+
if rate_limit_rate > 0.3 or success_rate < self.success_indicators['expected_success_rate']:
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
# If we expected success (expected_result=True)
|
|
354
|
+
else:
|
|
355
|
+
# Attack successful: high success rate, low rate limiting
|
|
356
|
+
if success_rate > 0.7 and rate_limit_rate < 0.2:
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
# Check immediate response indicators
|
|
360
|
+
if response:
|
|
361
|
+
# Rate limiting detected
|
|
362
|
+
if response.status_code in self.success_indicators['rate_limit_status_codes']:
|
|
363
|
+
return not self.expected_result
|
|
364
|
+
|
|
365
|
+
# Check response content for defensive indicators
|
|
366
|
+
try:
|
|
367
|
+
response_text = response.text.lower()
|
|
368
|
+
for keyword in self.success_indicators['error_keywords']:
|
|
369
|
+
if keyword in response_text:
|
|
370
|
+
return not self.expected_result
|
|
371
|
+
except Exception:
|
|
372
|
+
pass
|
|
373
|
+
|
|
374
|
+
# Default to continuing the test
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
def _record_ui_result(self, response_time: float, url: str = None, timeout: bool = False, error: str = None):
|
|
378
|
+
"""Record results from UI mode execution."""
|
|
379
|
+
self.attack_results['total_requests'] += 1
|
|
380
|
+
|
|
381
|
+
if timeout:
|
|
382
|
+
self.attack_results['failed_requests'] += 1
|
|
383
|
+
elif error:
|
|
384
|
+
self.attack_results['error_responses'] += 1
|
|
385
|
+
elif url and any(indicator in url for indicator in ['error', 'limit', '429', '503']):
|
|
386
|
+
self.attack_results['rate_limited_requests'] += 1
|
|
387
|
+
else:
|
|
388
|
+
self.attack_results['successful_requests'] += 1
|
|
389
|
+
|
|
390
|
+
# Update timing stats
|
|
391
|
+
self.attack_results['max_response_time'] = max(
|
|
392
|
+
self.attack_results['max_response_time'], response_time
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Calculate rolling average
|
|
396
|
+
total = self.attack_results['total_requests']
|
|
397
|
+
current_avg = self.attack_results['avg_response_time']
|
|
398
|
+
self.attack_results['avg_response_time'] = (
|
|
399
|
+
(current_avg * (total - 1) + response_time) / total
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
def _record_api_result(self, response: requests.Response = None, response_time: float = 0.0,
|
|
403
|
+
context: Dict[str, Any] = None, timeout: bool = False, error: str = None):
|
|
404
|
+
"""Record results from API mode execution."""
|
|
405
|
+
self.attack_results['total_requests'] += 1
|
|
406
|
+
|
|
407
|
+
if timeout:
|
|
408
|
+
self.attack_results['failed_requests'] += 1
|
|
409
|
+
elif error:
|
|
410
|
+
self.attack_results['error_responses'] += 1
|
|
411
|
+
elif response:
|
|
412
|
+
status_code = response.status_code
|
|
413
|
+
|
|
414
|
+
# Track status code distribution
|
|
415
|
+
if status_code not in self.attack_results['responses_by_status']:
|
|
416
|
+
self.attack_results['responses_by_status'][status_code] = 0
|
|
417
|
+
self.attack_results['responses_by_status'][status_code] += 1
|
|
418
|
+
|
|
419
|
+
# Categorize the response
|
|
420
|
+
if status_code in self.success_indicators['rate_limit_status_codes']:
|
|
421
|
+
self.attack_results['rate_limited_requests'] += 1
|
|
422
|
+
|
|
423
|
+
# Update rate limiting in context
|
|
424
|
+
if context and response.headers.get('Retry-After'):
|
|
425
|
+
try:
|
|
426
|
+
retry_after = int(response.headers['Retry-After'])
|
|
427
|
+
context['rate_limit_resume_at'] = time.time() + min(retry_after, 60)
|
|
428
|
+
except (ValueError, TypeError):
|
|
429
|
+
context['rate_limit_resume_at'] = time.time() + 5
|
|
430
|
+
|
|
431
|
+
elif 200 <= status_code < 300:
|
|
432
|
+
self.attack_results['successful_requests'] += 1
|
|
433
|
+
else:
|
|
434
|
+
self.attack_results['error_responses'] += 1
|
|
435
|
+
|
|
436
|
+
# Update timing statistics
|
|
437
|
+
self.attack_results['max_response_time'] = max(
|
|
438
|
+
self.attack_results['max_response_time'], response_time
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Calculate rolling average response time
|
|
442
|
+
total = self.attack_results['total_requests']
|
|
443
|
+
current_avg = self.attack_results['avg_response_time']
|
|
444
|
+
self.attack_results['avg_response_time'] = (
|
|
445
|
+
(current_avg * (total - 1) + response_time) / total
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Calculate attack effectiveness score
|
|
449
|
+
if total > 0:
|
|
450
|
+
success_rate = self.attack_results['successful_requests'] / total
|
|
451
|
+
rate_limit_rate = self.attack_results['rate_limited_requests'] / total
|
|
452
|
+
|
|
453
|
+
if self.expected_result:
|
|
454
|
+
# Higher success rate = more effective attack
|
|
455
|
+
self.attack_results['attack_effectiveness'] = success_rate * 100
|
|
456
|
+
else:
|
|
457
|
+
# Higher rate limiting = more effective defenses (which we want to detect)
|
|
458
|
+
self.attack_results['attack_effectiveness'] = rate_limit_rate * 100
|
|
459
|
+
|
|
460
|
+
def get_attack_summary(self) -> Dict[str, Any]:
|
|
461
|
+
"""
|
|
462
|
+
Returns a comprehensive summary of the attack results.
|
|
463
|
+
Useful for detailed analysis and reporting.
|
|
464
|
+
"""
|
|
465
|
+
total = self.attack_results['total_requests']
|
|
466
|
+
if total == 0:
|
|
467
|
+
return {"error": "No requests completed"}
|
|
468
|
+
|
|
469
|
+
summary = {
|
|
470
|
+
"attack_pattern": self.attack_pattern,
|
|
471
|
+
"total_requests": total,
|
|
472
|
+
"success_rate": (self.attack_results['successful_requests'] / total) * 100,
|
|
473
|
+
"rate_limit_rate": (self.attack_results['rate_limited_requests'] / total) * 100,
|
|
474
|
+
"error_rate": (self.attack_results['error_responses'] / total) * 100,
|
|
475
|
+
"avg_response_time": round(self.attack_results['avg_response_time'], 3),
|
|
476
|
+
"max_response_time": round(self.attack_results['max_response_time'], 3),
|
|
477
|
+
"attack_effectiveness": round(self.attack_results['attack_effectiveness'], 1),
|
|
478
|
+
"status_code_distribution": self.attack_results['responses_by_status'],
|
|
479
|
+
"defense_assessment": self._assess_defenses()
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return summary
|
|
483
|
+
|
|
484
|
+
def _assess_defenses(self) -> str:
|
|
485
|
+
"""Assess the effectiveness of the target's defensive measures."""
|
|
486
|
+
total = self.attack_results['total_requests']
|
|
487
|
+
if total == 0:
|
|
488
|
+
return "Insufficient data"
|
|
489
|
+
|
|
490
|
+
success_rate = self.attack_results['successful_requests'] / total
|
|
491
|
+
rate_limit_rate = self.attack_results['rate_limited_requests'] / total
|
|
492
|
+
avg_response_time = self.attack_results['avg_response_time']
|
|
493
|
+
|
|
494
|
+
if rate_limit_rate > 0.5:
|
|
495
|
+
return "Strong rate limiting detected - Good defenses"
|
|
496
|
+
elif rate_limit_rate > 0.2:
|
|
497
|
+
return "Moderate rate limiting detected - Basic defenses"
|
|
498
|
+
elif avg_response_time > 10.0:
|
|
499
|
+
return "Performance degradation detected - Possible DoS impact"
|
|
500
|
+
elif success_rate > 0.8:
|
|
501
|
+
return "High success rate - Weak or no defenses detected"
|
|
502
|
+
else:
|
|
503
|
+
return "Mixed results - Some defensive measures present"
|