scythe-ttp 0.10.0__py3-none-any.whl → 0.12.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.

Potentially problematic release.


This version of scythe-ttp might be problematic. Click here for more details.

scythe/core/executor.py CHANGED
@@ -5,6 +5,7 @@ from selenium.webdriver.chrome.options import Options
5
5
  from .ttp import TTP
6
6
  from typing import Optional
7
7
  from ..behaviors.base import Behavior
8
+ from .headers import HeaderExtractor
8
9
 
9
10
  # Configure logging
10
11
  logging.basicConfig(
@@ -33,8 +34,12 @@ class TTPExecutor:
33
34
  self.chrome_options.add_argument("--no-sandbox")
34
35
  self.chrome_options.add_argument("--disable-dev-shm-usage")
35
36
 
37
+ # Enable header extraction capabilities
38
+ HeaderExtractor.enable_logging_for_driver(self.chrome_options)
39
+
36
40
  self.driver = None
37
41
  self.results = []
42
+ self.header_extractor = HeaderExtractor()
38
43
 
39
44
  def _setup_driver(self):
40
45
  """Initializes the WebDriver."""
@@ -108,13 +113,27 @@ class TTPExecutor:
108
113
  if success:
109
114
  consecutive_failures = 0
110
115
  current_url = self.driver.current_url if self.driver else "unknown"
111
- result_entry = {'payload': payload, 'url': current_url, 'expected': self.ttp.expected_result, 'actual': True}
116
+
117
+ # Extract target version header
118
+ target_version = None
119
+ if self.driver:
120
+ target_version = self.header_extractor.extract_target_version(self.driver, self.target_url)
121
+
122
+ result_entry = {
123
+ 'payload': payload,
124
+ 'url': current_url,
125
+ 'expected': self.ttp.expected_result,
126
+ 'actual': True,
127
+ 'target_version': target_version
128
+ }
112
129
  self.results.append(result_entry)
113
130
 
114
131
  if self.ttp.expected_result:
115
- self.logger.info(f"EXPECTED SUCCESS: '{payload}'")
132
+ version_info = f" | Version: {target_version}" if target_version else ""
133
+ self.logger.info(f"EXPECTED SUCCESS: '{payload}'{version_info}")
116
134
  else:
117
- self.logger.warning(f"UNEXPECTED SUCCESS: '{payload}' (expected to fail)")
135
+ version_info = f" | Version: {target_version}" if target_version else ""
136
+ self.logger.warning(f"UNEXPECTED SUCCESS: '{payload}' (expected to fail){version_info}")
118
137
  else:
119
138
  consecutive_failures += 1
120
139
  if self.ttp.expected_result:
@@ -168,12 +187,26 @@ class TTPExecutor:
168
187
  if expected_successes:
169
188
  self.logger.info(f"Expected successes: {len(expected_successes)}")
170
189
  for result in expected_successes:
171
- self.logger.info(f" Payload: {result['payload']} | URL: {result['url']}")
190
+ version_info = f" | Version: {result['target_version']}" if result.get('target_version') else ""
191
+ self.logger.info(f" ✓ Payload: {result['payload']} | URL: {result['url']}{version_info}")
172
192
 
173
193
  if unexpected_successes:
174
194
  self.logger.warning(f"Unexpected successes: {len(unexpected_successes)}")
175
195
  for result in unexpected_successes:
176
- self.logger.warning(f" Payload: {result['payload']} | URL: {result['url']}")
196
+ version_info = f" | Version: {result['target_version']}" if result.get('target_version') else ""
197
+ self.logger.warning(f" ✗ Payload: {result['payload']} | URL: {result['url']}{version_info}")
198
+
199
+ # Display version summary
200
+ version_summary = self.header_extractor.get_version_summary(self.results)
201
+ if version_summary['results_with_version'] > 0:
202
+ self.logger.info("\nTarget Version Summary:")
203
+ self.logger.info(f" Results with version info: {version_summary['results_with_version']}/{version_summary['total_results']}")
204
+ if version_summary['unique_versions']:
205
+ for version in version_summary['unique_versions']:
206
+ count = version_summary['version_counts'][version]
207
+ self.logger.info(f" Version {version}: {count} result(s)")
208
+ else:
209
+ self.logger.info("\nNo X-SCYTHE-TARGET-VERSION headers detected in responses.")
177
210
  else:
178
211
  if self.ttp.expected_result:
179
212
  self.logger.info("No successes detected (expected to find vulnerabilities).")
scythe/core/headers.py ADDED
@@ -0,0 +1,194 @@
1
+ import json
2
+ import logging
3
+ from typing import Optional, Dict, Any
4
+ from selenium.webdriver.remote.webdriver import WebDriver
5
+ from selenium.webdriver.chrome.options import Options
6
+
7
+
8
+ class HeaderExtractor:
9
+ """
10
+ Utility class for extracting HTTP response headers from WebDriver sessions.
11
+
12
+ Specifically designed to capture the X-SCYTHE-TARGET-VERSION header
13
+ that indicates the version of the web application being tested.
14
+ """
15
+
16
+ SCYTHE_VERSION_HEADER = "X-Scythe-Target-Version"
17
+
18
+ def __init__(self):
19
+ self.logger = logging.getLogger("HeaderExtractor")
20
+
21
+ @staticmethod
22
+ def enable_logging_for_driver(chrome_options: Options) -> None:
23
+ """
24
+ Enable performance logging capabilities for Chrome WebDriver.
25
+
26
+ This must be called during WebDriver setup to capture network logs.
27
+
28
+ Args:
29
+ chrome_options: Chrome options object to modify
30
+ """
31
+ # Enable performance logging to capture network events
32
+ chrome_options.add_argument("--enable-logging")
33
+ chrome_options.add_argument("--log-level=0")
34
+ chrome_options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
35
+
36
+ def extract_target_version(self, driver: WebDriver, target_url: Optional[str] = None) -> Optional[str]:
37
+ """
38
+ Extract the X-SCYTHE-TARGET-VERSION header from the most recent HTTP response.
39
+
40
+ Args:
41
+ driver: WebDriver instance with performance logging enabled
42
+ target_url: Optional URL to filter responses for (if None, uses any response)
43
+
44
+ Returns:
45
+ Version string if header found, None otherwise
46
+ """
47
+ try:
48
+ # Get performance logs - using getattr to handle type checking
49
+ if not hasattr(driver, 'get_log'):
50
+ self.logger.warning("WebDriver does not support get_log method")
51
+ return None
52
+
53
+ logs = getattr(driver, 'get_log')('performance')
54
+
55
+ # Look for Network.responseReceived events
56
+ for log_entry in reversed(logs): # Start with most recent
57
+ try:
58
+ message = log_entry.get('message', {})
59
+ if isinstance(message, str):
60
+ message = json.loads(message)
61
+
62
+ method = message.get('message', {}).get('method', '')
63
+ params = message.get('message', {}).get('params', {})
64
+
65
+ if method == 'Network.responseReceived':
66
+ response = params.get('response', {})
67
+ headers = response.get('headers', {})
68
+ response_url = response.get('url', '')
69
+
70
+ # Filter by target URL if specified
71
+ if target_url and target_url not in response_url:
72
+ continue
73
+
74
+ # Look for the version header (case-insensitive)
75
+ version = self._find_version_header(headers)
76
+ if version:
77
+ self.logger.debug(f"Found target version '{version}' in response from {response_url}")
78
+ return version
79
+
80
+ except (json.JSONDecodeError, KeyError, AttributeError) as e:
81
+ self.logger.debug(f"Error parsing log entry: {e}")
82
+ continue
83
+
84
+ self.logger.debug("No X-SCYTHE-TARGET-VERSION header found in network logs")
85
+ return None
86
+
87
+ except Exception as e:
88
+ self.logger.warning(f"Failed to extract target version from logs: {e}")
89
+ return None
90
+
91
+ def _find_version_header(self, headers: Dict[str, Any]) -> Optional[str]:
92
+ """
93
+ Find the version header in a case-insensitive manner.
94
+
95
+ Args:
96
+ headers: Dictionary of HTTP headers
97
+
98
+ Returns:
99
+ Version string if found, None otherwise
100
+ """
101
+ # Check for exact case match first
102
+ if self.SCYTHE_VERSION_HEADER in headers:
103
+ return str(headers[self.SCYTHE_VERSION_HEADER]).strip()
104
+
105
+ # Check case-insensitive
106
+ header_lower = self.SCYTHE_VERSION_HEADER.lower()
107
+ for header_name, header_value in headers.items():
108
+ if header_name.lower() == header_lower:
109
+ return str(header_value).strip()
110
+
111
+ return None
112
+
113
+ def extract_all_headers(self, driver: WebDriver, target_url: Optional[str] = None) -> Dict[str, str]:
114
+ """
115
+ Extract all headers from the most recent HTTP response.
116
+
117
+ Useful for debugging or when additional headers might be needed.
118
+
119
+ Args:
120
+ driver: WebDriver instance with performance logging enabled
121
+ target_url: Optional URL to filter responses for
122
+
123
+ Returns:
124
+ Dictionary of headers from the most recent response
125
+ """
126
+ try:
127
+ # Get performance logs - using getattr to handle type checking
128
+ if not hasattr(driver, 'get_log'):
129
+ self.logger.warning("WebDriver does not support get_log method")
130
+ return {}
131
+
132
+ logs = getattr(driver, 'get_log')('performance')
133
+
134
+ for log_entry in reversed(logs):
135
+ try:
136
+ message = log_entry.get('message', {})
137
+ if isinstance(message, str):
138
+ message = json.loads(message)
139
+
140
+ method = message.get('message', {}).get('method', '')
141
+ params = message.get('message', {}).get('params', {})
142
+
143
+ if method == 'Network.responseReceived':
144
+ response = params.get('response', {})
145
+ headers = response.get('headers', {})
146
+ response_url = response.get('url', '')
147
+
148
+ # Filter by target URL if specified
149
+ if target_url and target_url not in response_url:
150
+ continue
151
+
152
+ # Convert all header values to strings
153
+ return {k: str(v) for k, v in headers.items()}
154
+
155
+ except (json.JSONDecodeError, KeyError, AttributeError):
156
+ continue
157
+
158
+ return {}
159
+
160
+ except Exception as e:
161
+ self.logger.warning(f"Failed to extract headers from logs: {e}")
162
+ return {}
163
+
164
+ def get_version_summary(self, results: list) -> Dict[str, Any]:
165
+ """
166
+ Analyze version information across multiple test results.
167
+
168
+ Args:
169
+ results: List of result dictionaries containing version information
170
+
171
+ Returns:
172
+ Dictionary with version analysis summary
173
+ """
174
+ versions = []
175
+ results_with_version = 0
176
+
177
+ for result in results:
178
+ version = result.get('target_version')
179
+ if version:
180
+ versions.append(version)
181
+ results_with_version += 1
182
+
183
+ summary = {
184
+ 'total_results': len(results),
185
+ 'results_with_version': results_with_version,
186
+ 'unique_versions': list(set(versions)) if versions else [],
187
+ 'version_counts': {}
188
+ }
189
+
190
+ # Count occurrences of each version
191
+ for version in versions:
192
+ summary['version_counts'][version] = summary['version_counts'].get(version, 0) + 1
193
+
194
+ return summary
scythe/journeys/base.py CHANGED
@@ -276,6 +276,10 @@ class Journey:
276
276
  logger.info(f"Description: {self.description}")
277
277
  logger.info(f"Steps: {len(self.steps)}")
278
278
 
279
+ # Import here to avoid circular imports
280
+ from ..core.headers import HeaderExtractor
281
+ header_extractor = HeaderExtractor()
282
+
279
283
  start_time = time.time()
280
284
 
281
285
  # Set initial context
@@ -297,7 +301,9 @@ class Journey:
297
301
  'overall_success': False,
298
302
  'execution_time': 0,
299
303
  'step_results': [],
300
- 'errors': []
304
+ 'errors': [],
305
+ 'target_versions': [],
306
+ 'version_summary': {}
301
307
  }
