85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
import requests
|
|
import yaml
|
|
|
|
|
|
NUCLEI_FINGERPRINT_URL: str = "https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/refs/heads/main/http/global-matchers/global-waf-detect.yaml"
|
|
|
|
def _check_nuclei_regex(matcher: dict[str,str|list[str]], response: requests.Response) -> bool:
|
|
import re
|
|
|
|
and_cond: bool = matcher.get('condition', '') == 'and'
|
|
|
|
target_text: str
|
|
if matcher['part'] == 'body':
|
|
target_text = response.text
|
|
elif matcher['part'] == 'header':
|
|
target_text = str(response.headers)
|
|
else:
|
|
target_text = response.text + str(response.headers)
|
|
|
|
for regex in matcher['regex']:
|
|
if re.search(regex, target_text):
|
|
if not and_cond:
|
|
return True
|
|
else:
|
|
break
|
|
else:
|
|
# `and` conditions will cycle, resulting in this default return True
|
|
# unless an early failed detection breaks the loop (resulting in False)
|
|
return True
|
|
return False
|
|
|
|
def _check_nuclei_words(matcher: dict[str,str|list[str]], response: requests.Response) -> bool:
|
|
and_cond: bool = matcher.get('condition', '') == 'and'
|
|
|
|
target_text: str
|
|
if matcher['part'] == 'body':
|
|
target_text = response.text
|
|
elif matcher['part'] == 'header':
|
|
target_text = str(response.headers)
|
|
else:
|
|
target_text = response.text + str(response.headers)
|
|
|
|
for word in matcher['words']:
|
|
if word in target_text:
|
|
if not and_cond:
|
|
return True
|
|
else:
|
|
break
|
|
else:
|
|
# `and` conditions will cycle, resulting in this default return True
|
|
# unless an early failed detection breaks the loop (resulting in False)
|
|
return True
|
|
return False
|
|
|
|
def fetch_nuclei_fingerprints() -> list[dict[str,str|list[str]]] | None:
|
|
"""Fetch the latest Nuclei WAF fingerprints from the official repository."""
|
|
try:
|
|
response = requests.get(NUCLEI_FINGERPRINT_URL, timeout=10)
|
|
response.raise_for_status()
|
|
raw = yaml.safe_load(response.text)
|
|
fingerprints: list[dict[str,str|list[str]]] = raw['http'][0]['matchers']
|
|
return fingerprints
|
|
except requests.RequestException as e:
|
|
print(f"Error fetching Nuclei fingerprints: {e}")
|
|
return None
|
|
except yaml.YAMLError as e:
|
|
print(f"Error parsing YAML data: {e}")
|
|
return None
|
|
|
|
def nuclei_check(response: requests.Response, fingerprints: list[dict[str,str|list[str]]]) -> bool:
|
|
"""Check if the response matches any of the WAF fingerprints.
|
|
|
|
Keyword arguments:
|
|
response -- The HTTP response to check.
|
|
fingerprints -- The list of Nuclei WAF fingerprints to check against.
|
|
|
|
Returns True if a WAF is detected, False otherwise.
|
|
"""
|
|
for matcher in fingerprints:
|
|
if matcher['type'] == 'word':
|
|
return _check_nuclei_words(matcher, response)
|
|
elif matcher['type'] == 'regex':
|
|
return _check_nuclei_regex(matcher, response)
|
|
return False
|