Initial build: geo-scout geo-targeted network scanner
masscan port discovery → nmap fingerprinting → probe engine → pattern/version matching Dynamic YAML inputs: countries.yaml (country codes + CIDR resolution) and targets.yaml (fingerprint probes) RIR delegated stats for authoritative country CIDR data (no external API) Supports tcp_banner, http, https, rtsp, udp probe types Version extraction and comparison operators (<=, >=, ==, etc.) SQLite results per engagement, CSV/JSON export format-spec/ docs for generating input files externally
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import re
|
||||
from typing import Optional
|
||||
from .prober import ProbeResult
|
||||
|
||||
|
||||
def _version_compare(extracted: str, operator: str, threshold: str) -> bool:
|
||||
def to_tuple(v: str):
|
||||
try:
|
||||
return tuple(int(x) for x in v.split("."))
|
||||
except ValueError:
|
||||
return (v,)
|
||||
|
||||
ev = to_tuple(extracted)
|
||||
tv = to_tuple(threshold)
|
||||
if operator == "<=":
|
||||
return ev <= tv
|
||||
elif operator == ">=":
|
||||
return ev >= tv
|
||||
elif operator == "<":
|
||||
return ev < tv
|
||||
elif operator == ">":
|
||||
return ev > tv
|
||||
elif operator == "==":
|
||||
return ev == tv
|
||||
elif operator == "!=":
|
||||
return ev != tv
|
||||
return False
|
||||
|
||||
|
||||
def _get_search_text(result: ProbeResult, match_in) -> str:
|
||||
if isinstance(match_in, list):
|
||||
parts = []
|
||||
if "body" in match_in:
|
||||
parts.append(result.body or result.banner)
|
||||
if "headers" in match_in:
|
||||
parts.append(" ".join(f"{k}: {v}" for k, v in result.headers.items()))
|
||||
return "\n".join(parts)
|
||||
if match_in == "headers":
|
||||
return " ".join(f"{k}: {v}" for k, v in result.headers.items())
|
||||
return result.body or result.banner
|
||||
|
||||
|
||||
def match_probe(result: ProbeResult, probe: dict) -> tuple[bool, Optional[str]]:
|
||||
if not result.success:
|
||||
return False, None
|
||||
|
||||
match_in = probe.get("match_in", "body")
|
||||
text = _get_search_text(result, match_in)
|
||||
|
||||
patterns = probe.get("patterns", [])
|
||||
all_patterns = probe.get("all_patterns", [])
|
||||
|
||||
if patterns:
|
||||
if not any(re.search(p, text) for p in patterns):
|
||||
return False, None
|
||||
|
||||
if all_patterns:
|
||||
if not all(re.search(p, text) for p in all_patterns):
|
||||
return False, None
|
||||
|
||||
extracted_version = None
|
||||
version_extract = probe.get("version_extract")
|
||||
if version_extract:
|
||||
m = re.search(version_extract, text)
|
||||
if m:
|
||||
extracted_version = m.group(1)
|
||||
|
||||
version_compare = probe.get("version_compare")
|
||||
if version_compare and extracted_version:
|
||||
op = version_compare.get("operator", "==")
|
||||
val = str(version_compare.get("value", ""))
|
||||
if not _version_compare(extracted_version, op, val):
|
||||
return False, None
|
||||
elif version_compare and not extracted_version:
|
||||
return False, None
|
||||
|
||||
return True, extracted_version
|
||||
|
||||
|
||||
def match_target(host: str, port: int, probe_results: list[tuple[dict, ProbeResult]]) -> list[dict]:
|
||||
matches = []
|
||||
for probe, result in probe_results:
|
||||
matched, version = match_probe(result, probe)
|
||||
if matched:
|
||||
matches.append({
|
||||
"probe_type": result.probe_type,
|
||||
"confidence": probe.get("confidence", "medium"),
|
||||
"version": version,
|
||||
})
|
||||
return matches
|
||||
Reference in New Issue
Block a user