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.
- bbot/__init__.py +1 -1
- bbot/core/event/base.py +64 -4
- bbot/core/helpers/diff.py +10 -7
- bbot/core/helpers/helper.py +5 -1
- bbot/core/helpers/misc.py +48 -11
- bbot/core/helpers/regex.py +4 -0
- bbot/core/helpers/regexes.py +45 -8
- bbot/core/helpers/url.py +21 -5
- bbot/core/helpers/web/client.py +25 -5
- bbot/core/helpers/web/engine.py +9 -1
- bbot/core/helpers/web/envelopes.py +352 -0
- bbot/core/helpers/web/web.py +10 -2
- bbot/core/helpers/yara_helper.py +50 -0
- bbot/core/modules.py +23 -7
- bbot/defaults.yml +26 -1
- bbot/modules/base.py +4 -2
- bbot/modules/{deadly/dastardly.py → dastardly.py} +1 -1
- bbot/modules/{deadly/ffuf.py → ffuf.py} +1 -1
- bbot/modules/ffuf_shortnames.py +1 -1
- bbot/modules/httpx.py +14 -0
- bbot/modules/hunt.py +24 -6
- bbot/modules/internal/aggregate.py +1 -0
- bbot/modules/internal/excavate.py +356 -197
- bbot/modules/lightfuzz/lightfuzz.py +203 -0
- bbot/modules/lightfuzz/submodules/__init__.py +0 -0
- bbot/modules/lightfuzz/submodules/base.py +312 -0
- bbot/modules/lightfuzz/submodules/cmdi.py +106 -0
- bbot/modules/lightfuzz/submodules/crypto.py +474 -0
- bbot/modules/lightfuzz/submodules/nosqli.py +183 -0
- bbot/modules/lightfuzz/submodules/path.py +154 -0
- bbot/modules/lightfuzz/submodules/serial.py +179 -0
- bbot/modules/lightfuzz/submodules/sqli.py +187 -0
- bbot/modules/lightfuzz/submodules/ssti.py +39 -0
- bbot/modules/lightfuzz/submodules/xss.py +191 -0
- bbot/modules/{deadly/nuclei.py → nuclei.py} +1 -1
- bbot/modules/paramminer_headers.py +2 -0
- bbot/modules/reflected_parameters.py +80 -0
- bbot/modules/{deadly/vhost.py → vhost.py} +2 -2
- bbot/presets/web/lightfuzz-heavy.yml +16 -0
- bbot/presets/web/lightfuzz-light.yml +20 -0
- bbot/presets/web/lightfuzz-medium.yml +14 -0
- bbot/presets/web/lightfuzz-superheavy.yml +13 -0
- bbot/presets/web/lightfuzz-xss.yml +21 -0
- bbot/presets/web/paramminer.yml +8 -5
- bbot/scanner/preset/args.py +26 -0
- bbot/scanner/scanner.py +6 -0
- bbot/test/test_step_1/test__module__tests.py +1 -1
- bbot/test/test_step_1/test_helpers.py +7 -0
- bbot/test/test_step_1/test_presets.py +2 -2
- bbot/test/test_step_1/test_web.py +20 -0
- bbot/test/test_step_1/test_web_envelopes.py +343 -0
- bbot/test/test_step_2/module_tests/test_module_excavate.py +404 -29
- bbot/test/test_step_2/module_tests/test_module_httpx.py +29 -0
- bbot/test/test_step_2/module_tests/test_module_hunt.py +18 -1
- bbot/test/test_step_2/module_tests/test_module_lightfuzz.py +1947 -0
- bbot/test/test_step_2/module_tests/test_module_paramminer_getparams.py +4 -1
- bbot/test/test_step_2/module_tests/test_module_paramminer_headers.py +46 -2
- bbot/test/test_step_2/module_tests/test_module_reflected_parameters.py +226 -0
- bbot/wordlists/paramminer_parameters.txt +0 -8
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/METADATA +2 -1
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/RECORD +64 -42
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/LICENSE +0 -0
- {bbot-2.4.2.dist-info → bbot-2.4.2.6590rc0.dist-info}/WHEEL +0 -0
- {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"
|