302
308
 
303
309
  try:
@@ -335,6 +341,12 @@ class Journey:
335
341
  else:
336
342
  results['actions_failed'] += 1
337
343
 
344
+ # Extract target version header after step execution
345
+ target_version = header_extractor.extract_target_version(driver, target_url)
346
+ if target_version:
347
+ results['target_versions'].append(target_version)
348
+ logger.info(f"Target version detected: {target_version}")
349
+
338
350
  # Store step results
339
351
  step_result = {
340
352
  'step_name': step.name,
@@ -342,7 +354,8 @@ class Journey:
342
354
  'expected': step.expected_result,
343
355
  'actual': step_success,
344
356
  'actions': step.execution_results.copy(),
345
- 'step_data': step.step_data.copy()
357
+ 'step_data': step.step_data.copy(),
358
+ 'target_version': target_version
346
359
  }
347
360
  results['step_results'].append(step_result)
348
361
 
@@ -368,6 +381,12 @@ class Journey:
368
381
  results['end_time'] = end_time
369
382
  results['execution_time'] = end_time - start_time
370
383
 
384
+ # Generate version summary
385
+ if results['target_versions']:
386
+ version_summary = header_extractor.get_version_summary([{'target_version': v} for v in results['target_versions']])
387
+ results['version_summary'] = version_summary
388
+ logger.info(f"Target versions detected: {list(set(results['target_versions']))}")
389
+
371
390
  logger.info(f"Journey completed in {results['execution_time']:.2f} seconds")
