scythe-ttp 0.15.2__py3-none-any.whl → 0.18.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.
@@ -1,28 +1,65 @@
1
1
  from selenium.webdriver.common.by import By
2
2
  from selenium.webdriver.remote.webdriver import WebDriver
3
3
  from selenium.common.exceptions import NoSuchElementException
4
+ from typing import Dict, Any, Optional
5
+ import requests
6
+
4
7
  from ...core.ttp import TTP
5
8
  from ...payloads.generators import PayloadGenerator
6
9
 
7
10
  class InputFieldInjector(TTP):
11
+ """
12
+ SQL Injection TTP that tests input fields for SQL injection vulnerabilities.
13
+
14
+ Supports two execution modes:
15
+ - UI mode: Fills form fields with SQL payloads
16
+ - API mode: Sends SQL payloads in API request body fields
17
+ """
8
18
  def __init__(self,
9
- target_url: str,
10
- field_selector: str,
11
- submit_selector: str,
12
- payload_generator: PayloadGenerator,
19
+ target_url: str = None,
20
+ field_selector: str = None,
21
+ submit_selector: str = None,
22
+ payload_generator: PayloadGenerator = None,
13
23
  expected_result: bool = True,
14
- authentication=None):
15
-
24
+ authentication=None,
25
+ execution_mode: str = 'ui',
26
+ api_endpoint: Optional[str] = None,
27
+ injection_field: str = 'query',
28
+ http_method: str = 'POST'):
29
+ """
30
+ Initialize the SQL Injection TTP.
31
+
32
+ Args:
33
+ target_url: Target URL (UI mode)
34
+ field_selector: CSS/tag selector for input field (UI mode)
35
+ submit_selector: CSS selector for submit button (UI mode)
36
+ payload_generator: Generator that yields SQL injection payloads
37
+ expected_result: Whether we expect to find SQL injection vulnerabilities
38
+ authentication: Optional authentication
39
+ execution_mode: 'ui' or 'api'
40
+ api_endpoint: API endpoint path (API mode, e.g., '/api/search')
41
+ injection_field: Field name to inject SQL payload into (API mode)
42
+ http_method: HTTP method to use (API mode) - 'POST' or 'GET'
43
+ """
16
44
  super().__init__(
17
- name="SQL Injection via URL manipulation",
18
- description="simulate an sql Injection by manipulation of url queries",
45
+ name="SQL Injection via Input Field",
46
+ description="Simulate SQL injection by injecting payloads into input fields",
19
47
  expected_result=expected_result,
20
- authentication=authentication)
48
+ authentication=authentication,
49
+ execution_mode=execution_mode)
21
50
 
51
+ # UI mode fields
22
52
  self.target_url = target_url
23
53
  self.field_selector = field_selector
24
- self.payload_generator = payload_generator
25
54
  self.submit_selector = submit_selector
55
+
56
+ # Common fields
57
+ self.payload_generator = payload_generator
58
+
59
+ # API mode fields
60
+ self.api_endpoint = api_endpoint
61
+ self.injection_field = injection_field
62
+ self.http_method = http_method.upper()
26
63
 
27
64
  def get_payloads(self):
28
65
  """yields queries from the configured generator"""
@@ -51,30 +88,210 @@ class InputFieldInjector(TTP):
51
88
 
52
89
 
53
90
  def verify_result(self, driver: WebDriver) -> bool:
91
+ """Checks for SQL error indicators in the page source (UI mode)."""
54
92
  return "sql" in driver.page_source.lower() or \
55
93
  "source" in driver.page_source.lower()
