scythe-ttp 0.16.1__py3-none-any.whl → 0.17.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/cli/main.py +145 -0
- scythe/core/executor.py +18 -0
- scythe/journeys/executor.py +6 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/METADATA +2 -1
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/RECORD +9 -9
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/WHEEL +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/entry_points.txt +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/licenses/LICENSE +0 -0
- {scythe_ttp-0.16.1.dist-info → scythe_ttp-0.17.1.dist-info}/top_level.txt +0 -0
scythe/cli/main.py
CHANGED
|
@@ -68,6 +68,19 @@ def check_version_in_response_header(args) -> bool:
|
|
|
68
68
|
def scythe_test_definition(args) -> bool:
|
|
69
69
|
# TODO: implement your test using Scythe primitives.
|
|
70
70
|
# Example placeholder that simply passes.
|
|
71
|
+
|
|
72
|
+
# Example usage with TTPExecutor:
|
|
73
|
+
# from scythe.core.executor import TTPExecutor
|
|
74
|
+
# executor = TTPExecutor(ttp=my_ttp, target_url=args.url)
|
|
75
|
+
# executor.run()
|
|
76
|
+
# return executor.was_successful() # Returns True if all results matched expectations
|
|
77
|
+
|
|
78
|
+
# Example usage with JourneyExecutor:
|
|
79
|
+
# from scythe.journeys.executor import JourneyExecutor
|
|
80
|
+
# executor = JourneyExecutor(journey=my_journey, target_url=args.url)
|
|
81
|
+
# executor.run()
|
|
82
|
+
# return executor.was_successful() # Returns True if journey succeeded as expected
|
|
83
|
+
|
|
71
84
|
return True
|
|
72
85
|
|
|
73
86
|
|
|
@@ -83,6 +96,138 @@ def main():
|
|
|
83
96
|
dest='gate_versions',
|
|
84
97
|
help='Gate versions to test against')
|
|
85
98
|
|
|
99
|
+
# Core Application Parameters
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
'--protocol',
|
|
102
|
+
default='https',
|
|
103
|
+
choices=['http', 'https'],
|
|
104
|
+
help='Protocol to use (http/https, default: https)')
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
'--port',
|
|
107
|
+
type=int,
|
|
108
|
+
help='Port number for the target application')
|
|
109
|
+
|
|
110
|
+
# Authentication Parameters
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
'--username',
|
|
113
|
+
help='Username for authentication')
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
'--password',
|
|
116
|
+
help='Password for authentication')
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
'--token',
|
|
119
|
+
help='Bearer token or API key')
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
'--auth-type',
|
|
122
|
+
choices=['basic', 'bearer', 'form'],
|
|
123
|
+
help='Authentication method (basic, bearer, form, etc.)')
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
'--credentials-file',
|
|
126
|
+
help='Path to file containing multiple user credentials')
|
|
127
|
+
|
|
128
|
+
# Test Data Parameters
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
'--users-file',
|
|
131
|
+
help='Path to CSV file containing user data')
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
'--emails-file',
|
|
134
|
+
help='Path to text file containing email addresses')
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
'--payload-file',
|
|
137
|
+
help='Path to file containing test payloads')
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
'--data-file',
|
|
140
|
+
help='Generic path to test data file')
|
|
141
|
+
|
|
142
|
+
# Execution Control Parameters
|
|
143
|
+
parser.add_argument(
|
|
144
|
+
'--batch-size',
|
|
145
|
+
type=int,
|
|
146
|
+
default=10,
|
|
147
|
+
help='Number of operations per batch (default: 10)')
|
|
148
|
+
parser.add_argument(
|
|
149
|
+
'--max-batches',
|
|
150
|
+
type=int,
|
|
151
|
+
help='Maximum number of batches to run')
|
|
152
|
+
parser.add_argument(
|
|
153
|
+
'--workers',
|
|
154
|
+
type=int,
|
|
155
|
+
help='Number of concurrent workers/threads')
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
'--replications',
|
|
158
|
+
type=int,
|
|
159
|
+
help='Number of test replications for load testing')
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
'--timeout',
|
|
162
|
+
type=int,
|
|
163
|
+
help='Request timeout in seconds')
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
'--delay',
|
|
166
|
+
type=float,
|
|
167
|
+
help='Delay between requests in seconds')
|
|
168
|
+
|
|
169
|
+
# Browser/Execution Parameters
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
'--headless',
|
|
172
|
+
action='store_true',
|
|
173
|
+
help='Run browser in headless mode (flag)')
|
|
174
|
+
parser.add_argument(
|
|
175
|
+
'--browser',
|
|
176
|
+
choices=['chrome', 'firefox', 'safari', 'edge'],
|
|
177
|
+
help='Browser type (chrome, firefox, etc.)')
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
'--user-agent',
|
|
180
|
+
help='Custom user agent string')
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
'--proxy',
|
|
183
|
+
help='Proxy server URL')
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
'--proxy-file',
|
|
186
|
+
help='Path to file containing proxy list')
|
|
187
|
+
|
|
188
|
+
# Output and Reporting Parameters
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
'--output-dir',
|
|
191
|
+
help='Directory for output files')
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
'--report-format',
|
|
194
|
+
choices=['json', 'csv', 'html'],
|
|
195
|
+
help='Report format (json, csv, html)')
|
|
196
|
+
parser.add_argument(
|
|
197
|
+
'--log-level',
|
|
198
|
+
choices=['debug', 'info', 'warning', 'error'],
|
|
199
|
+
help='Logging level (debug, info, warning, error)')
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
'--verbose',
|
|
202
|
+
action='store_true',
|
|
203
|
+
help='Enable verbose output (flag)')
|
|
204
|
+
parser.add_argument(
|
|
205
|
+
'--silent',
|
|
206
|
+
action='store_true',
|
|
207
|
+
help='Suppress output except errors (flag)')
|
|
208
|
+
|
|
209
|
+
# Test Control Parameters
|
|
210
|
+
parser.add_argument(
|
|
211
|
+
'--fail-fast',
|
|
212
|
+
action='store_true',
|
|
213
|
+
help='Stop immediately on first failure (flag)')
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
'--dry-run',
|
|
216
|
+
action='store_true',
|
|
217
|
+
help='Validate configuration without executing tests (flag)')
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
'--test-type',
|
|
220
|
+
choices=['load', 'security', 'functional'],
|
|
221
|
+
help='Type of test to run (load, security, functional)')
|
|
222
|
+
parser.add_argument(
|
|
223
|
+
'--iterations',
|
|
224
|
+
type=int,
|
|
225
|
+
help='Number of test iterations')
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
'--duration',
|
|
228
|
+
type=int,
|
|
229
|
+
help='Test duration in seconds')
|
|
230
|
+
|
|
86
231
|
args = parser.parse_args()
|
|
87
232
|
|
|
88
233
|
if check_url_available(args.url):
|
scythe/core/executor.py
CHANGED
|
@@ -40,6 +40,7 @@ class TTPExecutor:
|
|
|
40
40
|
self.driver = None
|
|
41
41
|
self.results = []
|
|
42
42
|
self.header_extractor = HeaderExtractor()
|
|
43
|
+
self.has_test_failures = False # Track if any test had unexpected results
|
|
43
44
|
|
|
44
45
|
def _setup_driver(self):
|
|
45
46
|
"""Initializes the WebDriver."""
|
|
@@ -134,10 +135,12 @@ class TTPExecutor:
|
|
|
134
135
|
else:
|
|
135
136
|
version_info = f" | Version: {target_version}" if target_version else ""
|
|
136
137
|
self.logger.warning(f"UNEXPECTED SUCCESS: '{payload}' (expected to fail){version_info}")
|
|
138
|
+
self.has_test_failures = True # Mark as failure when result differs from expected
|
|
137
139
|
else:
|
|
138
140
|
consecutive_failures += 1
|
|
139
141
|
if self.ttp.expected_result:
|
|
140
142
|
self.logger.info(f"EXPECTED FAILURE: '{payload}' (security control working)")
|
|
143
|
+
self.has_test_failures = True # Mark as failure when result differs from expected
|
|
141
144
|
else:
|
|
142
145
|
self.logger.info(f"EXPECTED FAILURE: '{payload}'")
|
|
143
146
|
|
|
@@ -212,3 +215,18 @@ class TTPExecutor:
|
|
|
212
215
|
self.logger.info("No successes detected (expected to find vulnerabilities).")
|
|
213
216
|
else:
|
|
214
217
|
self.logger.info("No successes detected (security controls working as expected).")
|
|
218
|
+
|
|
219
|
+
# Log overall test status
|
|
220
|
+
if self.has_test_failures:
|
|
221
|
+
self.logger.error("\n✗ TEST FAILED: One or more test results differed from expected")
|
|
222
|
+
else:
|
|
223
|
+
self.logger.info("\n✓ TEST PASSED: All test results matched expectations")
|
|
224
|
+
|
|
225
|
+
def was_successful(self) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Check if all test results matched expectations.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
True if all test results matched expectations, False otherwise
|
|
231
|
+
"""
|
|
232
|
+
return not self.has_test_failures
|
scythe/journeys/executor.py
CHANGED
|
@@ -406,6 +406,12 @@ class JourneyExecutor:
|
|
|
406
406
|
else:
|
|
407
407
|
self.logger.info("\nNo X-SCYTHE-TARGET-VERSION headers detected in responses.")
|
|
408
408
|
|
|
409
|
+
# Log overall test status (similar to TTPExecutor)
|
|
410
|
+
if self.was_successful():
|
|
411
|
+
self.logger.info("\n✓ TEST PASSED: Journey results matched expectations")
|
|
412
|
+
else:
|
|
413
|
+
self.logger.error("\n✗ TEST FAILED: Journey results differed from expected")
|
|
414
|
+
|
|
409
415
|
self.logger.info("="*60)
|
|
410
416
|
|
|
411
417
|
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.
|
|
3
|
+
Version: 0.17.1
|
|
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
|
|
@@ -35,6 +35,7 @@ Requires-Dist: urllib3==2.4.0
|
|
|
35
35
|
Requires-Dist: websocket-client==1.8.0
|
|
36
36
|
Requires-Dist: wsproto==1.2.0
|
|
37
37
|
Requires-Dist: typer
|
|
38
|
+
Requires-Dist: shellingham
|
|
38
39
|
Dynamic: license-file
|
|
39
40
|
|
|
40
41
|
<h1 align="center">Scythe</h1>
|
|
@@ -11,15 +11,15 @@ scythe/behaviors/human.py,sha256=1PqYvE7cnxlj-KDmDIr3uzfWHvDAbbxQxJ0V0iTl9yo,102
|
|
|
11
11
|
scythe/behaviors/machine.py,sha256=NDMUq3mDhpCvujzAFxhn2eSVq78-J-LSBhIcvHkzKXo,4624
|
|
12
12
|
scythe/behaviors/stealth.py,sha256=xv7MrPQgRCdCUJyYTcXV2aasWZoAw8rAQWg-AuQVb7U,15278
|
|
13
13
|
scythe/cli/__init__.py,sha256=9EVxmFiWsAoqWJ6br1bc3BxlA71JyOQP28fUHhX2k7E,43
|
|
14
|
-
scythe/cli/main.py,sha256
|
|
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=
|
|
16
|
+
scythe/core/executor.py,sha256=C3FkW-DNugv82T0_ky-3zAvHV_hFwVSHrX2nzgAcmAI,10588
|
|
17
17
|
scythe/core/headers.py,sha256=AokCQ3F5QGUcfoK7iO57hA1HHL4IznZeWV464_MqYcE,16670
|
|
18
18
|
scythe/core/ttp.py,sha256=Xw9GgptYsjZ-pMLdyPv64bhiwGKobrXHdF32pjIY7OU,3102
|
|
19
19
|
scythe/journeys/__init__.py,sha256=Odi8NhRg7Hefmo1EJj1guakrCSPhsuus4i-_62uUUjs,654
|
|
20
20
|
scythe/journeys/actions.py,sha256=k9WjfGR1nhJWyhDU_lHr7vFy5qAl7hyyV6kCL7ZQRMo,37479
|
|
21
21
|
scythe/journeys/base.py,sha256=vXIgEnSW__iYTriBbuMG4l_XCM96xojJH_fyFScKoBY,24969
|
|
22
|
-
scythe/journeys/executor.py,sha256=
|
|
22
|
+
scythe/journeys/executor.py,sha256=uJkjO3PALSLZh3IOSxgX18gRJX_Bck3gW9OClusiQeE,24949
|
|
23
23
|
scythe/orchestrators/__init__.py,sha256=_vemcXjKbB1jI0F2dPA0F1zNsyUekjcXImLDUDhWDN0,560
|
|
24
24
|
scythe/orchestrators/base.py,sha256=YOZV0ewlzJ49H08P_LKnimutUms8NnDrQprFpSKhOeM,13595
|
|
25
25
|
scythe/orchestrators/batch.py,sha256=FpK501kk-earJzz6v7dcuw2y708rTvt_IMH_5qjKdrc,26635
|
|
@@ -32,9 +32,9 @@ scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
32
32
|
scythe/ttps/web/login_bruteforce.py,sha256=D4G8zB_nU9LD5w3Vv2ABTuOl4XTeg2BgZwYMObt4JJw,2488
|
|
33
33
|
scythe/ttps/web/sql_injection.py,sha256=aWk4DFePbtFDsieOOj03Ux-5OiykyOs2_d_3SvWMOVE,2910
|
|
34
34
|
scythe/ttps/web/uuid_guessing.py,sha256=JwNt_9HVynMWFPPU6UGJFcpxvMVDsvc_wAnJVtcYbps,1235
|
|
35
|
-
scythe_ttp-0.
|
|
36
|
-
scythe_ttp-0.
|
|
37
|
-
scythe_ttp-0.
|
|
38
|
-
scythe_ttp-0.
|
|
39
|
-
scythe_ttp-0.
|
|
40
|
-
scythe_ttp-0.
|
|
35
|
+
scythe_ttp-0.17.1.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
|
|
36
|
+
scythe_ttp-0.17.1.dist-info/METADATA,sha256=bZTf4pApqa3-NNPv6PkktiBPKSgiOOhjv0a7DJmn7To,30188
|
|
37
|
+
scythe_ttp-0.17.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
scythe_ttp-0.17.1.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
|
|
39
|
+
scythe_ttp-0.17.1.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
|
|
40
|
+
scythe_ttp-0.17.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|