scythe-ttp 0.12.1__tar.gz → 0.12.4__tar.gz

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.
Files changed (52) hide show
  1. {scythe_ttp-0.12.1/scythe_ttp.egg-info → scythe_ttp-0.12.4}/PKG-INFO +1 -1
  2. scythe_ttp-0.12.4/VERSION +1 -0
  3. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/headers.py +152 -0
  4. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4/scythe_ttp.egg-info}/PKG-INFO +1 -1
  5. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/setup.py +1 -1
  6. scythe_ttp-0.12.1/VERSION +0 -1
  7. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/LICENSE +0 -0
  8. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/MANIFEST.in +0 -0
  9. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/README.md +0 -0
  10. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/requirements.txt +0 -0
  11. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/__init__.py +0 -0
  12. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/__init__.py +0 -0
  13. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/base.py +0 -0
  14. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/basic.py +0 -0
  15. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/bearer.py +0 -0
  16. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/__init__.py +0 -0
  17. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/base.py +0 -0
  18. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/default.py +0 -0
  19. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/human.py +0 -0
  20. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/machine.py +0 -0
  21. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/stealth.py +0 -0
  22. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/__init__.py +0 -0
  23. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/executor.py +0 -0
  24. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/ttp.py +0 -0
  25. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/__init__.py +0 -0
  26. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/actions.py +0 -0
  27. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/base.py +0 -0
  28. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/executor.py +0 -0
  29. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/__init__.py +0 -0
  30. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/base.py +0 -0
  31. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/batch.py +0 -0
  32. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/distributed.py +0 -0
  33. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/scale.py +0 -0
  34. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/payloads/__init__.py +0 -0
  35. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/payloads/generators.py +0 -0
  36. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/__init__.py +0 -0
  37. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/__init__.py +0 -0
  38. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/login_bruteforce.py +0 -0
  39. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/sql_injection.py +0 -0
  40. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/uuid_guessing.py +0 -0
  41. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/SOURCES.txt +0 -0
  42. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/dependency_links.txt +0 -0
  43. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/requires.txt +0 -0
  44. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/top_level.txt +0 -0
  45. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/setup.cfg +0 -0
  46. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_authentication.py +0 -0
  47. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_behaviors.py +0 -0
  48. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_expected_results.py +0 -0
  49. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_feature_completeness.py +0 -0
  50. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_header_extraction.py +0 -0
  51. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_journeys.py +0 -0
  52. {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_orchestrators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.12.1
3
+ Version: 0.12.4
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
@@ -0,0 +1 @@
1
+ 0.12.5
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import logging
3
+ import requests
3
4
  from typing import Optional, Dict, Any
4
5
  from selenium.webdriver.remote.webdriver import WebDriver
5
6
  from selenium.webdriver.chrome.options import Options
@@ -33,6 +34,157 @@ class HeaderExtractor:
33
34
  chrome_options.add_argument("--log-level=0")
34
35
  chrome_options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
35
36
 
37
+ def banner_grab(self, url: str, timeout: int = 10, method: str = "HEAD") -> Optional[str]:
38
+ """
39
+ Perform a simple HTTP request to extract the X-SCYTHE-TARGET-VERSION header.
40
+
41
+ This is a more reliable alternative to Selenium's performance logging
42
+ for cases where you just need to grab headers.
43
+
44
+ Args:
45
+ url: URL to make the request to
46
+ timeout: Request timeout in seconds
47
+ method: HTTP method to use ("HEAD" or "GET")
48
+
49
+ Returns:
50
+ Version string if header found, None otherwise
51
+ """
52
+ try:
53
+ self.logger.debug(f"Making {method} request to {url} for header extraction")
54
+
55
+ # Use HEAD by default for efficiency, fallback to GET if needed
56
+ if method.upper() == "HEAD":
57
+ response = requests.head(url, timeout=timeout, allow_redirects=True)
58
+ else:
59
+ response = requests.get(url, timeout=timeout, allow_redirects=True)
60
+
61
+ # Check if request was successful
62
+ response.raise_for_status()
63
+
64
+ # Look for the version header (case-insensitive)
65
+ version = self._find_version_header(dict(response.headers))
66
+ if version:
67
+ self.logger.debug(f"Found target version '{version}' via {method} request to {url}")
68
+ return version
69
+ else:
70
+ self.logger.debug(f"No X-SCYTHE-TARGET-VERSION header found in response from {url}")
71
+ return None
72
+
73
+ except requests.exceptions.RequestException as e:
74
+ self.logger.warning(f"Failed to make {method} request to {url}: {e}")
75
+ return None
76
+ except Exception as e:
77
+ self.logger.warning(f"Unexpected error during banner grab: {e}")
78
+ return None
79
+
80
+ def get_all_headers_via_request(self, url: str, timeout: int = 10, method: str = "HEAD") -> Dict[str, str]:
81
+ """
82
+ Get all headers from a simple HTTP request.
83
+
84
+ Args:
85
+ url: URL to make the request to
86
+ timeout: Request timeout in seconds
87
+ method: HTTP method to use ("HEAD" or "GET")
88
+
89
+ Returns:
90
+ Dictionary of all response headers
91
+ """
92
+ try:
93
+ self.logger.debug(f"Making {method} request to {url} for all headers")
94
+
95
+ if method.upper() == "HEAD":
96
+ response = requests.head(url, timeout=timeout, allow_redirects=True)
97
+ else:
98
+ response = requests.get(url, timeout=timeout, allow_redirects=True)
99
+
100
+ response.raise_for_status()
101
+
102
+ # Convert headers to regular dict with string values
103
+ return {k: str(v) for k, v in response.headers.items()}
104
+
105
+ except requests.exceptions.RequestException as e:
106
+ self.logger.warning(f"Failed to get headers from {url}: {e}")
107
+ return {}
108
+ except Exception as e:
109
+ self.logger.warning(f"Unexpected error getting headers: {e}")
110
+ return {}
111
+
112
+ def debug_headers(self, url: str, timeout: int = 10) -> None:
113
+ """
114
+ Debug method to print all headers received from a URL.
115
+
116
+ This is useful for troubleshooting when headers aren't being detected properly.
117
+ It will show you exactly what headers the server is sending.
118
+
119
+ Args:
120
+ url: URL to make the request to
121
+ timeout: Request timeout in seconds
122
+ """
123
+ print(f"\n{'='*60}")
124
+ print(f"DEBUG: Header dump for {url}")
125
+ print(f"{'='*60}")
126
+
127
+ try:
128
+ # Try HEAD request first
129
+ print("\n--- HEAD Request ---")
130
+ response = requests.head(url, timeout=timeout, allow_redirects=True)
131
+ print(f"Status Code: {response.status_code}")
132
+ print(f"Headers ({len(response.headers)} total):")
133
+
134
+ for name, value in response.headers.items():
135
+ print(f" {name}: {value}")
136
+ if "scythe" in name.lower() or "version" in name.lower():
137
+ print(" *** POTENTIAL VERSION HEADER ***")
138
+
139
+ # Try GET request
140
+ print("\n--- GET Request ---")
141
+ response = requests.get(url, timeout=timeout, allow_redirects=True)
142
+ print(f"Status Code: {response.status_code}")
143
+ print(f"Headers ({len(response.headers)} total):")
144
+
145
+ for name, value in response.headers.items():
146
+ print(f" {name}: {value}")
147
+ if "scythe" in name.lower() or "version" in name.lower():
148
+ print(" *** POTENTIAL VERSION HEADER ***")
149
+
150
+ # Check specifically for the target header
151
+ version = self._find_version_header(dict(response.headers))
152
+ print(f"\nTarget version extraction result: {version}")
153
+
154
+ except Exception as e:
155
+ print(f"ERROR: Failed to debug headers: {e}")
156
+
157
+ print(f"{'='*60}\n")
158
+
159
+ def extract_target_version_hybrid(self, driver: WebDriver, target_url: Optional[str] = None) -> Optional[str]:
160
+ """
161
+ Hybrid approach: Try banner grab first, then fall back to Selenium performance logs.
162
+
163
+ This method attempts to get the version header using a simple HTTP request first,
164
+ which is more reliable than Selenium's performance logging. If that fails or no
165
+ target_url is provided, it falls back to the Selenium-based extraction.
166
+
167
+ Args:
168
+ driver: WebDriver instance
169
+ target_url: URL to check (required for banner grab method)
170
+
171
+ Returns:
172
+ Version string if header found, None otherwise
173
+ """
174
+ # Try banner grab first if we have a target URL
175
+ if target_url:
176
+ self.logger.debug("Attempting banner grab method first")
177
+ version = self.banner_grab(target_url)
178
+ if version:
179
+ self.logger.debug(f"Successfully extracted version '{version}' via banner grab")
180
+ return version
181
+ else:
182
+ self.logger.debug("Banner grab failed, falling back to Selenium performance logs")
183
+
184
+ # Fall back to Selenium performance logs
185
+ self.logger.debug("Using Selenium performance logs method")
186
+ return self.extract_target_version(driver, target_url)
187
+
36
188
  def extract_target_version(self, driver: WebDriver, target_url: Optional[str] = None) -> Optional[str]:
37
189
  """
38
190
  Extract the X-SCYTHE-TARGET-VERSION header from the most recent HTTP response.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.12.1
3
+ Version: 0.12.4
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
@@ -8,7 +8,7 @@ with open("./requirements.txt", "r", encoding="utf-8") as f:
8
8
 
9
9
  setuptools.setup(
10
10
  name="scythe-ttp",
11
- version="0.12.1",
11
+ version="0.12.4",
12
12
  author="EpykLab",
13
13
  author_email="cyber@epyklab.com",
14
14
  description="An extensible framework for emulating attacker TTPs with Selenium.",
scythe_ttp-0.12.1/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.12.2
File without changes
File without changes
File without changes
File without changes