94
+
95
+ def execute_step_api(self, session: requests.Session, payload: str, context: Dict[str, Any]) -> requests.Response:
96
+ """
97
+ Executes a SQL injection attempt via API request.
98
+
99
+ Args:
100
+ session: requests.Session for making HTTP requests
101
+ payload: The SQL injection payload to test
102
+ context: Shared context dictionary
103
+
104
+ Returns:
105
+ requests.Response from the injection attempt
106
+ """
107
+ from urllib.parse import urljoin
108
+
109
+ # Build the full URL
110
+ base_url = context.get('target_url', '')
111
+ if not base_url:
112
+ raise ValueError("target_url must be set in context for API mode")
113
+
114
+ url = urljoin(base_url, self.api_endpoint or '/search')
115
+
116
+ # Merge auth headers from context
117
+ headers = {}
118
+ auth_headers = context.get('auth_headers', {})
119
+ if auth_headers:
120
+ headers.update(auth_headers)
121
+
122
+ # Honor rate limiting
123
+ import time
124
+ resume_at = context.get('rate_limit_resume_at')
125
+ now = time.time()
126
+ if isinstance(resume_at, (int, float)) and resume_at > now:
127
+ wait_s = min(resume_at - now, 30)
128
+ if wait_s > 0:
129
+ time.sleep(wait_s)
130
+
131
+ # Make the request based on HTTP method
132
+ if self.http_method == 'GET':
133
+ # For GET, put payload in query params
134
+ response = session.get(url, params={self.injection_field: payload}, headers=headers or None, timeout=10.0)
135
+ else:
136
+ # For POST/PUT/etc, put payload in JSON body
137
+ body = {self.injection_field: payload}
138
+ response = session.request(self.http_method, url, json=body, headers=headers or None, timeout=10.0)
139
+
140
+ # Handle rate limiting
141
+ if response.status_code == 429:
142
+ retry_after = response.headers.get('Retry-After', '1')
143
+ try:
144
+ wait_s = int(retry_after)
145
+ except (ValueError, TypeError):
146
+ wait_s = 1
147
+ context['rate_limit_resume_at'] = time.time() + min(wait_s, 30)
148
+
149
+ return response
150
+
151
+ def verify_result_api(self, response: requests.Response, context: Dict[str, Any]) -> bool:
152
+ """
153
+ Verifies if the SQL injection attempt triggered a vulnerability.
154
+
155
+ Args:
156
+ response: The response from execute_step_api
157
+ context: Shared context dictionary
158
+
159
+ Returns:
160
+ True if SQL error indicators found, False otherwise
161
+ """
162
+ try:
163
+ response_text = response.text.lower()
164
+ # Common SQL error indicators
165
+ sql_indicators = [
166
+ 'sql', 'syntax', 'mysql', 'sqlite', 'postgresql', 'oracle',
167
+ 'odbc', 'jdbc', 'driver', 'database', 'query', 'syntax error',
168
+ 'unterminated', 'unexpected', 'warning: mysql'
169
+ ]
170
+ return any(indicator in response_text for indicator in sql_indicators)
171
+ except Exception:
172
+ return False
56
173
 
57
174
 
58
175
  class URLManipulation(TTP):
176
+ """
177
+ SQL Injection TTP that tests URL query parameters for SQL injection vulnerabilities.
178
+
179
+ Supports two execution modes:
180
+ - UI mode: Navigates to URLs with SQL payloads in query parameters
181
+ - API mode: Sends GET requests with SQL payloads in query parameters
182
+ """
59
183
  def __init__(self,
60
184
  payload_generator: PayloadGenerator,
61
- target_url: str,
185
+ target_url: str = None,
62
186
  expected_result: bool = True,
63
- authentication=None):
187
+ authentication=None,
188
+ execution_mode: str = 'ui',
189
+ api_endpoint: Optional[str] = None,
190
+ query_param: str = 'q'):
191
+ """
192
+ Initialize the URL Manipulation SQL Injection TTP.
193
+
194
+ Args:
195
+ payload_generator: Generator that yields SQL injection payloads
196
+ target_url: Target URL (UI mode)
197
+ expected_result: Whether we expect to find SQL injection vulnerabilities
198
+ authentication: Optional authentication
199
+ execution_mode: 'ui' or 'api'
200
+ api_endpoint: API endpoint path (API mode, e.g., '/api/search')
201
+ query_param: Query parameter name to inject into (default: 'q')
202
+ """
64
203
  super().__init__(
65
204
  name="SQL Injection via URL manipulation",
66
- description="simulate an sql Injection by manipulation of url queries",
205
+ description="Simulate SQL injection by manipulating URL query parameters",
67
206
  expected_result=expected_result,
68
- authentication=authentication)
207
+ authentication=authentication,
208
+ execution_mode=execution_mode)
69
209
  self.target_url = target_url
70
210
  self.payload_generator = payload_generator
211
+ self.api_endpoint = api_endpoint
212
+ self.query_param = query_param
71
213
 
72
214
  def get_payloads(self):
73
215
  yield from self.payload_generator()
74
216
 
75
217
  def execute_step(self, driver: WebDriver, payload: str):
76
- driver.get(f"{self.target_url}?q={payload}")
218
+ """Execute SQL injection via URL manipulation in UI mode."""
219
+ driver.get(f"{self.target_url}?{self.query_param}={payload}")
77
220
 
78
221
  def verify_result(self, driver: WebDriver) -> bool:
222
+ """Check for SQL error indicators in UI mode."""
79
223
  return "sql" in driver.page_source.lower() or \
80
224
  "source" in driver.page_source.lower()
