sherlock/sherlock_project/waf_check.py

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