mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
c47eed6a64
- 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
147 lines
6.5 KiB
Python
147 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""OT Network Segmentation Agent - audits IT/OT boundaries, firewall rules, and zone compliance."""
|
|
|
|
import json
|
|
import argparse
|
|
import logging
|
|
import subprocess
|
|
from datetime import datetime
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PURDUE_ZONES = {
|
|
"level_0": {"name": "Process", "description": "Field devices, sensors, actuators"},
|
|
"level_1": {"name": "Control", "description": "PLCs, RTUs, safety systems"},
|
|
"level_2": {"name": "Supervisory", "description": "HMI, SCADA, engineering workstations"},
|
|
"level_3": {"name": "Operations", "description": "Historian, OT domain services"},
|
|
"level_3.5": {"name": "DMZ", "description": "IT/OT demilitarized zone"},
|
|
"level_4": {"name": "Enterprise", "description": "IT network, business systems"},
|
|
"level_5": {"name": "External", "description": "Internet, cloud services"},
|
|
}
|
|
|
|
|
|
def scan_zone_hosts(subnet):
|
|
"""Discover hosts in an OT zone via nmap ping scan."""
|
|
cmd = ["nmap", "-sn", subnet, "-oX", "-"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
hosts = []
|
|
import re
|
|
for match in re.finditer(r'addr="(\d+\.\d+\.\d+\.\d+)"', result.stdout):
|
|
hosts.append(match.group(1))
|
|
return hosts
|
|
|
|
|
|
def check_firewall_rules(firewall_config_file):
|
|
"""Parse firewall rules for IT/OT boundary enforcement."""
|
|
findings = []
|
|
try:
|
|
with open(firewall_config_file) as f:
|
|
rules = json.load(f)
|
|
for rule in rules:
|
|
if rule.get("action") == "permit":
|
|
src_zone = rule.get("source_zone", "")
|
|
dst_zone = rule.get("destination_zone", "")
|
|
if src_zone in ("enterprise", "external") and dst_zone in ("control", "process"):
|
|
findings.append({
|
|
"rule_id": rule.get("id", ""),
|
|
"issue": f"Direct access from {src_zone} to {dst_zone} zone",
|
|
"severity": "critical",
|
|
"recommendation": "Route through DMZ with application proxy",
|
|
})
|
|
if rule.get("protocol") == "any" or rule.get("port") == "any":
|
|
findings.append({
|
|
"rule_id": rule.get("id", ""),
|
|
"issue": "Overly permissive rule (any protocol/port)",
|
|
"severity": "high",
|
|
"recommendation": "Restrict to specific OT protocols (Modbus/TCP 502, EtherNet/IP 44818)",
|
|
})
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
findings.append({"issue": "Firewall config not found or invalid", "severity": "critical"})
|
|
return findings
|
|
|
|
|
|
def check_ot_protocol_exposure(target_subnet):
|
|
"""Check for exposed OT protocols on the network."""
|
|
ot_ports = {"502": "Modbus", "102": "S7comm", "44818": "EtherNet/IP",
|
|
"20000": "DNP3", "4840": "OPC-UA", "2222": "EtherNet/IP-explicit"}
|
|
port_list = ",".join(ot_ports.keys())
|
|
cmd = ["nmap", "-sS", "-p", port_list, target_subnet, "--open", "-oX", "-"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
exposed = []
|
|
import re
|
|
current_host = ""
|
|
for line in result.stdout.split("\n"):
|
|
host_match = re.search(r'addr="(\d+\.\d+\.\d+\.\d+)"', line)
|
|
if host_match:
|
|
current_host = host_match.group(1)
|
|
port_match = re.search(r'portid="(\d+)".*state="open"', line)
|
|
if port_match and current_host:
|
|
port = port_match.group(1)
|
|
exposed.append({
|
|
"host": current_host, "port": int(port),
|
|
"protocol": ot_ports.get(port, "unknown"),
|
|
"risk": "high" if port in ("502", "102") else "medium",
|
|
})
|
|
return exposed
|
|
|
|
|
|
def audit_zone_compliance(zone_config):
|
|
"""Audit zone assignment compliance against Purdue model."""
|
|
findings = []
|
|
for zone_id, zone_data in zone_config.items():
|
|
if zone_id not in PURDUE_ZONES:
|
|
findings.append({"zone": zone_id, "issue": "Non-standard zone", "severity": "medium"})
|
|
continue
|
|
hosts = zone_data.get("hosts", [])
|
|
for host in hosts:
|
|
if host.get("type") == "workstation" and zone_id in ("level_0", "level_1"):
|
|
findings.append({"zone": zone_id, "host": host.get("ip"), "issue": "Workstation in control/process zone", "severity": "high"})
|
|
if host.get("internet_access") and zone_id in ("level_0", "level_1", "level_2"):
|
|
findings.append({"zone": zone_id, "host": host.get("ip"), "issue": "Internet access from OT zone", "severity": "critical"})
|
|
return findings
|
|
|
|
|
|
def generate_report(fw_findings, ot_exposure, zone_findings, zone_config):
|
|
all_findings = fw_findings + zone_findings
|
|
critical = sum(1 for f in all_findings if f.get("severity") == "critical")
|
|
report = {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"framework": "IEC 62443 / Purdue Model",
|
|
"zones_defined": len(zone_config) if zone_config else 0,
|
|
"firewall_findings": fw_findings,
|
|
"ot_protocol_exposure": ot_exposure,
|
|
"zone_compliance_findings": zone_findings,
|
|
"total_findings": len(all_findings),
|
|
"critical_findings": critical,
|
|
}
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="OT Network Segmentation Audit Agent")
|
|
parser.add_argument("--firewall-config", help="JSON firewall rules config file")
|
|
parser.add_argument("--zone-config", help="JSON zone configuration file")
|
|
parser.add_argument("--scan-subnet", help="OT subnet to scan for protocol exposure")
|
|
parser.add_argument("--output", default="ot_segmentation_report.json")
|
|
args = parser.parse_args()
|
|
|
|
fw_findings = check_firewall_rules(args.firewall_config) if args.firewall_config else []
|
|
ot_exposure = check_ot_protocol_exposure(args.scan_subnet) if args.scan_subnet else []
|
|
zone_config = {}
|
|
zone_findings = []
|
|
if args.zone_config:
|
|
with open(args.zone_config) as f:
|
|
zone_config = json.load(f)
|
|
zone_findings = audit_zone_compliance(zone_config)
|
|
report = generate_report(fw_findings, ot_exposure, zone_findings, zone_config)
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
logger.info("OT segmentation: %d findings (%d critical), %d exposed OT ports",
|
|
report["total_findings"], report["critical_findings"], len(ot_exposure))
|
|
print(json.dumps(report, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|