225
+
226
+ def execute_step_api(self, session: requests.Session, payload: str, context: Dict[str, Any]) -> requests.Response:
227
+ """
228
+ Executes a SQL injection attempt via API request with query parameters.
229
+
230
+ Args:
231
+ session: requests.Session for making HTTP requests
232
+ payload: The SQL injection payload to test
233
+ context: Shared context dictionary
234
+
235
+ Returns:
236
+ requests.Response from the injection attempt
237
+ """
238
+ from urllib.parse import urljoin
239
+
240
+ # Build the full URL
241
+ base_url = context.get('target_url', '')
242
+ if not base_url:
243
+ raise ValueError("target_url must be set in context for API mode")
244
+
245
+ url = urljoin(base_url, self.api_endpoint or self.target_url or '/')
246
+
247
+ # Merge auth headers from context
248
+ headers = {}
249
+ auth_headers = context.get('auth_headers', {})
250
+ if auth_headers:
251
+ headers.update(auth_headers)
252
+
253
+ # Honor rate limiting
254
+ import time
255
+ resume_at = context.get('rate_limit_resume_at')
256
+ now = time.time()
257
+ if isinstance(resume_at, (int, float)) and resume_at > now:
258
+ wait_s = min(resume_at - now, 30)
259
+ if wait_s > 0:
260
+ time.sleep(wait_s)
261
+
262
+ # Make GET request with payload in query param
263
+ response = session.get(url, params={self.query_param: payload}, headers=headers or None, timeout=10.0)
264
+
265
+ # Handle rate limiting
266
+ if response.status_code == 429:
267
+ retry_after = response.headers.get('Retry-After', '1')
268
+ try:
269
+ wait_s = int(retry_after)
270
+ except (ValueError, TypeError):
271
+ wait_s = 1
272
+ context['rate_limit_resume_at'] = time.time() + min(wait_s, 30)
273
+
274
+ return response
275
+
276
+ def verify_result_api(self, response: requests.Response, context: Dict[str, Any]) -> bool:
277
+ """
278
+ Verifies if the SQL injection attempt triggered a vulnerability.
279
+
280
+ Args:
281
+ response: The response from execute_step_api
282
+ context: Shared context dictionary
283
+
284
+ Returns:
285
+ True if SQL error indicators found, False otherwise
286
+ """
287
+ try:
288
+ response_text = response.text.lower()
289
+ # Common SQL error indicators
290
+ sql_indicators = [
291
+ 'sql', 'syntax', 'mysql', 'sqlite', 'postgresql', 'oracle',
292
+ 'odbc', 'jdbc', 'driver', 'database', 'query', 'syntax error',
293
+ 'unterminated', 'unexpected', 'warning: mysql'
294
+ ]
295
+ return any(indicator in response_text for indicator in sql_indicators)
296
+ except Exception:
297
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.15.2
3
+ Version: 0.18.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>
@@ -3,7 +3,7 @@ scythe/auth/__init__.py,sha256=InEANqWEIAULFyzH9IyxWDPs_gJd3m_JYmzoaBk_37M,420
3
3
  scythe/auth/base.py,sha256=DllKaPGj0MRyRh4PQgQ2TUFgeAXjgXOT2h6zUz2ZAag,3807
4
4
  scythe/auth/basic.py,sha256=H4IG9-Y7wFe7ZQCNHmmqhre-Pp9CnBxlT23h2uvOPWo,14354
5
5
  scythe/auth/bearer.py,sha256=ngOL-sS6FcfB8XAKMR-CZbpqyySu2MaKxUl10SyBmmI,12687
6
- scythe/auth/cookie_jwt.py,sha256=z5Q-c594_m-dmh2Rv_4Xfeu0fXSQdlZ12Q-emtyj63g,6337
6
+ scythe/auth/cookie_jwt.py,sha256=A5ysCd6mVPNRj3aGNhk68GyM8A2hNW4BqCLZOQ8sa5U,7610
7
7
  scythe/behaviors/__init__.py,sha256=w-WRBGRgna5a1N8oHP2aXSQnkQUHyOXiujpwEVf_ZyM,291
8
8
  scythe/behaviors/base.py,sha256=INvIYKVIWzEi5w_4njOwKZ3X9IvySvqiMJnYX7_2Lns,3955
9
9
  scythe/behaviors/default.py,sha256=MDx4N-KwC23pPLGu1-ZIkGiTRNUG3Lxjbvo7SJ3UwMc,2117
@@ -11,30 +11,31 @@ 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=_6O6wxp4JV4k1vCSl5mMjO42-uIwTI0HoK7JRBfBHKk,21356
14
+ scythe/cli/main.py,sha256=DYF8iyJqmDwuen9By4DMX6GytwKQXwXh8Gk3LXW1Ypg,27480
15
15
  scythe/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- scythe/core/executor.py,sha256=x1w2nByVu2G70sh7t0kOh6urlrTm_r_pbk0S7v1Ov28,9736
16
+ scythe/core/executor.py,sha256=F8r_t5vSvsxNuUFx-Y0JzSe051cj_w42iyZrS7wocb4,16306
17
17
  scythe/core/headers.py,sha256=AokCQ3F5QGUcfoK7iO57hA1HHL4IznZeWV464_MqYcE,16670
