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.
- {scythe_ttp-0.12.1/scythe_ttp.egg-info → scythe_ttp-0.12.4}/PKG-INFO +1 -1
- scythe_ttp-0.12.4/VERSION +1 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/headers.py +152 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4/scythe_ttp.egg-info}/PKG-INFO +1 -1
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/setup.py +1 -1
- scythe_ttp-0.12.1/VERSION +0 -1
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/LICENSE +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/MANIFEST.in +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/README.md +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/requirements.txt +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/base.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/basic.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/auth/bearer.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/base.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/default.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/human.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/machine.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/behaviors/stealth.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/executor.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/core/ttp.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/actions.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/base.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/journeys/executor.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/base.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/batch.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/distributed.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/orchestrators/scale.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/payloads/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/payloads/generators.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/__init__.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/login_bruteforce.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/sql_injection.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe/ttps/web/uuid_guessing.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/SOURCES.txt +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/dependency_links.txt +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/requires.txt +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/scythe_ttp.egg-info/top_level.txt +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/setup.cfg +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_authentication.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_behaviors.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_expected_results.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_feature_completeness.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_header_extraction.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_journeys.py +0 -0
- {scythe_ttp-0.12.1 → scythe_ttp-0.12.4}/tests/test_orchestrators.py +0 -0
|
@@ -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.
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|