scythe-ttp 0.17.2__py3-none-any.whl → 0.17.3__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 scythe-ttp might be problematic. Click here for more details.

scythe/core/executor.py CHANGED
@@ -3,9 +3,10 @@ import logging
3
3
  from selenium import webdriver
4
4
  from selenium.webdriver.chrome.options import Options
5
5
  from .ttp import TTP
6
- from typing import Optional
6
+ from typing import Optional, Dict, Any
7
7
  from ..behaviors.base import Behavior
8
8
  from .headers import HeaderExtractor
9
+ import requests
9
10
 
10
11
  # Configure logging
11
12
  logging.basicConfig(
@@ -60,7 +61,18 @@ class TTPExecutor:
60
61
  self.logger.info(f"Using behavior: {self.behavior.name}")
61
62
  self.logger.info(f"Behavior description: {self.behavior.description}")
62
63
 
63
- self._setup_driver()
64
+ # Check execution mode
65
+ if self.ttp.execution_mode == 'api':
66
+ self.logger.info("Execution mode: API")
67
+ self._run_api_mode()
68
+ return
69
+ else:
70
+ self.logger.info("Execution mode: UI")
71
+ self._setup_driver()
72
+ self._run_ui_mode()
73
+
74
+ def _run_ui_mode(self):
75
+ """Execute TTP in UI mode using Selenium."""
64
76
 
65
77
  try:
66
78
  # Handle authentication if required
@@ -172,6 +184,108 @@ class TTPExecutor:
172
184
  finally:
173
185
  self._cleanup()
174
186
 
187
+ def _run_api_mode(self):
188
+ """Execute TTP in API mode using requests."""
189
+ session = requests.Session()
190
+ context: Dict[str, Any] = {
191
+ 'target_url': self.target_url,
192
+ 'auth_headers': {},
193
+ 'rate_limit_resume_at': None
194
+ }
195
+
196
+ try:
197
+ # Handle authentication if required (API mode)
198
+ if self.ttp.requires_authentication():
199
+ auth_name = self.ttp.authentication.name if self.ttp.authentication else "Unknown"
200
+ self.logger.info(f"Authentication required for TTP: {auth_name}")
201
+
202
+ # Try to get auth headers directly
203
+ try:
204
+ if hasattr(self.ttp.authentication, 'get_auth_headers'):
205
+ auth_headers = self.ttp.authentication.get_auth_headers() or {}
206
+ context['auth_headers'] = auth_headers
207
+ session.headers.update(auth_headers)
208
+ self.logger.info("Authentication headers applied")
209
+ except Exception as e:
210
+ self.logger.warning(f"Failed to get auth headers: {e}")
211
+
212
+ consecutive_failures = 0
213
+
214
+ for i, payload in enumerate(self.ttp.get_payloads(), 1):
215
+ # Check if behavior wants to continue
216
+ if self.behavior and not self.behavior.should_continue(i, consecutive_failures):
217
+ self.logger.info("Behavior requested to stop execution")
218
+ break
219
+
220
+ self.logger.info(f"Attempt {i}: Executing with payload -> '{payload}'")
221
+
222
+ try:
223
+ # Execute API request
224
+ response = self.ttp.execute_step_api(session, payload, context)
225
+
226
+ # Use behavior delay if available, otherwise use default
227
+ if self.behavior:
228
+ step_delay = self.behavior.get_step_delay(i)
229
+ else:
230
+ step_delay = self.delay
231
+
232
+ time.sleep(step_delay)
233
+
234
+ # Verify result
235
+ success = self.ttp.verify_result_api(response, context)
236
+
237
+ # Compare actual result with expected result
238
+ if success:
239
+ consecutive_failures = 0
240
+
241
+ # Extract target version from response headers
242
+ target_version = response.headers.get('X-SCYTHE-TARGET-VERSION') or response.headers.get('x-scythe-target-version')
243
+
244
+ result_entry = {
245
+ 'payload': payload,
246
+ 'url': response.url if hasattr(response, 'url') else self.target_url,
247
+ 'expected': self.ttp.expected_result,
248
+ 'actual': True,
249
+ 'target_version': target_version
250
+ }
251
+ self.results.append(result_entry)
252
+
253
+ if self.ttp.expected_result:
254
+ version_info = f" | Version: {target_version}" if target_version else ""
255
+ self.logger.info(f"EXPECTED SUCCESS: '{payload}'{version_info}")
256
+ else:
257
+ version_info = f" | Version: {target_version}" if target_version else ""
258
+ self.logger.warning(f"UNEXPECTED SUCCESS: '{payload}' (expected to fail){version_info}")
259
+ self.has_test_failures = True
260
+ else:
261
+ consecutive_failures += 1
262
+ if self.ttp.expected_result:
263
+ self.logger.info(f"EXPECTED FAILURE: '{payload}' (security control working)")
264
+ self.has_test_failures = True
265
+ else:
266
+ self.logger.info(f"EXPECTED FAILURE: '{payload}'")
267
+
268
+ except Exception as step_error:
269
+ consecutive_failures += 1
270
+ self.logger.error(f"Error during step {i}: {step_error}")
271
+
272
+ # Let behavior handle the error
273
+ if self.behavior:
274
+ if not self.behavior.on_error(step_error, i):
275
+ self.logger.info("Behavior requested to stop due to error")
276
+ break
277
+ else:
278
+ # Default behavior: continue on most errors
279
+ continue
280
+
281
+ except KeyboardInterrupt:
282
+ self.logger.info("Test interrupted by user.")
283
+ except Exception as e:
284
+ self.logger.error(f"An unexpected error occurred: {e}", exc_info=True)
285
+ finally:
286
+ session.close()
287
+ self._cleanup()
288
+
175
289
  def _cleanup(self):
176
290
  """Closes the WebDriver and prints a summary."""
177
291
  if self.driver:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.17.2
3
+ Version: 0.17.3
4
4
  Summary: An extensible framework for emulating attacker TTPs with Selenium.
5
5
  Author-email: EpykLab <cyber@epyklab.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -13,7 +13,7 @@ scythe/behaviors/stealth.py,sha256=xv7MrPQgRCdCUJyYTcXV2aasWZoAw8rAQWg-AuQVb7U,1
13
13
  scythe/cli/__init__.py,sha256=9EVxmFiWsAoqWJ6br1bc3BxlA71JyOQP28fUHhX2k7E,43
14
14
  scythe/cli/main.py,sha256=DFvOB39tX4FeiOxitJwXfq28J13GjewBZ9gOA_0HOjI,26003
15
15
  scythe/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- scythe/core/executor.py,sha256=C3FkW-DNugv82T0_ky-3zAvHV_hFwVSHrX2nzgAcmAI,10588
16
+ scythe/core/executor.py,sha256=FTuhvWiNOsN1LZ4scEsVGDXx9D3yF3tiAoDt-HW2QRI,16038
17
17
  scythe/core/headers.py,sha256=AokCQ3F5QGUcfoK7iO57hA1HHL4IznZeWV464_MqYcE,16670
18
18
  scythe/core/ttp.py,sha256=tEYIhDdr8kcwQrlcfVmdeLFiAfOvc0BhPOVxPh8TiWo,5676
19
19
  scythe/journeys/__init__.py,sha256=Odi8NhRg7Hefmo1EJj1guakrCSPhsuus4i-_62uUUjs,654
@@ -32,9 +32,9 @@ scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  scythe/ttps/web/login_bruteforce.py,sha256=ybmN2Vl9-p58YbOchirY1193GvlaTmUTW1qlluN_l3I,7816
33
33
  scythe/ttps/web/sql_injection.py,sha256=rIRHaRUilSrMA5q5MO1RqR6-TM_fRIiCanPaFz5wKKs,11712
34
34
  scythe/ttps/web/uuid_guessing.py,sha256=JwNt_9HVynMWFPPU6UGJFcpxvMVDsvc_wAnJVtcYbps,1235
35
- scythe_ttp-0.17.2.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
- scythe_ttp-0.17.2.dist-info/METADATA,sha256=O71-_tq82UjkFpZ3rBxpb__7dYPoRTJ2xbINezSbaTs,30188
37
- scythe_ttp-0.17.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- scythe_ttp-0.17.2.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
- scythe_ttp-0.17.2.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
- scythe_ttp-0.17.2.dist-info/RECORD,,
35
+ scythe_ttp-0.17.3.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
+ scythe_ttp-0.17.3.dist-info/METADATA,sha256=VEyrEx7ZfhrAx3hn_F1lXV5XkYjxkLE4bV-M65inP1s,30188
37
+ scythe_ttp-0.17.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ scythe_ttp-0.17.3.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
+ scythe_ttp-0.17.3.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
+ scythe_ttp-0.17.3.dist-info/RECORD,,