18
- scythe/core/ttp.py,sha256=Xw9GgptYsjZ-pMLdyPv64bhiwGKobrXHdF32pjIY7OU,3102
18
+ scythe/core/ttp.py,sha256=tEYIhDdr8kcwQrlcfVmdeLFiAfOvc0BhPOVxPh8TiWo,5676
19
19
  scythe/journeys/__init__.py,sha256=Odi8NhRg7Hefmo1EJj1guakrCSPhsuus4i-_62uUUjs,654
20
- scythe/journeys/actions.py,sha256=cDBYdhY5pCXKG-57-op8gH8z9u3_wbIOhwqSZ2Z_jDs,36432
20
+ scythe/journeys/actions.py,sha256=URr53p1GQxSIBZo0IubchQ1dlfvnPHgCtmkRfLSoi7A,40333
21
21
  scythe/journeys/base.py,sha256=vXIgEnSW__iYTriBbuMG4l_XCM96xojJH_fyFScKoBY,24969
22
- scythe/journeys/executor.py,sha256=_q2hzl4G9iv07I6NVMtNaK3O8QGLDwLNMiaxIle-nsY,24654
22
+ scythe/journeys/executor.py,sha256=wSAFFU9qwyYA2Q_5TZBUDgafVb1slwvj0VNJ7cR46FE,25223
23
23
  scythe/orchestrators/__init__.py,sha256=_vemcXjKbB1jI0F2dPA0F1zNsyUekjcXImLDUDhWDN0,560
24
- scythe/orchestrators/base.py,sha256=YOZV0ewlzJ49H08P_LKnimutUms8NnDrQprFpSKhOeM,13595
24
+ scythe/orchestrators/base.py,sha256=oZ68VmX18mBITvFUXfHJYvIE20PCfEAKrsikdr6_0Qg,14219
25
25
  scythe/orchestrators/batch.py,sha256=FpK501kk-earJzz6v7dcuw2y708rTvt_IMH_5qjKdrc,26635
26
26
  scythe/orchestrators/distributed.py,sha256=ts19NZzPfr0ouFbUFrktPO-iL5jBNB57mcOh0eDJDmE,33554
27
27
  scythe/orchestrators/scale.py,sha256=l2-4U6ISeBDBZz7CG6ef9W-zyF6LiAM4uXXKlfczLB0,21394
28
28
  scythe/payloads/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  scythe/payloads/generators.py,sha256=tCcJULoFnUppgaiFhYq5f20OoQxTdKKIb2O-Ntby9ZM,914
30
30
  scythe/ttps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- scythe/ttps/web/login_bruteforce.py,sha256=D4G8zB_nU9LD5w3Vv2ABTuOl4XTeg2BgZwYMObt4JJw,2488
33
- scythe/ttps/web/sql_injection.py,sha256=aWk4DFePbtFDsieOOj03Ux-5OiykyOs2_d_3SvWMOVE,2910
31
+ scythe/ttps/web/__init__.py,sha256=xSAg10WySjbjLl4T1r-mZ3-DkDU-5A3pjibo5gymGbI,341
32
+ scythe/ttps/web/login_bruteforce.py,sha256=ybmN2Vl9-p58YbOchirY1193GvlaTmUTW1qlluN_l3I,7816
33
+ scythe/ttps/web/request_flooding.py,sha256=hPdYj1FpGtVcAYqsSabo-J_uL-sbc_DUkHS7Op6Qchk,22146
34
+ scythe/ttps/web/sql_injection.py,sha256=rIRHaRUilSrMA5q5MO1RqR6-TM_fRIiCanPaFz5wKKs,11712
34
35
  scythe/ttps/web/uuid_guessing.py,sha256=JwNt_9HVynMWFPPU6UGJFcpxvMVDsvc_wAnJVtcYbps,1235
35
- scythe_ttp-0.15.2.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
- scythe_ttp-0.15.2.dist-info/METADATA,sha256=WSQt_hazIFYYW-rWj40BtxevUMM04E84hEgI4t39qXM,30161
37
- scythe_ttp-0.15.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- scythe_ttp-0.15.2.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
- scythe_ttp-0.15.2.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
- scythe_ttp-0.15.2.dist-info/RECORD,,
36
+ scythe_ttp-0.18.1.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
37
+ scythe_ttp-0.18.1.dist-info/METADATA,sha256=cUP37iBhA9aM1rUUuX-ox52MJ_rMHTFeswiyHeM4IzQ,30188
38
+ scythe_ttp-0.18.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ scythe_ttp-0.18.1.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
40
+ scythe_ttp-0.18.1.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
41
+ scythe_ttp-0.18.1.dist-info/RECORD,,