bbot 2.4.2__py3-none-any.whl → 2.4.2.6590rc0__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 bbot might be problematic. Click here for more details.

Files changed (64) hide show
  1. bbot/__init__.py +1 -1
  2. bbot/core/event/base.py +64 -4
  3. bbot/core/helpers/diff.py +10 -7
  4. bbot/core/helpers/helper.py +5 -1
  5. bbot/core/helpers/misc.py +48 -11
  6. bbot/core/helpers/regex.py +4 -0
  7. bbot/core/helpers/regexes.py +45 -8
  8. bbot/core/helpers/url.py +21 -5
  9. bbot/core/helpers/web/client.py +25 -5
  10. bbot/core/helpers/web/engine.py +9 -1
  11. bbot/core/helpers/web/envelopes.py +352 -0
  12. bbot/core/helpers/web/web.py +10 -2
  13. bbot/core/helpers/yara_helper.py +50 -0
  14. bbot/core/modules.py +23 -7
  15. bbot/defaults.yml +26 -1
  16. bbot/modules/base.py +4 -2
  17. bbot/modules/{deadly/dastardly.py → dastardly.py} +1 -1
  18. bbot/modules/{deadly/ffuf.py → ffuf.py} +1 -1
  19. bbot/modules/ffuf_shortnames.py +1 -1
  20. bbot/modules/httpx.py +14 -0
  21. bbot/modules/hunt.py +24 -6
  22. bbot/modules/internal/aggregate.py +1 -0
  23. bbot/modules/internal/excavate.py +356 -197
  24. bbot/modules/lightfuzz/lightfuzz.py +203 -0
  25. bbot/modules/lightfuzz/submodules/__init__.py +0 -0
  26. bbot/modules/lightfuzz/submodules/base.py +312 -0
  27. bbot/modules/lightfuzz/submodules/cmdi.py +106 -0
  28. bbot/modules/lightfuzz/submodules/crypto.py +474 -0
  29. bbot/modules/lightfuzz/submodules/nosqli.py +183 -0
  30. bbot/modules/lightfuzz/submodules/path.py +154 -0
  31. bbot/modules/lightfuzz/submodules/serial.py +179 -0
  32. bbot/modules/lightfuzz/submodules/sqli.py +187 -0
  33. bbot/modules/lightfuzz/submodules/ssti.py +39 -0
  34. bbot/modules/lightfuzz/submodules/xss.py +191 -0
  35. bbot/modules/{deadly/nuclei.py → nuclei.py} +1 -1
  36. bbot/modules/paramminer_headers.py +2 -0
  37. bbot/modules/reflected_parameters.py +80 -0
  38. bbot/modules/{deadly/vhost.py → vhost.py} +2 -2
  39. bbot/presets/web/lightfuzz-heavy.yml +16 -0
  40. bbot/presets/web/lightfuzz-light.yml +20 -0
  41. bbot/presets/web/lightfuzz-medium.yml +14 -0
  42. bbot/presets/web/lightfuzz-superheavy.yml +13 -0
  43. bbot/presets/web/lightfuzz-xss.yml +21 -0
  44. bbot/presets/web/paramminer.yml +8 -5
  45. bbot/scanner/preset/args.py +26 -0
  46. bbot/scanner/scanner.py +6 -0
  47. bbot/test/test_step_1/test__module__tests.py +1 -1
  48. bbot/test/test_step_1/test_helpers.py +7 -0
  49. bbot/test/test_step_1/test_presets.py +2 -2
  50. bbot/test/test_step_1/test_web.py +20 -0
  51. bbot/test/test_step_1/test_web_envelopes.py +343 -0
  52. bbot/test/test_step_2/module_tests/test_module_excavate.py +404 -29
  53. bbot/test/test_step_2/module_tests/test_module_httpx.py +29 -0
  54. bbot/test/test_step_2/module_tests/test_module_hunt.py +18 -1
  55. bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +1947 -0
  56. bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +4 -1
  57. bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +46 -2
  58. bbot/test/test_step_2/module_tests/test_module_reflected_parameters.py +226 -0
  59. bbot/wordlists/paramminer_parameters.txt +0 -8
  60. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/METADATA +2 -1
  61. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/RECORD +64 -42
  62. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/LICENSE +0 -0
  63. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/WHEEL +0 -0
  64. {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,1947 @@
1
+ import json
2
+ import re
3
+ import base64
4
+
5
+ from .base import ModuleTestBase, tempwordlist
6
+ from werkzeug.wrappers import Response
7
+ from urllib.parse import unquote, quote
8
+
9
+ import xml.etree.ElementTree as ET
10
+
11
+ from .test_module_paramminer_headers import helper
12
+
13
+
14
+ # Path Traversal single dot tolerance
15
+ class Test_Lightfuzz_path_singledot(ModuleTestBase):
16
+ targets = ["http://127.0.0.1:8888"]
17
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
18
+ config_overrides = {
19
+ "interactsh_disable": True,
20
+ "modules": {
21
+ "lightfuzz": {
22
+ "enabled_submodules": ["path"],
23
+ }
24
+ },
25
+ }
26
+
27
+ async def setup_after_prep(self, module_test):
28
+ expect_args = re.compile("/images")
29
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
30
+ respond_args = {
31
+ "response_data": '"<section class="images"><img src="/images?filename=default.jpg"></section>',
32
+ "status": 200,
33
+ }
34
+
35
+ expect_args = {"method": "GET", "uri": "/"}
36
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
37
+
38
+ def request_handler(self, request):
39
+ qs = str(request.query_string.decode())
40
+ if "filename=" in qs:
41
+ value = qs.split("=")[1]
42
+
43
+ if "&" in value:
44
+ value = value.split("&")[0]
45
+
46
+ block = """
47
+ <svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
48
+ <rect width="1" height="1" fill="black"/>
49
+ </svg>
50
+ """
51
+ if value == "%2F.%2Fa%2F..%2Fdefault.jpg" or value == "default.jpg":
52
+ return Response(block, status=200)
53
+ return Response("file not found", status=500)
54
+
55
+ def check(self, module_test, events):
56
+ web_parameter_emitted = False
57
+ pathtraversal_finding_emitted = False
58
+ for e in events:
59
+ if e.type == "WEB_PARAMETER":
60
+ if "HTTP Extracted Parameter [filename]" in e.data["description"]:
61
+ web_parameter_emitted = True
62
+
63
+ if e.type == "FINDING":
64
+ if (
65
+ "POSSIBLE Path Traversal. Parameter: [filename] Parameter Type: [GETPARAM] Original Value: [default.jpg] Detection Method: [single-dot traversal tolerance (url-encoding, leading slash)]"
66
+ in e.data["description"]
67
+ ):
68
+ pathtraversal_finding_emitted = True
69
+
70
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
71
+ assert pathtraversal_finding_emitted, "Path Traversal single dot tolerance FINDING not emitted"
72
+
73
+
74
+ # Path Traversal Absolute path
75
+ class Test_Lightfuzz_path_absolute(Test_Lightfuzz_path_singledot):
76
+ etc_passwd = """
77
+ root:x:0:0:root:/root:/bin/bash
78
+ daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
79
+ bin:x:2:2:bin:/bin:/usr/sbin/nologin
80
+ sys:x:3:3:sys:/dev:/usr/sbin/nologin
81
+ sync:x:4:65534:sync:/bin:/bin/sync
82
+ games:x:5:60:games:/usr/games:/usr/sbin/nologin
83
+ man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
84
+ lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
85
+ """
86
+
87
+ async def setup_after_prep(self, module_test):
88
+ expect_args = {"method": "GET", "uri": "/images", "query_string": "filename=/etc/passwd"}
89
+ respond_args = {"response_data": self.etc_passwd}
90
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
91
+
92
+ expect_args = {"method": "GET", "uri": "/images"}
93
+ respond_args = {"response_data": "<html><head><body><p>ERROR: Invalid File</p></body></html>", "status": 200}
94
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
95
+
96
+ expect_args = {"method": "GET", "uri": "/"}
97
+ respond_args = {
98
+ "response_data": '"<section class="images"><img src="/images?filename=default.jpg"></section>',
99
+ "status": 200,
100
+ }
101
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
102
+
103
+ def check(self, module_test, events):
104
+ web_parameter_emitted = False
105
+ pathtraversal_finding_emitted = False
106
+ for e in events:
107
+ if e.type == "WEB_PARAMETER":
108
+ if "HTTP Extracted Parameter [filename]" in e.data["description"]:
109
+ web_parameter_emitted = True
110
+
111
+ if e.type == "FINDING":
112
+ if (
113
+ "POSSIBLE Path Traversal. Parameter: [filename] Parameter Type: [GETPARAM] Original Value: [default.jpg] Detection Method: [Absolute Path: /etc/passwd]"
114
+ in e.data["description"]
115
+ ):
116
+ pathtraversal_finding_emitted = True
117
+
118
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
119
+ assert pathtraversal_finding_emitted, "Path Traversal single dot tolerance FINDING not emitted"
120
+
121
+
122
+ # SSTI Integer Multiplcation
123
+ class Test_Lightfuzz_ssti_multiply(ModuleTestBase):
124
+ targets = ["http://127.0.0.1:8888"]
125
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
126
+ config_overrides = {
127
+ "interactsh_disable": True,
128
+ "modules": {
129
+ "lightfuzz": {
130
+ "enabled_submodules": ["ssti"],
131
+ }
132
+ },
133
+ }
134
+
135
+ def request_handler(self, request):
136
+ qs = str(request.query_string.decode())
137
+ if "data=" in qs:
138
+ value = qs.split("=")[1]
139
+ if "&" in value:
140
+ value = value.split("&")[0]
141
+ nums = value.split("%20")[1].split("*")
142
+ ints = [int(s) for s in nums]
143
+ ssti_block = f"<html><div class=data>{str(ints[0] * ints[1])}</div</html>"
144
+ return Response(ssti_block, status=200)
145
+
146
+ async def setup_after_prep(self, module_test):
147
+ expect_args = {"method": "GET", "uri": "/"}
148
+ respond_args = {"response_data": "", "status": 302, "headers": {"Location": "/test?data=9"}}
149
+ module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args)
150
+
151
+ expect_args = re.compile("/test.*")
152
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
153
+
154
+ def check(self, module_test, events):
155
+ web_parameter_emitted = False
156
+ ssti_finding_emitted = False
157
+ for e in events:
158
+ if e.type == "WEB_PARAMETER":
159
+ if "HTTP Extracted Parameter [data]" in e.data["description"]:
160
+ web_parameter_emitted = True
161
+
162
+ if e.type == "FINDING":
163
+ if (
164
+ "POSSIBLE Server-side Template Injection. Parameter: [data] Parameter Type: [GETPARAM] Original Value: [9] Detection Method: [Integer Multiplication]"
165
+ in e.data["description"]
166
+ ):
167
+ ssti_finding_emitted = True
168
+
169
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
170
+ assert ssti_finding_emitted, "SSTI integer multiply FINDING not emitted"
171
+
172
+
173
+ # Between Tags XSS Detection
174
+ class Test_Lightfuzz_xss(ModuleTestBase):
175
+ targets = ["http://127.0.0.1:8888"]
176
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
177
+ config_overrides = {
178
+ "interactsh_disable": True,
179
+ "modules": {
180
+ "lightfuzz": {
181
+ "enabled_submodules": ["xss"],
182
+ }
183
+ },
184
+ }
185
+
186
+ def request_handler(self, request):
187
+ qs = str(request.query_string.decode())
188
+
189
+ parameter_block = """
190
+ <section class=search>
191
+ <form action=/ method=GET>
192
+ <input type=text placeholder='Search the blog...' name=search>
193
+ <button type=submit class=button>Search</button>
194
+ </form>
195
+ </section>
196
+ """
197
+ if "search=" in qs:
198
+ value = qs.split("=")[1]
199
+ if "&" in value:
200
+ value = value.split("&")[0]
201
+ xss_block = f"""
202
+ <section class=blog-header>
203
+ <h1>0 search results for '{unquote(value)}'</h1>
204
+ <hr>
205
+ </section>
206
+ """
207
+ return Response(xss_block, status=200)
208
+ return Response(parameter_block, status=200)
209
+
210
+ async def setup_after_prep(self, module_test):
211
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
212
+ expect_args = re.compile("/")
213
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
214
+
215
+ def check(self, module_test, events):
216
+ web_parameter_emitted = False
217
+ xss_finding_emitted = False
218
+ for e in events:
219
+ if e.type == "WEB_PARAMETER":
220
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
221
+ web_parameter_emitted = True
222
+
223
+ if e.type == "FINDING":
224
+ if "Possible Reflected XSS. Parameter: [search] Context: [Between Tags" in e.data["description"]:
225
+ xss_finding_emitted = True
226
+
227
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
228
+ assert xss_finding_emitted, "Between Tags XSS FINDING not emitted"
229
+
230
+
231
+ # Form Action Injection Detection
232
+ class Test_Lightfuzz_xss_formaction(Test_Lightfuzz_xss):
233
+ def request_handler(self, request):
234
+ form_data = request.form
235
+ value = form_data.get("func", None)
236
+
237
+ parameter_block = """
238
+ <section class=search>
239
+ <form action="/" method=POST>
240
+ <input type=text placeholder='Search the blog...' name=search>
241
+ <input type=text name=func value="/">
242
+ <button type=submit class=button>Search</button>
243
+ </form>
244
+ </section>
245
+ """
246
+
247
+ if value:
248
+ xss_block = f"""
249
+ <section class=search>
250
+ <form action="{value}" method=POST>
251
+ <input type=text placeholder='Search the blog...' name=search>
252
+ <input type=text name=func value="{value}">
253
+ <button type=submit class=button>Search</button>
254
+ </form>
255
+ </section>
256
+ """
257
+
258
+ return Response(xss_block, status=200)
259
+
260
+ return Response(parameter_block, status=200)
261
+
262
+ def check(self, module_test, events):
263
+ web_parameter_emitted = False
264
+ xss_finding_emitted = False
265
+ for e in events:
266
+ if e.type == "WEB_PARAMETER":
267
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
268
+ web_parameter_emitted = True
269
+
270
+ if e.type == "FINDING":
271
+ if (
272
+ "Possible Reflected XSS. Parameter: [func] Context: [Form Action Injection] Parameter Type: [POSTPARAM]"
273
+ in e.data["description"]
274
+ ):
275
+ xss_finding_emitted = True
276
+
277
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
278
+ assert xss_finding_emitted, "Form Action XSS FINDING not emitted"
279
+
280
+
281
+ # Base64 Envelope XSS Detection
282
+ class Test_Lightfuzz_envelope_base64(Test_Lightfuzz_xss):
283
+ def request_handler(self, request):
284
+ qs = str(request.query_string.decode())
285
+
286
+ parameter_block = """
287
+ <section class=search>
288
+ <form action=/ method=GET>
289
+ <input type=text value='dGV4dA==' name=search>
290
+ <button type=submit class=button>Search</button>
291
+ </form>
292
+ </section>
293
+ """
294
+ if "search=" in qs:
295
+ value = qs.split("search=")[1]
296
+ if "&" in value:
297
+ value = value.split("&")[0]
298
+
299
+ xss_block = f"""
300
+ <section class=blog-header>
301
+ <h1>0 search results for '{unquote(base64.b64decode(unquote(value)))}'</h1>
302
+ <hr>
303
+ </section>
304
+ """
305
+ return Response(xss_block, status=200)
306
+ return Response(parameter_block, status=200)
307
+
308
+ def check(self, module_test, events):
309
+ web_parameter_emitted = False
310
+ xss_finding_emitted = False
311
+ for e in events:
312
+ if e.type == "WEB_PARAMETER":
313
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
314
+ web_parameter_emitted = True
315
+
316
+ if e.type == "FINDING":
317
+ if (
318
+ "Possible Reflected XSS. Parameter: [search] Context: [Between Tags (z tag)"
319
+ in e.data["description"]
320
+ ):
321
+ xss_finding_emitted = True
322
+
323
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
324
+ assert xss_finding_emitted, "Between Tags XSS FINDING not emitted"
325
+
326
+
327
+ # Hex Envelope XSS Detection
328
+ class Test_Lightfuzz_envelope_hex(Test_Lightfuzz_envelope_base64):
329
+ def request_handler(self, request):
330
+ qs = str(request.query_string.decode())
331
+
332
+ parameter_block = """
333
+ <section class=search>
334
+ <form action=/ method=GET>
335
+ <input type=text value='7b22736561726368223a202264656d6f6b6579776f7264227d' name=search>
336
+ <button type=submit class=button>Search</button>
337
+ </form>
338
+ </section>
339
+ """
340
+
341
+ if "search=" in qs:
342
+ value = qs.split("search=")[1]
343
+ if "&" in value:
344
+ value = value.split("&")[0]
345
+
346
+ try:
347
+ # Decode the hex value
348
+ decoded_value = bytes.fromhex(unquote(value)).decode()
349
+
350
+ # Parse the decoded value as JSON
351
+ json_data = json.loads(decoded_value)
352
+
353
+ # Extract the desired parameter from the JSON (e.g., 'search')
354
+ if "search" in json_data:
355
+ extracted_value = json_data["search"]
356
+ else:
357
+ extracted_value = "[Parameter not found in JSON]"
358
+
359
+ except (json.JSONDecodeError, ValueError):
360
+ extracted_value = "[Invalid hex or JSON format]"
361
+
362
+ xss_block = f"""
363
+ <section class=blog-header>
364
+ <h1>0 search results for '{extracted_value}'</h1>
365
+ <hr>
366
+ </section>
367
+ """
368
+ return Response(xss_block, status=200)
369
+ return Response(parameter_block, status=200)
370
+
371
+
372
+ # Base64 (JSON) Envelope XSS Detection
373
+ class Test_Lightfuzz_envelope_jsonb64(Test_Lightfuzz_envelope_base64):
374
+ def request_handler(self, request):
375
+ qs = str(request.query_string.decode())
376
+
377
+ parameter_block = """
378
+ <section class=search>
379
+ <form action=/ method=GET>
380
+ <input type=text value='eyJzZWFyY2giOiAiZGVtb2tleXdvcmQifQ==' name=search>
381
+ <button type=submit class=button>Search</button>
382
+ </form>
383
+ </section>
384
+ """
385
+
386
+ if "search=" in qs:
387
+ value = qs.split("search=")[1]
388
+ if "&" in value:
389
+ value = value.split("&")[0]
390
+
391
+ try:
392
+ # Base64 decode the value
393
+ decoded_value = base64.b64decode(unquote(value)).decode()
394
+
395
+ # Parse the decoded value as JSON
396
+ json_data = json.loads(decoded_value)
397
+
398
+ # Extract the desired parameter from the JSON (e.g., 'search')
399
+ if "search" in json_data:
400
+ extracted_value = json_data["search"]
401
+ else:
402
+ extracted_value = "[Parameter not found in JSON]"
403
+
404
+ except (json.JSONDecodeError, base64.binascii.Error):
405
+ extracted_value = "[Invalid base64 or JSON format]"
406
+
407
+ xss_block = f"""
408
+ <section class=blog-header>
409
+ <h1>0 search results for '{extracted_value}'</h1>
410
+ <hr>
411
+ </section>
412
+ """
413
+ return Response(xss_block, status=200)
414
+
415
+ return Response(parameter_block, status=200)
416
+
417
+
418
+ # Base64 (JSON) Multiple Envelope Detection
419
+ class Test_Lightfuzz_envelope_multiple_json(Test_Lightfuzz_envelope_base64):
420
+ def request_handler(self, request):
421
+ parameter_block = """
422
+ <section class=search>
423
+ <form action=/ method=GET>
424
+ <input type=text value='%65%79%4a%7a%64%48%4a%70%62%6d%63%78%49%6a%6f%69%64%6d%46%73%64%57%55%78%49%69%77%69%63%33%52%79%61%57%35%6e%4d%69%49%36%49%6e%5a%68%62%48%56%6c%4d%69%4a%39' name=search>
425
+ <button type=submit class=button>Search</button>
426
+ </form>
427
+ </section>
428
+ """
429
+ return Response(parameter_block, status=200)
430
+
431
+ def check(self, module_test, events):
432
+ web_parameter_emitted = False
433
+ web_parameter_clone_emitted = False
434
+
435
+ for e in events:
436
+ if e.type == "WEB_PARAMETER":
437
+ for subparam in e.envelopes.get_subparams():
438
+ if len(subparam[0]) > 0:
439
+ if subparam[0][0] == "string1" and subparam[1] == "value1":
440
+ web_parameter_emitted = True
441
+ if subparam[0][0] == "string2" and subparam[1] == "value2":
442
+ web_parameter_clone_emitted = True
443
+
444
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
445
+ assert web_parameter_clone_emitted, "WEB_PARAMETER clone was not emitted"
446
+
447
+
448
+ # Base64 (XML) Envelope XSS Detection
449
+ class Test_Lightfuzz_envelope_xmlb64(Test_Lightfuzz_envelope_base64):
450
+ def request_handler(self, request):
451
+ qs = str(request.query_string.decode())
452
+
453
+ parameter_block = """
454
+ <section class=search>
455
+ <form action=/ method=GET>
456
+ <input type=text value='PGZpbmQ+PHNlYXJjaD5kZW1va2V5d29yZDwvc2VhcmNoPjwvZmluZD4=' name=search>
457
+ <button type=submit class=button>Search</button>
458
+ </form>
459
+ </section>
460
+ """
461
+
462
+ if "search=" in qs:
463
+ value = qs.split("search=")[1]
464
+ if "&" in value:
465
+ value = value.split("&")[0]
466
+
467
+ try:
468
+ # Base64 decode the value
469
+ decoded_value = base64.b64decode(unquote(value)).decode()
470
+
471
+ # Parse the decoded value as XML
472
+ root = ET.fromstring(decoded_value)
473
+
474
+ # Extract the desired parameter from the XML (e.g., 'search')
475
+ search_element = root.find(".//search")
476
+ if search_element is not None:
477
+ extracted_value = search_element.text
478
+ else:
479
+ extracted_value = "[Parameter not found in XML]"
480
+
481
+ except (ET.ParseError, base64.binascii.Error):
482
+ extracted_value = "[Invalid base64 or XML format]"
483
+
484
+ xss_block = f"""
485
+ <section class=blog-header>
486
+ <h1>0 search results for '{extracted_value}'</h1>
487
+ <hr>
488
+ </section>
489
+ """
490
+ return Response(xss_block, status=200)
491
+
492
+ return Response(parameter_block, status=200)
493
+
494
+
495
+ # In Tag Attribute XSS Detection
496
+ class Test_Lightfuzz_xss_intag(Test_Lightfuzz_xss):
497
+ def request_handler(self, request):
498
+ qs = str(request.query_string.decode())
499
+
500
+ parameter_block = """
501
+ <html>
502
+ <a href="/otherpage.php?foo=bar">Link</a>
503
+ </html>
504
+ """
505
+ if "foo=" in qs:
506
+ value = qs.split("=")[1]
507
+ if "&" in value:
508
+ value = value.split("&")[0]
509
+
510
+ xss_block = f"""
511
+ <section class=blog-header>
512
+ <div something="{unquote(value)}">stuff</div>
513
+ <hr>
514
+ </section>
515
+ """
516
+ return Response(xss_block, status=200)
517
+ return Response(parameter_block, status=200)
518
+
519
+ async def setup_after_prep(self, module_test):
520
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
521
+ expect_args = re.compile("/")
522
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
523
+ expect_args = re.compile("/otherpage.php")
524
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
525
+
526
+ def check(self, module_test, events):
527
+ web_parameter_emitted = False
528
+ original_value_captured = False
529
+ xss_finding_emitted = False
530
+ for e in events:
531
+ if e.type == "WEB_PARAMETER":
532
+ if "HTTP Extracted Parameter [foo]" in e.data["description"]:
533
+ web_parameter_emitted = True
534
+ if e.data["original_value"] == "bar":
535
+ original_value_captured = True
536
+
537
+ if e.type == "FINDING":
538
+ if "Possible Reflected XSS. Parameter: [foo] Context: [Tag Attribute]" in e.data["description"]:
539
+ xss_finding_emitted = True
540
+
541
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
542
+ assert original_value_captured, "original_value not captured"
543
+ assert xss_finding_emitted, "Between Tags XSS FINDING not emitted"
544
+
545
+
546
+ # In Javascript XSS Detection
547
+ class Test_Lightfuzz_xss_injs(Test_Lightfuzz_xss):
548
+ parameter_block = """
549
+ <html>
550
+ <a href="/otherpage.php?language=en">Link</a>
551
+ </html>
552
+ """
553
+
554
+ def request_handler(self, request):
555
+ qs = str(request.query_string.decode())
556
+ if "language=" in qs:
557
+ value = qs.split("=")[1]
558
+
559
+ if "&" in value:
560
+ value = value.split("&")[0]
561
+
562
+ xss_block = f"""
563
+ <html>
564
+ <head>
565
+ <script>
566
+ var lang = '{unquote(value)}';
567
+ console.log(lang);
568
+ </script>
569
+ </head>
570
+ <body>
571
+ <p>test</p>
572
+ </body>
573
+ </html>
574
+ """
575
+ return Response(xss_block, status=200)
576
+ return Response(self.parameter_block, status=200)
577
+
578
+ async def setup_after_prep(self, module_test):
579
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
580
+ expect_args = re.compile("/")
581
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
582
+ expect_args = re.compile("/otherpage.php")
583
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
584
+
585
+ def check(self, module_test, events):
586
+ web_parameter_emitted = False
587
+ original_value_captured = False
588
+ xss_finding_emitted = False
589
+ for e in events:
590
+ if e.type == "WEB_PARAMETER":
591
+ if "HTTP Extracted Parameter [language]" in e.data["description"]:
592
+ web_parameter_emitted = True
593
+ if e.data["original_value"] == "en":
594
+ original_value_captured = True
595
+
596
+ if e.type == "FINDING":
597
+ if "Possible Reflected XSS. Parameter: [language] Context: [In Javascript]" in e.data["description"]:
598
+ xss_finding_emitted = True
599
+
600
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
601
+ assert original_value_captured, "original_value not captured"
602
+ assert xss_finding_emitted, "In Javascript XSS FINDING not emitted"
603
+
604
+
605
+ # XSS Parameter Needing URL-Encoding
606
+ class Test_Lightfuzz_urlencoding(Test_Lightfuzz_xss_injs):
607
+ config_overrides = {
608
+ "interactsh_disable": True,
609
+ "modules": {
610
+ "lightfuzz": {
611
+ "enabled_submodules": ["cmdi", "crypto", "path", "serial", "sqli", "ssti", "xss"],
612
+ }
613
+ },
614
+ }
615
+
616
+ parameter_block = """
617
+ <html>
618
+ <a href="/otherpage.php?language=parameter with spaces">Link</a>
619
+ </html>
620
+ """
621
+
622
+ def check(self, module_test, events):
623
+ web_parameter_emitted = False
624
+ original_value_captured = False
625
+ xss_finding_emitted = False
626
+ for e in events:
627
+ if e.type == "WEB_PARAMETER":
628
+ if "HTTP Extracted Parameter [language]" in e.data["description"]:
629
+ web_parameter_emitted = True
630
+ if e.data["original_value"] is not None and e.data["original_value"] == "parameter with spaces":
631
+ original_value_captured = True
632
+
633
+ if e.type == "FINDING":
634
+ if "Possible Reflected XSS. Parameter: [language] Context: [In Javascript]" in e.data["description"]:
635
+ xss_finding_emitted = True
636
+
637
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
638
+ assert original_value_captured, "original_value not captured"
639
+ assert xss_finding_emitted, "In Javascript XSS FINDING not emitted"
640
+
641
+
642
+ class Test_Lightfuzz_nosqli_quoteescape(ModuleTestBase):
643
+ targets = ["http://127.0.0.1:8888"]
644
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
645
+ config_overrides = {
646
+ "interactsh_disable": True,
647
+ "modules": {
648
+ "lightfuzz": {
649
+ "enabled_submodules": ["nosqli"],
650
+ }
651
+ },
652
+ }
653
+
654
+ def request_handler(self, request):
655
+ normal_block = """
656
+ <section class="search-filters">
657
+ <label>Refine your search:</label>
658
+ <a class="filter-category" href="/?category=Pets">Pets</a>
659
+ </section>
660
+ """
661
+
662
+ qs = str(request.query_string.decode())
663
+ if "category=" in qs:
664
+ value = qs.split("=")[1]
665
+ if "&" in value:
666
+ value = value.split("&")[0]
667
+ if value == "Pets%27":
668
+ return Response("JSON ERROR!", status=500)
669
+ elif value == "Pets%5C%27":
670
+ return Response("No results", status=200)
671
+ elif value == "Pets%27%20%26%26%200%20%26%26%20%27x":
672
+ return Response("No results", status=200)
673
+ elif value == "Pets%27%20%26%26%201%20%26%26%20%27x":
674
+ return Response('{"category":"Pets","entries":["dog","cat","bird"]}', status=200)
675
+ else:
676
+ return Response("No results", status=200)
677
+ return Response(normal_block, status=200)
678
+
679
+ async def setup_after_prep(self, module_test):
680
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
681
+ expect_args = re.compile("/")
682
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
683
+
684
+ def check(self, module_test, events):
685
+ nosqli_finding_emitted = False
686
+ finding_count = 0
687
+ for e in events:
688
+ if e.type == "FINDING":
689
+ finding_count += 1
690
+ if (
691
+ "Possible NoSQL Injection. Parameter: [category] Parameter Type: [GETPARAM] Original Value: [Pets] Detection Method: [Quote/Escaped Quote + Conditional Affect]"
692
+ in e.data["description"]
693
+ ):
694
+ nosqli_finding_emitted = True
695
+ assert nosqli_finding_emitted, "NoSQLi FINDING not emitted"
696
+ assert finding_count == 1, "Unexpected FINDING events reported"
697
+
698
+
699
+ class Test_Lightfuzz_nosqli_negation(Test_Lightfuzz_nosqli_quoteescape):
700
+ def request_handler(self, request):
701
+ form_block = """
702
+ <form method="POST" action="">
703
+ <label for="username">Username:</label>
704
+ <input type="text" id="username" name="username" required>
705
+ <br>
706
+ <label for="password">Password:</label>
707
+ <input type="password" id="password" name="password" required>
708
+ <br>
709
+ <button type="submit">Login</button>
710
+ </form>
711
+ """
712
+ if request.method == "GET":
713
+ return Response(form_block, status=200)
714
+
715
+ if "username[$ne]" in request.form.keys() and "password[$ne]" in request.form.keys():
716
+ return Response("Welcome, testuser1!", status=200)
717
+ if "username[$eq]" in request.form.keys() and "password[$eq]" in request.form.keys():
718
+ return Response("Invalid Username or Password!", status=200)
719
+ else:
720
+ return Response("Invalid Username or Password!", status=200)
721
+
722
+ def check(self, module_test, events):
723
+ nosqli_finding_emitted = False
724
+ finding_count = 0
725
+ for e in events:
726
+ if e.type == "FINDING":
727
+ finding_count += 1
728
+ if (
729
+ "Possible NoSQL Injection. Parameter: [password] Parameter Type: [POSTPARAM] Detection Method: [Parameter Name Operator Injection - Negation ([$ne])] Differences: [body]"
730
+ in e.data["description"]
731
+ ):
732
+ nosqli_finding_emitted = True
733
+ assert nosqli_finding_emitted, "NoSQLi FINDING not emitted"
734
+ assert finding_count == 2, "Unexpected FINDING events reported"
735
+
736
+
737
+ class Test_Lightfuzz_nosqli_negation_falsepositive(Test_Lightfuzz_nosqli_quoteescape):
738
+ def request_handler(self, request):
739
+ form_block = """
740
+ <form method="POST" action="">
741
+ <label for="username">Username:</label>
742
+ <input type="text" id="username" name="username" required>
743
+ <br>
744
+ <label for="password">Password:</label>
745
+ <input type="password" id="password" name="password" required>
746
+ <br>
747
+ <button type="submit">Login</button>
748
+ </form>
749
+ """
750
+ if request.method == "GET":
751
+ return Response(form_block, status=200)
752
+
753
+ if "username[$ne]" in request.form.keys() and "password[$ne]" in request.form.keys():
754
+ return Response("missing username or password", status=500)
755
+ if "username[$eq]" in request.form.keys() and "password[$eq]" in request.form.keys():
756
+ return Response("missing username or password", status=500)
757
+ else:
758
+ return Response("Invalid Username or Password!", status=200)
759
+
760
+ def check(self, module_test, events):
761
+ finding_count = 0
762
+ for e in events:
763
+ if e.type == "FINDING":
764
+ finding_count += 1
765
+ assert finding_count == 0, "False positive FINDING emitted"
766
+
767
+
768
+ # SQLI Single Quote/Two Single Quote (getparam)
769
+ class Test_Lightfuzz_sqli(ModuleTestBase):
770
+ targets = ["http://127.0.0.1:8888"]
771
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
772
+ config_overrides = {
773
+ "interactsh_disable": True,
774
+ "modules": {
775
+ "lightfuzz": {
776
+ "enabled_submodules": ["sqli"],
777
+ }
778
+ },
779
+ }
780
+
781
+ def request_handler(self, request):
782
+ qs = str(request.query_string.decode())
783
+ parameter_block = """
784
+ <section class=search>
785
+ <form action=/ method=GET>
786
+ <input type=text placeholder='Search the blog...' name=search>
787
+ <button type=submit class=button>Search</button>
788
+ </form>
789
+ </section>
790
+ """
791
+ if "search=" in qs:
792
+ value = qs.split("=")[1]
793
+
794
+ if "&" in value:
795
+ value = value.split("&")[0]
796
+
797
+ sql_block_normal = f"""
798
+ <section class=blog-header>
799
+ <h1>0 search results for '{unquote(value)}'</h1>
800
+ <hr>
801
+ </section>
802
+ """
803
+
804
+ sql_block_error = """
805
+ <section class=error>
806
+ <h1>Found error in SQL query</h1>
807
+ <hr>
808
+ </section>
809
+ """
810
+ if value.endswith("'"):
811
+ if value.endswith("''"):
812
+ return Response(sql_block_normal, status=200)
813
+ return Response(sql_block_error, status=500)
814
+ return Response(parameter_block, status=200)
815
+
816
+ async def setup_after_prep(self, module_test):
817
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
818
+ expect_args = re.compile("/")
819
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
820
+
821
+ def check(self, module_test, events):
822
+ web_parameter_emitted = False
823
+ sqli_finding_emitted = False
824
+ for e in events:
825
+ if e.type == "WEB_PARAMETER":
826
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
827
+ web_parameter_emitted = True
828
+ if e.type == "FINDING":
829
+ if (
830
+ "Possible SQL Injection. Parameter: [search] Parameter Type: [GETPARAM] Detection Method: [Single Quote/Two Single Quote, Code Change (200->500->200)]"
831
+ in e.data["description"]
832
+ ):
833
+ sqli_finding_emitted = True
834
+
835
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
836
+ assert sqli_finding_emitted, "SQLi Single/Double Quote getparam FINDING not emitted"
837
+
838
+
839
+ # SQLI Single Quote/Two Single Quote (postparam)
840
+ class Test_Lightfuzz_sqli_post(ModuleTestBase):
841
+ targets = ["http://127.0.0.1:8888"]
842
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
843
+ config_overrides = {
844
+ "interactsh_disable": True,
845
+ "modules": {
846
+ "lightfuzz": {
847
+ "enabled_submodules": ["sqli"],
848
+ }
849
+ },
850
+ }
851
+
852
+ def request_handler(self, request):
853
+ parameter_block = """
854
+ <section class=search>
855
+ <form action=/ method=POST>
856
+ <input type=text placeholder='Search the blog...' name=search>
857
+ <button type=submit class=button>Search</button>
858
+ </form>
859
+ </section>
860
+ """
861
+
862
+ if "search" in request.form.keys():
863
+ value = request.form["search"]
864
+
865
+ sql_block_normal = f"""
866
+ <section class=blog-header>
867
+ <h1>0 search results for '{unquote(value)}'</h1>
868
+ <hr>
869
+ </section>
870
+ """
871
+
872
+ sql_block_error = """
873
+ <section class=error>
874
+ <h1>Found error in SQL query</h1>
875
+ <hr>
876
+ </section>
877
+ """
878
+ if value.endswith("'"):
879
+ if value.endswith("''"):
880
+ return Response(sql_block_normal, status=200)
881
+ return Response(sql_block_error, status=500)
882
+ return Response(parameter_block, status=200)
883
+
884
+ async def setup_after_prep(self, module_test):
885
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
886
+ expect_args = re.compile("/")
887
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
888
+
889
+ def check(self, module_test, events):
890
+ web_parameter_emitted = False
891
+ sqli_finding_emitted = False
892
+ for e in events:
893
+ if e.type == "WEB_PARAMETER":
894
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
895
+ web_parameter_emitted = True
896
+
897
+ if e.type == "FINDING":
898
+ if (
899
+ "Possible SQL Injection. Parameter: [search] Parameter Type: [POSTPARAM] Detection Method: [Single Quote/Two Single Quote, Code Change (200->500->200)]"
900
+ in e.data["description"]
901
+ ):
902
+ sqli_finding_emitted = True
903
+
904
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
905
+ assert sqli_finding_emitted, "SQLi Single/Double Quote postparam FINDING not emitted"
906
+
907
+
908
+ # disable_post test
909
+ class Test_Lightfuzz_disable_post(Test_Lightfuzz_sqli_post):
910
+ config_overrides = {
911
+ "interactsh_disable": True,
912
+ "modules": {
913
+ "lightfuzz": {
914
+ "enabled_submodules": ["sqli"],
915
+ "disable_post": True,
916
+ }
917
+ },
918
+ }
919
+
920
+ def check(self, module_test, events):
921
+ web_parameter_emitted = False
922
+ sqli_finding_emitted = False
923
+ for e in events:
924
+ if e.type == "WEB_PARAMETER":
925
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
926
+ web_parameter_emitted = True
927
+
928
+ if e.type == "FINDING":
929
+ if (
930
+ "Possible SQL Injection. Parameter: [search] Parameter Type: [POSTPARAM] Detection Method: [Single Quote/Two Single Quote, Code Change (200->500->200)]"
931
+ in e.data["description"]
932
+ ):
933
+ sqli_finding_emitted = True
934
+
935
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
936
+ assert not sqli_finding_emitted, "post-based SQLI emitted despite post-parameters being disabled"
937
+
938
+
939
+ # SQLI Single Quote/Two Single Quote (headers)
940
+ class Test_Lightfuzz_sqli_headers(Test_Lightfuzz_sqli):
941
+ async def setup_after_prep(self, module_test):
942
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
943
+ expect_args = re.compile("/")
944
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
945
+
946
+ seed_events = []
947
+ parent_event = module_test.scan.make_event(
948
+ "http://127.0.0.1:8888/",
949
+ "URL",
950
+ module_test.scan.root_event,
951
+ module="httpx",
952
+ tags=["status-200", "distance-0"],
953
+ )
954
+
955
+ data = {
956
+ "host": "127.0.0.1",
957
+ "type": "HEADER",
958
+ "name": "testheader",
959
+ "original_value": None,
960
+ "url": "http://127.0.0.1:8888",
961
+ "description": "Test Dummy Header",
962
+ }
963
+ seed_event = module_test.scan.make_event(data, "WEB_PARAMETER", parent_event, tags=["distance-0"])
964
+ seed_events.append(seed_event)
965
+ for event in seed_events:
966
+ await module_test.scan.ingress_module.incoming_event_queue.put(event)
967
+
968
+ def request_handler(self, request):
969
+ placeholder_block = """
970
+ <html>
971
+ <p>placeholder</p>
972
+ </html>
973
+ """
974
+
975
+ if request.headers.get("testheader") is not None:
976
+ header_value = request.headers.get("testheader")
977
+
978
+ header_block_normal = f"""
979
+ <html>
980
+ <p>placeholder</p>
981
+ <p>test: {header_value}</p>
982
+ </html>
983
+ """
984
+ header_block_error = """
985
+ <html>
986
+ <p>placeholder</p>
987
+ <p>Error!</p>
988
+ </html>
989
+ """
990
+ if header_value.endswith("'") and not header_value.endswith("''"):
991
+ return Response(header_block_error, status=500)
992
+ return Response(header_block_normal, status=200)
993
+ return Response(placeholder_block, status=200)
994
+
995
+ def check(self, module_test, events):
996
+ sqli_finding_emitted = False
997
+ for e in events:
998
+ if e.type == "FINDING":
999
+ if (
1000
+ "Possible SQL Injection. Parameter: [testheader] Parameter Type: [HEADER] Detection Method: [Single Quote/Two Single Quote, Code Change (200->500->200)]"
1001
+ in e.data["description"]
1002
+ ):
1003
+ sqli_finding_emitted = True
1004
+ assert sqli_finding_emitted, "SQLi Single/Double Quote headers FINDING not emitted"
1005
+
1006
+
1007
+ # SQLI Single Quote/Two Single Quote (cookies)
1008
+ class Test_Lightfuzz_sqli_cookies(Test_Lightfuzz_sqli):
1009
+ async def setup_after_prep(self, module_test):
1010
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1011
+ expect_args = re.compile("/")
1012
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1013
+
1014
+ seed_events = []
1015
+ parent_event = module_test.scan.make_event(
1016
+ "http://127.0.0.1:8888/",
1017
+ "URL",
1018
+ module_test.scan.root_event,
1019
+ module="httpx",
1020
+ tags=["status-200", "distance-0"],
1021
+ )
1022
+
1023
+ data = {
1024
+ "host": "127.0.0.1",
1025
+ "type": "COOKIE",
1026
+ "name": "test",
1027
+ "original_value": None,
1028
+ "url": "http://127.0.0.1:8888",
1029
+ "description": "Test Dummy Cookie",
1030
+ }
1031
+ seed_event = module_test.scan.make_event(data, "WEB_PARAMETER", parent_event, tags=["distance-0"])
1032
+ seed_events.append(seed_event)
1033
+ for event in seed_events:
1034
+ await module_test.scan.ingress_module.incoming_event_queue.put(event)
1035
+
1036
+ def request_handler(self, request):
1037
+ placeholder_block = """
1038
+ <html>
1039
+ <p>placeholder</p>
1040
+ </html>
1041
+ """
1042
+
1043
+ print("@@@@@???")
1044
+ print(request.cookies)
1045
+ if request.cookies.get("test") is not None:
1046
+ header_value = request.cookies.get("test")
1047
+
1048
+ header_block_normal = f"""
1049
+ <html>
1050
+ <p>placeholder</p>
1051
+ <p>test: {header_value}</p>
1052
+ </html>
1053
+ """
1054
+
1055
+ header_block_error = """
1056
+ <html>
1057
+ <p>placeholder</p>
1058
+ <p>Error!</p>
1059
+ </html>
1060
+ """
1061
+ if header_value.endswith("'") and not header_value.endswith("''"):
1062
+ return Response(header_block_error, status=500)
1063
+ return Response(header_block_normal, status=200)
1064
+ return Response(placeholder_block, status=200)
1065
+
1066
+ def check(self, module_test, events):
1067
+ sqli_finding_emitted = False
1068
+ for e in events:
1069
+ if e.type == "FINDING":
1070
+ if (
1071
+ "Possible SQL Injection. Parameter: [test] Parameter Type: [COOKIE] Detection Method: [Single Quote/Two Single Quote, Code Change (200->500->200)]"
1072
+ in e.data["description"]
1073
+ ):
1074
+ sqli_finding_emitted = True
1075
+ assert sqli_finding_emitted, "SQLi Single/Double Quote cookies FINDING not emitted"
1076
+
1077
+
1078
+ # SQLi Delay Probe
1079
+ class Test_Lightfuzz_sqli_delay(Test_Lightfuzz_sqli):
1080
+ def request_handler(self, request):
1081
+ from time import sleep
1082
+
1083
+ qs = str(request.query_string.decode())
1084
+
1085
+ parameter_block = """
1086
+ <section class=search>
1087
+ <form action=/ method=GET>
1088
+ <input type=text placeholder='Search the blog...' name=search>
1089
+ <button type=submit class=button>Search</button>
1090
+ </form>
1091
+ </section>
1092
+
1093
+ """
1094
+ if "search=" in qs:
1095
+ value = qs.split("=")[1]
1096
+
1097
+ if "&" in value:
1098
+ value = value.split("&")[0]
1099
+
1100
+ sql_block = """
1101
+ <section class=blog-header>
1102
+ <h1>0 search results found</h1>
1103
+ <hr>
1104
+ </section>
1105
+ """
1106
+ if "' AND (SLEEP(5)) AND '" in unquote(value):
1107
+ sleep(5)
1108
+ return Response(sql_block, status=200)
1109
+ return Response(parameter_block, status=200)
1110
+
1111
+ def check(self, module_test, events):
1112
+ web_parameter_emitted = False
1113
+ sqldelay_finding_emitted = False
1114
+ for e in events:
1115
+ if e.type == "WEB_PARAMETER":
1116
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
1117
+ web_parameter_emitted = True
1118
+
1119
+ if e.type == "FINDING":
1120
+ if (
1121
+ "Possible Blind SQL Injection. Parameter: [search] Parameter Type: [GETPARAM] Detection Method: [Delay Probe (1' AND (SLEEP(5)) AND ')]"
1122
+ in e.data["description"]
1123
+ ):
1124
+ sqldelay_finding_emitted = True
1125
+
1126
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
1127
+ assert sqldelay_finding_emitted, "SQLi Delay FINDING not emitted"
1128
+
1129
+
1130
+ # Serialization Module (Error Resolution)
1131
+ class Test_Lightfuzz_serial_errorresolution(ModuleTestBase):
1132
+ targets = ["http://127.0.0.1:8888"]
1133
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
1134
+ config_overrides = {
1135
+ "interactsh_disable": True,
1136
+ "modules": {
1137
+ "lightfuzz": {
1138
+ "enabled_submodules": ["serial"],
1139
+ }
1140
+ },
1141
+ }
1142
+
1143
+ dotnet_serial_error = """
1144
+ <html>
1145
+ <b> Description: </b>An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
1146
+
1147
+ <br><br>
1148
+
1149
+ <b> Exception Details: </b>System.Runtime.Serialization.SerializationException: End of Stream encountered before parsing was completed.<br><br>
1150
+ </html>
1151
+ """
1152
+
1153
+ dotnet_serial_html = """
1154
+ <!DOCTYPE html>
1155
+ <html>
1156
+ <head><title>
1157
+ Deserialization RCE Example
1158
+ </title></head>
1159
+ <body>
1160
+ <form method="post" action="./deser.aspx" id="form1">
1161
+ <div class="aspNetHidden">
1162
+ <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP" />
1163
+ </div>
1164
+
1165
+ <div class="aspNetHidden">
1166
+
1167
+ <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="AD6F025C" />
1168
+ <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAANdCjkiIFhjCB8ta8aO/EhuESCFkFW/RuhzY1oLb/NUVM34O/GfAV4V4n0wgFZHr3czZjft8VgObR/WUivai7w4kfR1wg==" />
1169
+ </div>
1170
+ <div>
1171
+ <h2>Deserialization Test</h2>
1172
+ <span id="Label1">Enter serialized data:</span><br />
1173
+ <textarea name="TextBox1" rows="2" cols="20" id="TextBox1" style="height:100px;width:400px;">
1174
+ </textarea><br /><br />
1175
+ <input type="submit" name="Button1" value="Submit" id="Button1" /><br /><br />
1176
+ </div>
1177
+ </form>
1178
+
1179
+
1180
+ </body>
1181
+ </html>
1182
+ """
1183
+
1184
+ async def setup_after_prep(self, module_test):
1185
+ expect_args = re.compile("/")
1186
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1187
+
1188
+ def request_handler(self, request):
1189
+ dotnet_serial_error_resolved = (
1190
+ "<html><body>Deserialization successful! Object type: System.String</body></html>"
1191
+ )
1192
+ post_params = request.form
1193
+
1194
+ if "TextBox1" not in post_params.keys():
1195
+ return Response(self.dotnet_serial_html, status=200)
1196
+
1197
+ else:
1198
+ if post_params["__VIEWSTATE"] != "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP":
1199
+ return Response(self.dotnet_serial_error, status=500)
1200
+ if post_params["TextBox1"] == "AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==":
1201
+ return Response(dotnet_serial_error_resolved, status=200)
1202
+ else:
1203
+ return Response(self.dotnet_serial_error, status=500)
1204
+
1205
+ def check(self, module_test, events):
1206
+ excavate_extracted_form_parameter = False
1207
+ excavate_extracted_form_parameter_details = False
1208
+ lightfuzz_serial_detect_errorresolution = False
1209
+
1210
+ for e in events:
1211
+ if e.type == "WEB_PARAMETER":
1212
+ if e.data["name"] == "TextBox1":
1213
+ excavate_extracted_form_parameter = True
1214
+ if (
1215
+ e.data["url"] == "http://127.0.0.1:8888/deser.aspx"
1216
+ and e.data["host"] == "127.0.0.1"
1217
+ and e.data["additional_params"]
1218
+ == {
1219
+ "__VIEWSTATE": "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP",
1220
+ "__VIEWSTATEGENERATOR": "AD6F025C",
1221
+ "__EVENTVALIDATION": "/wEdAANdCjkiIFhjCB8ta8aO/EhuESCFkFW/RuhzY1oLb/NUVM34O/GfAV4V4n0wgFZHr3czZjft8VgObR/WUivai7w4kfR1wg==",
1222
+ "Button1": "Submit",
1223
+ }
1224
+ ):
1225
+ excavate_extracted_form_parameter_details = True
1226
+ if e.type == "FINDING":
1227
+ if (
1228
+ e.data["description"]
1229
+ == "POSSIBLE Unsafe Deserialization. Parameter: [TextBox1] Parameter Type: [POSTPARAM] Technique: [Error Resolution] Serialization Payload: [dotnet_base64]"
1230
+ ):
1231
+ lightfuzz_serial_detect_errorresolution = True
1232
+
1233
+ assert excavate_extracted_form_parameter, "WEB_PARAMETER for POST form was not emitted"
1234
+ assert excavate_extracted_form_parameter_details, "WEB_PARAMETER for POST form did not have correct data"
1235
+ assert lightfuzz_serial_detect_errorresolution, (
1236
+ "Lightfuzz Serial module failed to detect ASP.NET error resolution based deserialization"
1237
+ )
1238
+
1239
+
1240
+ # Serialization Module (Error Resolution False Positive)
1241
+ class Test_Lightfuzz_serial_errorresolution_falsepositive(Test_Lightfuzz_serial_errorresolution):
1242
+ def request_handler(self, request):
1243
+ dotnet_serial_error_resolved_with_general_error = (
1244
+ "<html><body>Internal Server Error (invalid characters!)</body></html>"
1245
+ )
1246
+ post_params = request.form
1247
+
1248
+ if "TextBox1" not in post_params.keys():
1249
+ return Response(self.dotnet_serial_html, status=200)
1250
+
1251
+ else:
1252
+ if post_params["__VIEWSTATE"] != "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP":
1253
+ return Response(self.dotnet_serial_error, status=500)
1254
+ if post_params["TextBox1"] == "AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==":
1255
+ return Response(dotnet_serial_error_resolved_with_general_error, status=200)
1256
+ else:
1257
+ return Response(self.dotnet_serial_error, status=500)
1258
+
1259
+ def check(self, module_test, events):
1260
+ no_finding_emitted = True
1261
+
1262
+ for e in events:
1263
+ if e.type == "FINDING":
1264
+ no_finding_emitted = False
1265
+
1266
+ assert no_finding_emitted, "False positive finding was emitted"
1267
+
1268
+
1269
+ class Test_Lightfuzz_serial_errorresolution_existingvalue_valid(Test_Lightfuzz_serial_errorresolution):
1270
+ dotnet_serial_html = """
1271
+ <!DOCTYPE html>
1272
+ <html>
1273
+ <head><title>
1274
+ Deserialization RCE Example
1275
+ </title></head>
1276
+ <body>
1277
+ <form method="post" action="./deser.aspx" id="form1">
1278
+ <div class="aspNetHidden">
1279
+ <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP" />
1280
+ </div>
1281
+
1282
+ <div class="aspNetHidden">
1283
+
1284
+ <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="AD6F025C" />
1285
+ <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAANdCjkiIFhjCB8ta8aO/EhuESCFkFW/RuhzY1oLb/NUVM34O/GfAV4V4n0wgFZHr3czZjft8VgObR/WUivai7w4kfR1wg==" />
1286
+ </div>
1287
+ <div>
1288
+ <h2>Deserialization Test</h2>
1289
+ <span id="Label1">Enter serialized data:</span><br />
1290
+ <textarea name="TextBox1" rows="2" cols="20" id="TextBox1" value="AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==" style="height:100px;width:400px;">
1291
+ </textarea><br /><br />
1292
+ <input type="submit" name="Button1" value="Submit" id="Button1" /><br /><br />
1293
+ </div>
1294
+ </form>
1295
+
1296
+
1297
+ </body>
1298
+ </html>
1299
+ """
1300
+
1301
+ def check(self, module_test, events):
1302
+ excavate_extracted_form_parameter = False
1303
+ excavate_extracted_form_parameter_details = False
1304
+ excavate_detect_serialization_value = False
1305
+ lightfuzz_serial_detect_errorresolution = False
1306
+
1307
+ for e in events:
1308
+ if e.type == "WEB_PARAMETER":
1309
+ if e.data["name"] == "TextBox1":
1310
+ excavate_extracted_form_parameter = True
1311
+ if (
1312
+ e.data["url"] == "http://127.0.0.1:8888/deser.aspx"
1313
+ and e.data["host"] == "127.0.0.1"
1314
+ and e.data["original_value"] == "AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw=="
1315
+ and e.data["additional_params"]
1316
+ == {
1317
+ "__VIEWSTATE": "/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP",
1318
+ "__VIEWSTATEGENERATOR": "AD6F025C",
1319
+ "__EVENTVALIDATION": "/wEdAANdCjkiIFhjCB8ta8aO/EhuESCFkFW/RuhzY1oLb/NUVM34O/GfAV4V4n0wgFZHr3czZjft8VgObR/WUivai7w4kfR1wg==",
1320
+ "Button1": "Submit",
1321
+ }
1322
+ ):
1323
+ excavate_extracted_form_parameter_details = True
1324
+ if e.type == "FINDING":
1325
+ if e.data["description"] == "HTTP response (body) contains a possible serialized object (DOTNET)":
1326
+ excavate_detect_serialization_value = True
1327
+ if (
1328
+ e.data["description"]
1329
+ == "POSSIBLE Unsafe Deserialization. Parameter: [TextBox1] Parameter Type: [POSTPARAM] Original Value: [AAEAAAD/////AQAAAAAAAAAGAQAAAAdndXN0YXZvCw==] Technique: [Error Resolution] Serialization Payload: [dotnet_base64]"
1330
+ ):
1331
+ lightfuzz_serial_detect_errorresolution = True
1332
+
1333
+ assert excavate_extracted_form_parameter, "WEB_PARAMETER for POST form was not emitted"
1334
+ assert excavate_extracted_form_parameter_details, "WEB_PARAMETER for POST form did not have correct data"
1335
+ assert excavate_detect_serialization_value, "WEB_PARAMETER for POST form did not have correct data"
1336
+ assert lightfuzz_serial_detect_errorresolution, (
1337
+ "Lightfuzz Serial module failed to detect ASP.NET error resolution based deserialization"
1338
+ )
1339
+
1340
+
1341
+ class Test_Lightfuzz_serial_errorresolution_existingvalue_invalid(Test_Lightfuzz_serial_errorresolution_falsepositive):
1342
+ dotnet_serial_html = """
1343
+ <!DOCTYPE html>
1344
+ <html>
1345
+ <head><title>
1346
+ Deserialization RCE Example
1347
+ </title></head>
1348
+ <body>
1349
+ <form method="post" action="./deser.aspx" id="form1">
1350
+ <div class="aspNetHidden">
1351
+ <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE5MTI4MzkxNjVkZNt7ICM+GixNryV6ucx+srzhXlwP" />
1352
+ </div>
1353
+
1354
+ <div class="aspNetHidden">
1355
+
1356
+ <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="AD6F025C" />
1357
+ <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAANdCjkiIFhjCB8ta8aO/EhuESCFkFW/RuhzY1oLb/NUVM34O/GfAV4V4n0wgFZHr3czZjft8VgObR/WUivai7w4kfR1wg==" />
1358
+ </div>
1359
+ <div>
1360
+ <h2>Deserialization Test</h2>
1361
+ <span id="Label1">Enter serialized data:</span><br />
1362
+ <textarea name="TextBox1" rows="2" cols="20" id="TextBox1" value="not_valid_base64!" style="height:100px;width:400px;">
1363
+ </textarea><br /><br />
1364
+ <input type="submit" name="Button1" value="Submit" id="Button1" /><br /><br />
1365
+ </div>
1366
+ </form>
1367
+
1368
+
1369
+ </body>
1370
+ </html>
1371
+ """
1372
+
1373
+
1374
+ # Serialization Module (Error Differential)
1375
+ class Test_Lightfuzz_serial_errordifferential(Test_Lightfuzz_serial_errorresolution):
1376
+ def request_handler(self, request):
1377
+ java_serial_error = """
1378
+ <html>
1379
+ <h4>Internal Server Error</h4>
1380
+ <p class=is-warning>java.io.StreamCorruptedException: invalid stream header: 0C400304</p>
1381
+ </html>
1382
+ """
1383
+
1384
+ java_serial_error_keyword = """
1385
+ <html>
1386
+ <h4>Internal Server Error</h4>
1387
+ <p class=is-warning>java.lang.ClassCastException: Cannot cast java.lang.String to lab.actions.common.serializable.AccessTokenUser</p>
1388
+ </html>
1389
+ """
1390
+
1391
+ java_serial_html = """
1392
+ <!DOCTYPE html>
1393
+ <html>
1394
+ <head><title>
1395
+ Deserialization RCE Example
1396
+ </title></head>
1397
+ <body>
1398
+ Please log in to continue.
1399
+ </body>
1400
+ </html>
1401
+ """
1402
+
1403
+ cookies = request.cookies
1404
+
1405
+ if "session" not in cookies.keys():
1406
+ response = Response(java_serial_html, status=200)
1407
+ response.set_cookie("session", value="", max_age=3600, httponly=True)
1408
+ return response
1409
+
1410
+ else:
1411
+ if unquote(cookies["session"]) == "rO0ABXQABHRlc3Q=":
1412
+ return Response(java_serial_error_keyword, status=500)
1413
+ else:
1414
+ return Response(java_serial_error, status=500)
1415
+
1416
+ def check(self, module_test, events):
1417
+ excavate_extracted_cookie_parameter = False
1418
+ lightfuzz_serial_detect_errordifferential = False
1419
+
1420
+ for e in events:
1421
+ if e.type == "WEB_PARAMETER":
1422
+ if e.data["description"] == "Set-Cookie Assigned Cookie [session]" and e.data["type"] == "COOKIE":
1423
+ excavate_extracted_cookie_parameter = True
1424
+
1425
+ if e.type == "FINDING":
1426
+ if (
1427
+ e.data["description"]
1428
+ == "POSSIBLE Unsafe Deserialization. Parameter: [session] Parameter Type: [COOKIE] Technique: [Differential Error Analysis] Error-String: [cannot cast java.lang.string] Payload: [java_base64_string_error]"
1429
+ ):
1430
+ lightfuzz_serial_detect_errordifferential = True
1431
+
1432
+ assert excavate_extracted_cookie_parameter, "WEB_PARAMETER for cookie was not emitted"
1433
+ assert lightfuzz_serial_detect_errordifferential, (
1434
+ "Lightfuzz Serial module failed to detect Java error differential based deserialization"
1435
+ )
1436
+
1437
+
1438
+ # Serialization Modules (Error Differential - False positive check)
1439
+ class Test_Lightfuzz_serial_errordifferential_falsepositive(Test_Lightfuzz_serial_errorresolution):
1440
+ def request_handler(self, request):
1441
+ post_params = request.form
1442
+ if "TextBox1" not in post_params.keys():
1443
+ return Response(self.dotnet_serial_html, status=200)
1444
+
1445
+ else:
1446
+ dotnet_serial_reflection = (
1447
+ f"<html><body><p>invalid user</p><p>reflected input: {post_params['TextBox1']}</body></html>"
1448
+ )
1449
+ return Response(dotnet_serial_reflection, status=500)
1450
+
1451
+ def check(self, module_test, events):
1452
+ finding_count = 0
1453
+ for e in events:
1454
+ if e.type == "FINDING":
1455
+ finding_count += 1
1456
+ assert finding_count == 0, "Unexpected FINDING events reported"
1457
+
1458
+
1459
+ # CMDi echo canary
1460
+ class Test_Lightfuzz_cmdi(ModuleTestBase):
1461
+ targets = ["http://127.0.0.1:8888"]
1462
+ modules_overrides = ["httpx", "lightfuzz", "excavate"]
1463
+ config_overrides = {
1464
+ "interactsh_disable": True,
1465
+ "modules": {
1466
+ "lightfuzz": {
1467
+ "enabled_submodules": ["cmdi"],
1468
+ }
1469
+ },
1470
+ }
1471
+
1472
+ def request_handler(self, request):
1473
+ qs = str(request.query_string.decode())
1474
+
1475
+ parameter_block = """
1476
+ <section class=search>
1477
+ <form action=/ method=GET>
1478
+ <input type=text placeholder='Search the blog...' name=search>
1479
+ <button type=submit class=button>Search</button>
1480
+ </form>
1481
+ </section>
1482
+ """
1483
+ if "search=" in qs:
1484
+ value = qs.split("=")[1]
1485
+ if "&" in value:
1486
+ value = value.split("&")[0]
1487
+ if "&& echo " in unquote(value):
1488
+ cmdi_value = unquote(value).split("&& echo ")[1].split(" ")[0]
1489
+ else:
1490
+ cmdi_value = value
1491
+ cmdi_block = f"""
1492
+ <section class=blog-header>
1493
+ <h1>0 search results for '{unquote(cmdi_value)}'</h1>
1494
+ <hr>
1495
+ </section>
1496
+ """
1497
+ return Response(cmdi_block, status=200)
1498
+
1499
+ return Response(parameter_block, status=200)
1500
+
1501
+ async def setup_after_prep(self, module_test):
1502
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1503
+ expect_args = re.compile("/")
1504
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1505
+
1506
+ def check(self, module_test, events):
1507
+ web_parameter_emitted = False
1508
+ cmdi_echocanary_finding_emitted = False
1509
+ for e in events:
1510
+ if e.type == "WEB_PARAMETER":
1511
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
1512
+ web_parameter_emitted = True
1513
+
1514
+ if e.type == "FINDING":
1515
+ if (
1516
+ "POSSIBLE OS Command Injection. Parameter: [search] Parameter Type: [GETPARAM] Detection Method: [echo canary] CMD Probe Delimeters: [&&]"
1517
+ in e.data["description"]
1518
+ ):
1519
+ cmdi_echocanary_finding_emitted = True
1520
+
1521
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
1522
+ assert cmdi_echocanary_finding_emitted, "echo canary CMDi FINDING not emitted"
1523
+
1524
+
1525
+ # CMDi interactsh
1526
+ class Test_Lightfuzz_cmdi_interactsh(Test_Lightfuzz_cmdi):
1527
+ @staticmethod
1528
+ def extract_subdomain_tag(data):
1529
+ pattern = r"search=.+%26%26%20nslookup%20(.+)\.fakedomain\.fakeinteractsh.com%20%26%26"
1530
+ match = re.search(pattern, data)
1531
+ if match:
1532
+ return match.group(1)
1533
+
1534
+ config_overrides = {
1535
+ "interactsh_disable": False,
1536
+ "modules": {
1537
+ "lightfuzz": {
1538
+ "enabled_submodules": ["cmdi"],
1539
+ }
1540
+ },
1541
+ }
1542
+
1543
+ def request_handler(self, request):
1544
+ qs = str(request.query_string.decode())
1545
+
1546
+ parameter_block = """
1547
+ <section class=search>
1548
+ <form action=/ method=GET>
1549
+ <input type=text placeholder='Search the blog...' name=search>
1550
+ <button type=submit class=button>Search</button>
1551
+ </form>
1552
+ </section>
1553
+ """
1554
+
1555
+ if "search=" in qs:
1556
+ subdomain_tag = None
1557
+ subdomain_tag = self.extract_subdomain_tag(request.full_path)
1558
+
1559
+ if subdomain_tag:
1560
+ self.interactsh_mock_instance.mock_interaction(subdomain_tag)
1561
+ return Response(parameter_block, status=200)
1562
+
1563
+ async def setup_before_prep(self, module_test):
1564
+ self.interactsh_mock_instance = module_test.mock_interactsh("lightfuzz")
1565
+
1566
+ module_test.monkeypatch.setattr(
1567
+ module_test.scan.helpers, "interactsh", lambda *args, **kwargs: self.interactsh_mock_instance
1568
+ )
1569
+
1570
+ async def setup_after_prep(self, module_test):
1571
+ expect_args = re.compile("/")
1572
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1573
+
1574
+ def check(self, module_test, events):
1575
+ web_parameter_emitted = False
1576
+ cmdi_interacttsh_finding_emitted = False
1577
+ for e in events:
1578
+ if e.type == "WEB_PARAMETER":
1579
+ if "HTTP Extracted Parameter [search]" in e.data["description"]:
1580
+ web_parameter_emitted = True
1581
+
1582
+ if e.type == "VULNERABILITY":
1583
+ if (
1584
+ "OS Command Injection (OOB Interaction) Type: [GETPARAM] Parameter Name: [search] Probe: [&&]"
1585
+ in e.data["description"]
1586
+ ):
1587
+ cmdi_interacttsh_finding_emitted = True
1588
+
1589
+ assert web_parameter_emitted, "WEB_PARAMETER was not emitted"
1590
+ assert cmdi_interacttsh_finding_emitted, "interactsh CMDi FINDING not emitted"
1591
+
1592
+
1593
+ class Test_Lightfuzz_speculative(ModuleTestBase):
1594
+ targets = ["http://127.0.0.1:8888/"]
1595
+ modules_overrides = ["httpx", "excavate", "paramminer_getparams", "lightfuzz"]
1596
+ config_overrides = {
1597
+ "interactsh_disable": True,
1598
+ "modules": {
1599
+ "lightfuzz": {"enabled_submodules": ["xss"]},
1600
+ "paramminer_getparams": {"wordlist": tempwordlist([]), "recycle_words": True},
1601
+ "excavate": {"speculate_params": True},
1602
+ },
1603
+ }
1604
+
1605
+ def request_handler(self, request):
1606
+ qs = str(request.query_string.decode())
1607
+ parameter_block = """
1608
+ {
1609
+ "search": 1,
1610
+ "common": 1
1611
+ }
1612
+ """
1613
+ if "search=" in qs:
1614
+ value = qs.split("=")[1]
1615
+ if "&" in value:
1616
+ value = value.split("&")[0]
1617
+ xss_block = f"""
1618
+ <section class=blog-header>
1619
+ <h1>0 search results for '{unquote(value)}'</h1>
1620
+ <hr>
1621
+ </section>
1622
+ """
1623
+ return Response(xss_block, status=200)
1624
+ return Response(parameter_block, status=200, headers={"Content-Type": "application/json"})
1625
+
1626
+ async def setup_after_prep(self, module_test):
1627
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1628
+ expect_args = re.compile("/")
1629
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1630
+
1631
+ def check(self, module_test, events):
1632
+ excavate_json_extraction = False
1633
+ xss_finding_emitted = False
1634
+
1635
+ for e in events:
1636
+ if e.type == "WEB_PARAMETER":
1637
+ if "HTTP Extracted Parameter (speculative from json content) [search]" in e.data["description"]:
1638
+ excavate_json_extraction = True
1639
+
1640
+ if e.type == "FINDING":
1641
+ if "Possible Reflected XSS. Parameter: [search] Context: [Between Tags" in e.data["description"]:
1642
+ xss_finding_emitted = True
1643
+
1644
+ assert excavate_json_extraction, "Excavate failed to extract json parameter"
1645
+ assert xss_finding_emitted, "Between Tags XSS FINDING not emitted"
1646
+
1647
+
1648
+ class Test_Lightfuzz_crypto_error(ModuleTestBase):
1649
+ targets = ["http://127.0.0.1:8888/"]
1650
+ modules_overrides = ["httpx", "excavate", "lightfuzz"]
1651
+ config_overrides = {
1652
+ "interactsh_disable": True,
1653
+ "modules": {
1654
+ "lightfuzz": {"enabled_submodules": ["crypto"]},
1655
+ },
1656
+ }
1657
+
1658
+ def request_handler(self, request):
1659
+ qs = str(request.query_string.decode())
1660
+
1661
+ parameter_block = """
1662
+ <section class=secret>
1663
+ <form action=/ method=GET>
1664
+ <input type=text value='08a5a2cea9c5a5576e6e5314edcba581d21c7111c9c0c06990327b9127058d67' name=secret>
1665
+ <button type=submit class=button>Secret Submit</button>
1666
+ </form>
1667
+ </section>
1668
+ """
1669
+ crypto_block = """
1670
+ <section class=blog-header>
1671
+ <h1>Access Denied!</h1>
1672
+ <hr>
1673
+ </section>
1674
+ """
1675
+ if "secret=" in qs:
1676
+ value = qs.split("=")[1]
1677
+ if value:
1678
+ return Response(crypto_block, status=200)
1679
+
1680
+ return Response(parameter_block, status=200)
1681
+
1682
+ async def setup_after_prep(self, module_test):
1683
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1684
+ expect_args = re.compile("/")
1685
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1686
+
1687
+ def check(self, module_test, events):
1688
+ cryptoerror_parameter_extracted = False
1689
+ cryptoerror_finding_emitted = False
1690
+
1691
+ for e in events:
1692
+ if e.type == "WEB_PARAMETER":
1693
+ if "HTTP Extracted Parameter [secret] (GET Form Submodule)" in e.data["description"]:
1694
+ cryptoerror_parameter_extracted = True
1695
+ if e.type == "FINDING":
1696
+ if (
1697
+ "Possible Cryptographic Error. Parameter: [secret] Parameter Type: [GETPARAM] Original Value: [08a5a2cea9c5a5576e6e5314edcba581d21c7111c9c0c06990327b9127058d67]"
1698
+ in e.data["description"]
1699
+ ):
1700
+ cryptoerror_finding_emitted = True
1701
+ assert cryptoerror_parameter_extracted, "Parameter not extracted"
1702
+ assert cryptoerror_finding_emitted, "Crypto Error Message FINDING not emitted"
1703
+
1704
+
1705
+ class Test_Lightfuzz_crypto_error_falsepositive(ModuleTestBase):
1706
+ targets = ["http://127.0.0.1:8888/"]
1707
+ modules_overrides = ["httpx", "excavate", "lightfuzz"]
1708
+ config_overrides = {
1709
+ "interactsh_disable": True,
1710
+ "modules": {
1711
+ "lightfuzz": {"enabled_submodules": ["crypto"]},
1712
+ },
1713
+ }
1714
+
1715
+ def request_handler(self, request):
1716
+ fp_block = """
1717
+ <section class=secret>
1718
+ <form action=/ method=GET>
1719
+ <input type=text value='08a5a2cea9c5a5576e6e5314edcba581d21c7111c9c0c06990327b9127058d67' name=secret>
1720
+ <button type=submit class=button>Secret Submit</button>
1721
+ </form>
1722
+ <h1>Access Denied!</h1>
1723
+ </section>
1724
+ """
1725
+ return Response(fp_block, status=200)
1726
+
1727
+ async def setup_after_prep(self, module_test):
1728
+ module_test.scan.modules["lightfuzz"].helpers.rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1729
+ expect_args = re.compile("/")
1730
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1731
+
1732
+ def check(self, module_test, events):
1733
+ cryptoerror_parameter_extracted = False
1734
+ cryptoerror_finding_emitted = False
1735
+
1736
+ for e in events:
1737
+ if e.type == "WEB_PARAMETER":
1738
+ if "HTTP Extracted Parameter [secret] (GET Form Submodule)" in e.data["description"]:
1739
+ cryptoerror_parameter_extracted = True
1740
+ if e.type == "FINDING":
1741
+ if "Possible Cryptographic Error" in e.data["description"]:
1742
+ cryptoerror_finding_emitted = True
1743
+ assert cryptoerror_parameter_extracted, "Parameter not extracted"
1744
+ assert not cryptoerror_finding_emitted, (
1745
+ "Crypto Error Message FINDING was emitted (it is an intentional false positive)"
1746
+ )
1747
+
1748
+
1749
+ class Test_Lightfuzz_PaddingOracleDetection(ModuleTestBase):
1750
+ targets = ["http://127.0.0.1:8888"]
1751
+ modules_overrides = ["httpx", "excavate", "lightfuzz"]
1752
+ config_overrides = {
1753
+ "interactsh_disable": True,
1754
+ "modules": {
1755
+ "lightfuzz": {
1756
+ "enabled_submodules": ["crypto"],
1757
+ }
1758
+ },
1759
+ }
1760
+
1761
+ def request_handler(self, request):
1762
+ encrypted_value = quote(
1763
+ "dplyorsu8VUriMW/8DqVDU6kRwL/FDk3Q+4GXVGZbo0CTh9YX1YvzZZJrYe4cHxvAICyliYtp1im4fWoOa54Zg=="
1764
+ )
1765
+ default_html_response = f"""
1766
+ <html>
1767
+ <body>
1768
+ <form action="/decrypt" method="post">
1769
+ <input type="hidden" name="encrypted_data" value="{encrypted_value}" />
1770
+ <button type="submit">Decrypt</button>
1771
+ </form>
1772
+ </body>
1773
+ </html>
1774
+ """
1775
+
1776
+ if "/decrypt" in request.url and request.method == "POST":
1777
+ if request.form and request.form["encrypted_data"]:
1778
+ encrypted_data = request.form["encrypted_data"]
1779
+ if "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwAgLKWJi2nWKbh9ag5rnhm" in encrypted_data:
1780
+ response_content = "Padding error detected"
1781
+ elif "4GXVGZbo0DTh9YX1YvzZZJrYe4cHxvAICyliYtp1im4fWoOa54Zg" in encrypted_data:
1782
+ response_content = "DIFFERENT CRYPTOGRAPHIC ERROR"
1783
+ elif "AAAAAAA" in encrypted_data:
1784
+ response_content = "YET DIFFERENT CRYPTOGRAPHIC ERROR"
1785
+ else:
1786
+ response_content = "Decryption failed"
1787
+
1788
+ return Response(response_content, status=200)
1789
+ else:
1790
+ return Response(default_html_response, status=200)
1791
+
1792
+ async def setup_after_prep(self, module_test):
1793
+ module_test.set_expect_requests_handler(expect_args=re.compile(".*"), request_handler=self.request_handler)
1794
+
1795
+ def check(self, module_test, events):
1796
+ web_parameter_extracted = False
1797
+ cryptographic_parameter_finding = False
1798
+ padding_oracle_detected = False
1799
+ for e in events:
1800
+ if e.type == "WEB_PARAMETER":
1801
+ if "HTTP Extracted Parameter [encrypted_data] (POST Form" in e.data["description"]:
1802
+ web_parameter_extracted = True
1803
+ if e.type == "FINDING":
1804
+ if (
1805
+ e.data["description"]
1806
+ == "Probable Cryptographic Parameter. Parameter: [encrypted_data] Parameter Type: [POSTPARAM] Original Value: [dplyorsu8VUriMW/8DqVDU6kRwL/FDk3Q%2B4GXVGZbo0CTh9YX1YvzZZJrYe4cHxvAICyliYtp1im4fWoOa54Zg%3D%3D] Detection Technique(s): [Single-byte Mutation] Envelopes: [URL-Encoded]"
1807
+ ):
1808
+ cryptographic_parameter_finding = True
1809
+
1810
+ if e.type == "VULNERABILITY":
1811
+ if (
1812
+ e.data["description"]
1813
+ == "Padding Oracle Vulnerability. Block size: [16] Parameter: [encrypted_data] Parameter Type: [POSTPARAM] Original Value: [dplyorsu8VUriMW/8DqVDU6kRwL/FDk3Q%2B4GXVGZbo0CTh9YX1YvzZZJrYe4cHxvAICyliYtp1im4fWoOa54Zg%3D%3D] Envelopes: [URL-Encoded]"
1814
+ ):
1815
+ padding_oracle_detected = True
1816
+
1817
+ assert web_parameter_extracted, "Web parameter was not extracted"
1818
+ assert cryptographic_parameter_finding, "Cryptographic parameter not detected"
1819
+ assert padding_oracle_detected, "Padding oracle vulnerability was not detected"
1820
+
1821
+
1822
+ class Test_Lightfuzz_XSS_jsquotecontext(ModuleTestBase):
1823
+ targets = ["http://127.0.0.1:8888"]
1824
+ modules_overrides = ["httpx", "lightfuzz", "excavate", "paramminer_getparams"]
1825
+ config_overrides = {
1826
+ "interactsh_disable": True,
1827
+ "modules": {
1828
+ "lightfuzz": {"enabled_submodules": ["xss"]},
1829
+ "paramminer_getparams": {"wordlist": tempwordlist(["junk", "input"]), "recycle_words": True},
1830
+ },
1831
+ }
1832
+
1833
+ def request_handler(self, request):
1834
+ # Decode the query string
1835
+ qs = str(request.query_string.decode())
1836
+ default_output = """
1837
+ <html>
1838
+ <form action="/" method="get">
1839
+ <input type="text" name="input" value="default">
1840
+ <input type="submit" value="Submit">
1841
+ </form>
1842
+ </html>
1843
+ """
1844
+
1845
+ if "input=" in qs:
1846
+ # Split the query string to isolate the 'input' parameter
1847
+ params = qs.split("&")
1848
+ input_value = None
1849
+ for param in params:
1850
+ if param.startswith("input="):
1851
+ input_value = param.split("=")[1]
1852
+ break
1853
+
1854
+ if input_value:
1855
+ # Simulate flawed escaping
1856
+ sanitized_input = input_value.replace('"', '\\"').replace("'", "\\'")
1857
+ sanitized_input = sanitized_input.replace("<", "%3C").replace(">", "%3E")
1858
+
1859
+ # Construct the reflected block with the sanitized input
1860
+ reflected_block = f"""
1861
+ <html>
1862
+ <script>
1863
+ let userInput = '{sanitized_input}';
1864
+ console.log(userInput);
1865
+ </script>
1866
+ </html>
1867
+ """
1868
+ return Response(reflected_block, status=200)
1869
+
1870
+ return Response(default_output, status=200)
1871
+
1872
+ async def setup_after_prep(self, module_test):
1873
+ module_test.scan.modules["paramminer_getparams"].rand_string = lambda *args, **kwargs: "AAAAAAAAAAAAAA"
1874
+ module_test.monkeypatch.setattr(
1875
+ helper.HttpCompare, "gen_cache_buster", lambda *args, **kwargs: {"AAAAAA": "1"}
1876
+ )
1877
+ expect_args = re.compile("/")
1878
+ module_test.set_expect_requests_handler(expect_args=expect_args, request_handler=self.request_handler)
1879
+
1880
+ def check(self, module_test, events):
1881
+ web_parameter_emitted = False
1882
+ xss_finding_emitted = False
1883
+
1884
+ for e in events:
1885
+ if e.type == "WEB_PARAMETER":
1886
+ if "[Paramminer] Getparam: [input] Reasons: [body] Reflection: [True]" in e.data["description"]:
1887
+ web_parameter_emitted = True
1888
+
1889
+ if e.type == "FINDING":
1890
+ if "Possible Reflected XSS. Parameter: [input] Context: [In Javascript (escaping the escape character, single quote)] Parameter Type: [GETPARAM]":
1891
+ xss_finding_emitted = True
1892
+
1893
+ assert web_parameter_emitted, "WEB_PARAMETER for was not emitted"
1894
+ assert xss_finding_emitted, "XSS FINDING not emitted"
1895
+
1896
+
1897
+ class Test_Lightfuzz_XSS_jsquotecontext_doublequote(Test_Lightfuzz_XSS_jsquotecontext):
1898
+ def request_handler(self, request):
1899
+ qs = str(request.query_string.decode())
1900
+ default_output = """
1901
+ <html>
1902
+ <form action="/" method="get">
1903
+ <input type="text" name="input" value="default">
1904
+ <input type="submit" value="Submit">
1905
+ </form>
1906
+ </html>
1907
+ """
1908
+
1909
+ if "input=" in qs:
1910
+ params = qs.split("&")
1911
+ input_value = None
1912
+ for param in params:
1913
+ if param.startswith("input="):
1914
+ input_value = param.split("=")[1]
1915
+ break
1916
+
1917
+ if input_value:
1918
+ # Simulate flawed escaping with opposite quotes
1919
+ sanitized_input = input_value.replace("'", "\\").replace("%22", '\\"')
1920
+ sanitized_input = sanitized_input.replace("<", "%3C").replace(">", "%3E")
1921
+
1922
+ reflected_block = f"""
1923
+ <html>
1924
+ <script>
1925
+ let userInput = "{sanitized_input}";
1926
+ console.log(userInput);
1927
+ </script>
1928
+ </html>
1929
+ """
1930
+ return Response(reflected_block, status=200)
1931
+
1932
+ return Response(default_output, status=200)
1933
+
1934
+ def check(self, module_test, events):
1935
+ web_parameter_emitted = False
1936
+ xss_finding_emitted = False
1937
+ for e in events:
1938
+ if e.type == "WEB_PARAMETER":
1939
+ if "[Paramminer] Getparam: [input] Reasons: [body] Reflection: [True]" in e.data["description"]:
1940
+ web_parameter_emitted = True
1941
+
1942
+ if e.type == "FINDING":
1943
+ if "Possible Reflected XSS. Parameter: [input] Context: [In Javascript (escaping the escape character, double quote)] Parameter Type: [GETPARAM]":
1944
+ xss_finding_emitted = True
1945
+
1946
+ assert web_parameter_emitted, "WEB_PARAMETER for was not emitted"
1947
+ assert xss_finding_emitted, "XSS FINDING not emitted"