Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

172 lines
5.7 KiB
Python

#!/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()