372
391
  logger.info(f"Overall success: {results['overall_success']}")
373
392
  logger.info(f"Steps: {results['steps_succeeded']}/{results['steps_executed']} succeeded")
@@ -5,6 +5,7 @@ from selenium.webdriver.chrome.options import Options
5
5
  from typing import Optional, Dict, Any, List
6
6
  from ..behaviors.base import Behavior
7
7
  from .base import Journey
8
+ from ..core.headers import HeaderExtractor
8
9
 
9
10
  # Configure logging
10
11
  logging.basicConfig(
@@ -64,8 +65,12 @@ class JourneyExecutor:
64
65
  else:
65
66
  self.chrome_options.add_argument(f"--{key}={value}")
66
67
 
68
+ # Enable header extraction capabilities
69
+ HeaderExtractor.enable_logging_for_driver(self.chrome_options)
70
+
67
71
  self.driver = None
68
72
  self.execution_results = None
73
+ self.header_extractor = HeaderExtractor()
69
74
 
70
75
  def _setup_driver(self):
71
76
  """Initialize the WebDriver."""
@@ -297,14 +302,30 @@ class JourneyExecutor:
297
302
  step_success = step_result.get('actual', False)
298
303
  step_expected = step_result.get('expected', True)
299
304
  actions = step_result.get('actions', [])
305
+ target_version = step_result.get('target_version')
300
306
 
301
307
  status = "✓" if step_success == step_expected else "✗"
302
308
  result_text = "SUCCESS" if step_success else "FAILURE"
303
309
  expected_text = "expected" if step_success == step_expected else "unexpected"
310
+ version_info = f" | Version: {target_version}" if target_version else ""
304
311
 
305
- self.logger.info(f" {status} Step {i}: {step_name} - {result_text} ({expected_text})")
312
+ self.logger.info(f" {status} Step {i}: {step_name} - {result_text} ({expected_text}){version_info}")
306
313
  self.logger.info(f" Actions: {len([a for a in actions if a.get('actual', False)])}/{len(actions)} succeeded")
307
314
 
315
+ # Version summary
316
+ target_versions = self.execution_results.get('target_versions', [])
317
+
318
+ if target_versions:
319
+ self.logger.info("\nTarget Version Summary:")
320
+ self.logger.info(f" Steps with version info: {len(target_versions)}/{len(step_results) if step_results else 0}")
321
+ unique_versions = list(set(target_versions))
322
+ if unique_versions:
323
+ for version in unique_versions:
324
+ count = target_versions.count(version)
325
+ self.logger.info(f" Version {version}: {count} step(s)")
326
+ else:
327
+ self.logger.info("\nNo X-SCYTHE-TARGET-VERSION headers detected in responses.")
328
+
308
329
  self.logger.info("="*60)
309
330
 
310
331
  def get_results(self) -> Optional[Dict[str, Any]]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.10.0
3
+ Version: 0.12.1
4
4
  Summary: An extensible framework for emulating attacker TTPs with Selenium.
5
5
  Home-page: https://github.com/EpykLab/scythe
6
6
  Author: EpykLab
@@ -96,6 +96,7 @@ Scythe operates on the principle that robust systems must be tested under advers
96
96
  ### 📊 **Professional Reporting**
97
97
  * **Clear Result Indicators**: ✓ Expected outcomes, ✗ Unexpected results
98
98
  * **Comprehensive Logging**: Detailed execution tracking and analysis
99
+ * **Version Detection**: Automatic extraction of X-SCYTHE-TARGET-VERSION headers
99
100
  * **Performance Metrics**: Timing, success rates, and resource utilization
100
101
  * **Execution Statistics**: Detailed reporting across all test types
101
102
 
@@ -168,6 +169,26 @@ file_upload_ttp = FileUploadTTP(
168
169
 
169
170
  ### Installation
170
171
 
172
+ #### If you would like to use as a library:
173
+
174
+ setup the virtual environment
175
+ ```bash
176
+ python3 -m venv venv
177
+
178
+ # source the venv
179
+ # bash,zsh: source venv/bin/activate
180
+ # fish: source venv/bin/activate.fish
181
+ ```
182
+
183
+ install the package
184
+ ```bash
185
+ # in an activated venv
186
+
187
+ pip3 install scythe-ttp
188
+ ```
189
+
190
+ #### If you would like like to contribute:
191
+
171
192
  1. Clone the repository:
172
193
  ```bash
173
194
  git clone https://github.com/EpykLab/scythe.git
@@ -443,6 +464,74 @@ executor = TTPExecutor(
443
464
  )
444
465
  ```
445
466
 
467
+ ### Version Detection
468
+
469
+ Scythe automatically captures the `X-SCYTHE-TARGET-VERSION` header from HTTP responses to track which version of your web application is being tested:
470
+
471
+ ```python
472
+ from scythe.core.ttp import TTP
473
+ from scythe.core.executor import TTPExecutor
474
+
475
+ # Your web application should set this header:
476
+ # X-SCYTHE-TARGET-VERSION: 1.3.2
477
+
478
+ class MyTTP(TTP):
479
+ def get_payloads(self):
480
+ yield "test_payload"
481
+
482
+ def execute_step(self, driver, payload):
483
+ driver.get("http://your-app.com/login")
484
+ # ... test logic ...
485
+
486
+ def verify_result(self, driver):
487
+ return "welcome" in driver.page_source
488
+
489
+ # Run the test
490
+ ttp = MyTTP("Version Test", "Test with version detection")
491
+ executor = TTPExecutor(ttp=ttp, target_url="http://your-app.com")
492
+ executor.run()
493
+ ```
494
+
495
+ **Output includes version information:**
496
+ ```
497
+ ✓ EXPECTED SUCCESS: 'test_payload' | Version: 1.3.2
498
+ Target Version Summary:
499
+ Results with version info: 1/1
500
+ Version 1.3.2: 1 result(s)
501
+ ```
502
+
503
+ **Server-side implementation examples:**
504
+ ```python
505
+ # Python/Flask
506
+ @app.after_request
507
+ def add_version_header(response):
508
+ response.headers['X-SCYTHE-TARGET-VERSION'] = '1.3.2'
509
+ return response
510
+
511
+ # Node.js/Express
512
+ app.use((req, res, next) => {
513
+ res.set('X-SCYTHE-TARGET-VERSION', '1.3.2');
514
+ next();
515
+ });
516
+
517
+ # Java/Spring Boot
518
+ @Component
519
+ public class VersionHeaderFilter implements Filter {
520
+ @Override
521
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
522
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
523
+ httpResponse.setHeader("X-SCYTHE-TARGET-VERSION", "1.3.2");
524
+ chain.doFilter(request, response);
525
+ }
526
+ }
527
+ ```
528
+
529
+ This feature helps you:
530
+ - **Track test results** by application version
531
+ - **Verify deployment status** during testing
532
+ - **Correlate issues** with specific software versions
533
+ - **Ensure consistency** across test environments
534
+
446
535
  ### Custom Test Creation
447
536
 
448
537
  Extend Scythe for specific testing needs:
@@ -454,7 +543,7 @@ from typing import Generator, Any
454
543
 
455
544
  class CustomBusinessLogicTTP(TTP):
456
545
  """Test specific business logic under adverse conditions."""
457
-
546
+
458
547
  def __init__(self, business_scenarios: list, expected_result: bool = True):
459
548
  super().__init__(
460
549
  name="Business Logic Test",
@@ -462,28 +551,28 @@ class CustomBusinessLogicTTP(TTP):
462
551
  expected_result=expected_result
463
552
  )
464
553
  self.scenarios = business_scenarios
465
-
554
+
466
555
  def get_payloads(self) -> Generator[Any, None, None]:
467
556
  for scenario in self.scenarios:
468
557
  yield scenario
469
-
558
+
470
559
  def execute_step(self, driver, payload):
471
560
  # Implement your specific business logic testing
472
561
  # This could involve API calls, database interactions, etc.
473
562
  pass
474
-
563
+
475
564
  def verify_result(self, driver) -> bool:
476
565
  # Verify the business logic behaved correctly
477
566
  return self.check_business_rules(driver)
478
567
 
479
568
  class CustomWorkflowAction(Action):
480
569
  """Custom action for specific workflow steps."""
481
-
570
+
482
571
  def __init__(self, workflow_step: str, parameters: dict):
483
572
  super().__init__(f"Custom {workflow_step}", f"Execute {workflow_step}")
484
573
  self.workflow_step = workflow_step
485
574
  self.parameters = parameters
486
-
575
+
487
576
  def execute(self, driver, context):
488
577
  # Implement custom workflow logic
489
578
  return self.perform_workflow_step(driver, context)
@@ -500,12 +589,12 @@ ecommerce_suite = [
500
589
  payment_security_test, # Test payment form security
501
590
  user_data_protection_test, # Test PII protection
502
591
  session_management_test, # Test session security
503
-
592
+
504
593
  # Load testing
505
594
  product_catalog_load_test, # High-traffic product browsing
506
595
  checkout_process_load_test, # Concurrent checkout processes
507
596
  search_functionality_test, # Search under load
508
-
597
+
509
598
  # Workflow testing
510
599
  complete_purchase_journey, # End-to-end purchase flow
511
600
  return_process_journey, # Product return workflow
@@ -578,30 +667,30 @@ def analyze_test_results(orchestration_result):
578
667
  print("="*60)
579
668
  print("COMPREHENSIVE TEST ANALYSIS")
580
669
  print("="*60)
581
-
670
+
582
671
  print(f"Total Executions: {orchestration_result.total_executions}")
583
672
  print(f"Success Rate: {orchestration_result.success_rate:.1f}%")
584
673
  print(f"Average Execution Time: {orchestration_result.average_execution_time:.2f}s")
585
-
674
+
586
675
  # Performance metrics
587
676
  if orchestration_result.metadata.get('performance_stats'):
588
677
  stats = orchestration_result.metadata['performance_stats']
589
678
  print(f"Peak Response Time: {stats.get('peak_response_time', 'N/A')}")
590
679
  print(f"95th Percentile: {stats.get('p95_response_time', 'N/A')}")
591
-
680
+
592
681
  # Geographic distribution (if applicable)
593
682
  if orchestration_result.metadata.get('distribution_stats'):
594
683
  dist = orchestration_result.metadata['distribution_stats']
595
684
  print("Geographic Distribution:")
596
685
  for location, count in dist.get('location_usage', {}).items():
597
686
  print(f" {location}: {count} executions")
598
-
687
+
599
688
  # Error analysis
600
689
  if orchestration_result.errors:
601
690
  print(f"\nErrors Encountered: {len(orchestration_result.errors)}")
602
691
  for i, error in enumerate(orchestration_result.errors[:5], 1):
603
692
  print(f" {i}. {error}")
604
-
693
+
605
694
  print("="*60)
606
695
 
607
696
  # Use with any orchestration result
@@ -10,12 +10,13 @@ scythe/behaviors/human.py,sha256=1PqYvE7cnxlj-KDmDIr3uzfWHvDAbbxQxJ0V0iTl9yo,102
10
10
  scythe/behaviors/machine.py,sha256=NDMUq3mDhpCvujzAFxhn2eSVq78-J-LSBhIcvHkzKXo,4624
11
11
  scythe/behaviors/stealth.py,sha256=xv7MrPQgRCdCUJyYTcXV2aasWZoAw8rAQWg-AuQVb7U,15278
12
12
  scythe/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- scythe/core/executor.py,sha256=KyfnOnYK5mtfAE9dNYFeay7me8dEA5g0ICLgbWPtscg,7721
13
+ scythe/core/executor.py,sha256=x1w2nByVu2G70sh7t0kOh6urlrTm_r_pbk0S7v1Ov28,9736
14
+ scythe/core/headers.py,sha256=1LznVigcyLepho4EL_Z5_EWjEcn9sJRSdHmROpf6_90,7326
14
15
  scythe/core/ttp.py,sha256=Xw9GgptYsjZ-pMLdyPv64bhiwGKobrXHdF32pjIY7OU,3102
15
16
  scythe/journeys/__init__.py,sha256=-8AIpCmkeWtQ656yU3omj_guMG4v4i1koIpD6NZhUGM,612
16
17
  scythe/journeys/actions.py,sha256=Ez6Bpzs2VHzXMl6GtPve85XxzQV09rDscmDuzSs3VBE,25229
17
- scythe/journeys/base.py,sha256=jSP-qy_xquoCH5UBFO8e52EdnF-2nugSoJHNeLLuXSE,14408
18
- scythe/journeys/executor.py,sha256=uarNHeAfRJMxC343Lgh8z9i0pZj9F-pNU8YX-R2ov5E,18875
18
+ scythe/journeys/base.py,sha256=BWf35Ee3N9qy76Awh-r04-waUTDfLyxssvDmwYToXgY,15461
19
+ scythe/journeys/executor.py,sha256=1D_HUzvi4Z7a5uE7QbIDWH7HTGz5DoxcQffr-05bi_0,19978
19
20
  scythe/orchestrators/__init__.py,sha256=_vemcXjKbB1jI0F2dPA0F1zNsyUekjcXImLDUDhWDN0,560
20
21
  scythe/orchestrators/base.py,sha256=YOZV0ewlzJ49H08P_LKnimutUms8NnDrQprFpSKhOeM,13595
21
22
  scythe/orchestrators/batch.py,sha256=FpK501kk-earJzz6v7dcuw2y708rTvt_IMH_5qjKdrc,26635
@@ -28,8 +29,8 @@ scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
29
  scythe/ttps/web/login_bruteforce.py,sha256=D4G8zB_nU9LD5w3Vv2ABTuOl4XTeg2BgZwYMObt4JJw,2488
29
30
  scythe/ttps/web/sql_injection.py,sha256=aWk4DFePbtFDsieOOj03Ux-5OiykyOs2_d_3SvWMOVE,2910
30
31
  scythe/ttps/web/uuid_guessing.py,sha256=WwCIQPLIixd5U2EY4bhnj7YP2AQDaPfQy7Yhj84UHy8,1245
31
- scythe_ttp-0.10.0.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
32
- scythe_ttp-0.10.0.dist-info/METADATA,sha256=YsBI0wg0fungTybVjt4mrE3PwD728C3meTcQgfaUywg,25478
33
- scythe_ttp-0.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- scythe_ttp-0.10.0.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
35
- scythe_ttp-0.10.0.dist-info/RECORD,,
32
+ scythe_ttp-0.12.1.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
33
+ scythe_ttp-0.12.1.dist-info/METADATA,sha256=hU3I4EUC7C1MdaPbNY6tSiDUSke2CXOmAMoZPVmLFfI,27741
34
+ scythe_ttp-0.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ scythe_ttp-0.12.1.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
36
+ scythe_ttp-0.12.1.dist-info/RECORD,,