#!/usr/bin/env python3 """Network scanning detection agent using IDS signature analysis. Detects port scanning, host sweeps, and service enumeration by analyzing Suricata/Snort alerts and connection logs for scanning patterns. """ import argparse import json import re from collections import defaultdict from datetime import datetime SCAN_SIGNATURES = { "SYN_SCAN": {"ports_threshold": 20, "severity": "HIGH", "mitre": "T1046"}, "CONNECT_SCAN": {"ports_threshold": 15, "severity": "HIGH", "mitre": "T1046"}, "UDP_SCAN": {"ports_threshold": 20, "severity": "MEDIUM", "mitre": "T1046"}, "XMAS_SCAN": {"severity": "HIGH", "mitre": "T1046"}, "FIN_SCAN": {"severity": "HIGH", "mitre": "T1046"}, "NULL_SCAN": {"severity": "HIGH", "mitre": "T1046"}, "HOST_SWEEP": {"hosts_threshold": 10, "severity": "MEDIUM", "mitre": "T1018"}, "SERVICE_ENUM": {"severity": "MEDIUM", "mitre": "T1046"}, } NMAP_SIGNATURES = [ r"Nmap\s+Scripting\s+Engine", r"nmap", r"masscan", r"zmap", r"rustscan", r"unicornscan", ] def parse_suricata_eve(filepath, event_type="alert"): events = [] with open(filepath, "r") as f: for line in f: try: evt = json.loads(line.strip()) if evt.get("event_type") == event_type: events.append(evt) except json.JSONDecodeError: continue return events def parse_connection_log(filepath): connections = [] with open(filepath, "r") as f: headers = None for line in f: if line.startswith("#fields"): headers = line.strip().split("\t")[1:] continue if line.startswith("#"): continue if not headers: continue fields = line.strip().split("\t") if len(fields) >= len(headers): connections.append(dict(zip(headers, fields))) return connections def detect_port_scan(connections, threshold=20): findings = [] src_dst_ports = defaultdict(set) src_dst_count = defaultdict(int) for conn in connections: src = conn.get("id.orig_h", "") dst = conn.get("id.resp_h", "") port = conn.get("id.resp_p", "") state = conn.get("conn_state", "") src_dst_ports[f"{src}->{dst}"].add(port) src_dst_count[f"{src}->{dst}"] += 1 for pair, ports in src_dst_ports.items(): if len(ports) >= threshold: src = pair.split("->")[0] dst = pair.split("->")[1] findings.append({ "type": "port_scan", "source": src, "destination": dst, "unique_ports": len(ports), "total_connections": src_dst_count[pair], "severity": "CRITICAL" if len(ports) > 100 else "HIGH", "mitre": "T1046", }) return findings def detect_host_sweep(connections, threshold=10): findings = [] src_dsts = defaultdict(set) src_port = defaultdict(set) for conn in connections: src = conn.get("id.orig_h", "") dst = conn.get("id.resp_h", "") port = conn.get("id.resp_p", "") src_dsts[src].add(dst) src_port[f"{src}:{port}"].add(dst) for src_p, hosts in src_port.items(): if len(hosts) >= threshold: src, port = src_p.rsplit(":", 1) findings.append({ "type": "host_sweep", "source": src, "port": port, "unique_hosts": len(hosts), "severity": "HIGH" if len(hosts) > 50 else "MEDIUM", "mitre": "T1018", }) return findings def analyze_ids_alerts(alerts): findings = [] for alert in alerts: sig = alert.get("alert", {}).get("signature", "") category = alert.get("alert", {}).get("category", "") src = alert.get("src_ip", "") dst = alert.get("dest_ip", "") severity = alert.get("alert", {}).get("severity", 3) for pattern in NMAP_SIGNATURES: if re.search(pattern, sig, re.IGNORECASE): findings.append({ "type": "scanner_detected", "tool": pattern.replace("\\s+", " "), "source": src, "destination": dst, "signature": sig, "severity": "HIGH", "mitre": "T1046", }) if "scan" in category.lower() or "scan" in sig.lower(): findings.append({ "type": "ids_scan_alert", "source": src, "destination": dst, "signature": sig, "category": category, "severity": "HIGH" if severity <= 2 else "MEDIUM", }) return findings def main(): parser = argparse.ArgumentParser(description="Network Scanning Detector") parser.add_argument("--eve-log", help="Suricata EVE JSON log") parser.add_argument("--conn-log", help="Zeek conn.log file") parser.add_argument("--port-threshold", type=int, default=20) parser.add_argument("--sweep-threshold", type=int, default=10) args = parser.parse_args() results = {"timestamp": datetime.utcnow().isoformat() + "Z", "findings": []} if args.eve_log: alerts = parse_suricata_eve(args.eve_log) results["findings"].extend(analyze_ids_alerts(alerts)) if args.conn_log: connections = parse_connection_log(args.conn_log) results["findings"].extend(detect_port_scan(connections, args.port_threshold)) results["findings"].extend(detect_host_sweep(connections, args.sweep_threshold)) results["total_findings"] = len(results["findings"]) print(json.dumps(results, indent=2)) if __name__ == "__main__": main()