Skip to content

Commit 9fc3837

Browse files
committed
Use YARA for WAF string detection instead of Python string-in loops
1 parent 79349dd commit 9fc3837

5 files changed

Lines changed: 34 additions & 16 deletions

File tree

bbot/modules/bypass403.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from bbot.errors import HttpCompareError
22
from bbot.modules.base import BaseModule
3+
from bbot.core.helpers.misc import get_waf_strings
34

45
"""
56
Port of https://github.com/iamj0ker/bypass-403/ and https://portswigger.net/bappstore/444407b96d9c4de0adb7aed89e826122
@@ -80,6 +81,10 @@ class bypass403(BaseModule):
8081
meta = {"description": "Check 403 pages for common bypasses", "created_date": "2022-07-05", "author": "@liquidsec"}
8182
in_scope_only = True
8283

84+
async def setup(self):
85+
self.waf_yara_rules = self.helpers.yara.compile_strings(get_waf_strings(), nocase=True)
86+
return True
87+
8388
async def do_checks(self, compare_helper, event, collapse_threshold):
8489
results = set()
8590
error_count = 0
@@ -105,10 +110,10 @@ async def do_checks(self, compare_helper, event, collapse_threshold):
105110

106111
# In some cases WAFs will respond with a 200 code which causes a false positive
107112
if subject_response is not None:
108-
for waf_string in self.helpers.get_waf_strings():
109-
if waf_string in subject_response.text:
110-
self.debug("Rejecting result based on presence of WAF string")
111-
return
113+
waf_matches = await self.helpers.yara.match(self.waf_yara_rules, subject_response.text)
114+
if waf_matches:
115+
self.debug("Rejecting result based on presence of WAF string")
116+
return
112117

113118
if match is False:
114119
if str(subject_response.status_code)[0] != "4":

bbot/modules/lightfuzz/lightfuzz.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from bbot.modules.base import BaseModule
33

44
from bbot.errors import InteractshError
5+
from bbot.core.helpers.misc import get_waf_strings
56

67

78
class lightfuzz(BaseModule):
@@ -60,6 +61,8 @@ async def setup(self):
6061
return False, f"Invalid Lightfuzz submodule ({submodule_name}) specified in enabled_modules"
6162
self.submodules[submodule_name] = submodule_class
6263

64+
self.waf_yara_rules = self.helpers.yara.compile_strings(get_waf_strings(), nocase=True)
65+
6366
interactsh_needed = any(submodule.uses_interactsh for submodule in self.submodules.values())
6467
if interactsh_needed and not self.interactsh_disable:
6568
try:

bbot/modules/lightfuzz/submodules/path.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,8 @@ async def fuzz(self):
113113
and doubledot_probe[0] is False
114114
and doubledot_probe[3] is not None
115115
and doubledot_probe[1] != ["header"]
116-
and not any(
117-
waf_string in doubledot_probe[3].text
118-
for waf_string in self.lightfuzz.helpers.get_waf_strings()
116+
and not await self.lightfuzz.helpers.yara.match(
117+
self.lightfuzz.waf_yara_rules, doubledot_probe[3].text
119118
)
120119
):
121120
confirmations += 1

bbot/modules/lightfuzz/submodules/serial.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from .base import BaseLightfuzz
22
from bbot.errors import HttpCompareError
3-
from bbot.core.helpers.misc import get_waf_strings
43

54

65
class serial(BaseLightfuzz):
@@ -48,10 +47,20 @@ class serial(BaseLightfuzz):
4847
"java.io.optionaldataexception",
4948
]
5049

51-
GENERAL_ERRORS = [
50+
GENERAL_ERROR_STRINGS = [
5251
"Internal Error",
5352
"Internal Server Error",
54-
] + get_waf_strings()
53+
]
54+
55+
@property
56+
def general_error_yara_rules(self):
57+
if not hasattr(self.lightfuzz, "_serial_general_error_rules"):
58+
from bbot.core.helpers.misc import get_waf_strings
59+
60+
self.lightfuzz._serial_general_error_rules = self.lightfuzz.helpers.yara.compile_strings(
61+
self.GENERAL_ERROR_STRINGS + get_waf_strings(), nocase=True
62+
)
63+
return self.lightfuzz._serial_general_error_rules
5564

5665
def is_possibly_serialized(self, value):
5766
# Use the is_base64 method from BaseLightfuzz via self
@@ -101,7 +110,6 @@ async def fuzz(self):
101110
php_raw_serialization_payloads = self.PHP_RAW_SERIALIZATION_PAYLOADS
102111

103112
serialization_errors = self.SERIALIZATION_ERRORS
104-
general_errors = self.GENERAL_ERRORS
105113

106114
probe_value = self.incoming_probe_value(populate_empty=False)
107115
if probe_value:
@@ -172,12 +180,13 @@ async def fuzz(self):
172180
)
173181
continue
174182

183+
general_error_matches = await self.lightfuzz.helpers.yara.match(
184+
self.general_error_yara_rules, response.text
185+
)
175186
if (
176187
status_code == 200
177188
and "code" in diff_reasons
178-
and not any(
179-
error in response.text for error in general_errors
180-
) # ensure the 200 is not actually an error
189+
and not general_error_matches # ensure the 200 is not actually an error
181190
):
182191
# Confirm the baseline error state is stable by re-sending the control payload.
183192
# If the control also returns 200 now, the original error was transient.

bbot/modules/lightfuzz/submodules/sqli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from .base import BaseLightfuzz
22
from bbot.errors import HttpCompareError
3-
from bbot.core.helpers.misc import get_waf_strings
43

54
import statistics
65

@@ -122,7 +121,10 @@ async def fuzz(self):
122121
):
123122
# Check if the status code change is due to a WAF, not SQL injection
124123
if single_quote[3].status_code == 403:
125-
waf_detected = any(ws in single_quote[3].text for ws in get_waf_strings())
124+
waf_matches = await self.lightfuzz.helpers.yara.match(
125+
self.lightfuzz.waf_yara_rules, single_quote[3].text
126+
)
127+
waf_detected = len(waf_matches) > 0
126128
if waf_detected:
127129
self.debug(
128130
"Single quote probe returned 403 with WAF signature, "

0 commit comments

Comments
 (0)