mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
c21af3347e
- Add scripts/agent.py and references/api-reference.md to all remaining skills - Update all 648 LICENSE files: copyright now reads 'Mahipal' - Add implementing-security-monitoring-with-datadog (new skill with full anatomy) - All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
157 lines
6.4 KiB
Python
157 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for performing OT network security assessment based on Purdue model."""
|
|
|
|
import json
|
|
import argparse
|
|
import csv
|
|
import subprocess
|
|
from datetime import datetime
|
|
from collections import Counter
|
|
|
|
|
|
PURDUE_LEVELS = {
|
|
0: "Physical Process (sensors, actuators)",
|
|
1: "Basic Control (PLC, RTU, SIS)",
|
|
2: "Area Supervisory (HMI, SCADA, DCS)",
|
|
3: "Site Operations (Historian, Patch Mgmt)",
|
|
3.5: "IT/OT DMZ (jump servers, data diode)",
|
|
4: "Business Planning (ERP, Email)",
|
|
5: "Enterprise Network (Internet, Cloud)",
|
|
}
|
|
|
|
|
|
def assess_asset_inventory(csv_file):
|
|
"""Assess OT asset inventory against Purdue model zones."""
|
|
with open(csv_file, "r", encoding="utf-8", errors="replace") as f:
|
|
reader = csv.DictReader(f)
|
|
assets = list(reader)
|
|
by_level = {}
|
|
by_vendor = Counter()
|
|
by_protocol = Counter()
|
|
findings = []
|
|
for asset in assets:
|
|
level = asset.get("purdue_level", asset.get("zone", "unknown"))
|
|
by_level.setdefault(str(level), []).append(asset)
|
|
by_vendor[asset.get("vendor", "unknown")] += 1
|
|
proto = asset.get("protocol", asset.get("protocols", ""))
|
|
for p in proto.split(","):
|
|
if p.strip():
|
|
by_protocol[p.strip()] += 1
|
|
firmware = asset.get("firmware_version", "")
|
|
if asset.get("end_of_life", "").lower() in ("yes", "true"):
|
|
findings.append({"asset": asset.get("name", ""), "issue": "END_OF_LIFE", "severity": "HIGH"})
|
|
if not firmware:
|
|
findings.append({"asset": asset.get("name", ""), "issue": "UNKNOWN_FIRMWARE", "severity": "MEDIUM"})
|
|
return {
|
|
"total_assets": len(assets),
|
|
"by_purdue_level": {k: len(v) for k, v in by_level.items()},
|
|
"by_vendor": dict(by_vendor.most_common(10)),
|
|
"protocols": dict(by_protocol.most_common(15)),
|
|
"findings": findings[:20],
|
|
}
|
|
|
|
|
|
def assess_network_segmentation(csv_file):
|
|
"""Check OT network segmentation and firewall rules."""
|
|
with open(csv_file, "r", encoding="utf-8", errors="replace") as f:
|
|
reader = csv.DictReader(f)
|
|
rules = list(reader)
|
|
findings = []
|
|
for rule in rules:
|
|
src_zone = rule.get("src_zone", "")
|
|
dst_zone = rule.get("dst_zone", "")
|
|
action = rule.get("action", "").lower()
|
|
protocol = rule.get("protocol", "")
|
|
if action == "allow" and src_zone.startswith("IT") and dst_zone.startswith("OT"):
|
|
findings.append({
|
|
"rule": rule.get("name", rule.get("id", "")),
|
|
"issue": "DIRECT_IT_TO_OT_ACCESS",
|
|
"severity": "CRITICAL",
|
|
"src_zone": src_zone, "dst_zone": dst_zone,
|
|
})
|
|
if action == "allow" and protocol.lower() in ("any", "all", "*"):
|
|
findings.append({
|
|
"rule": rule.get("name", rule.get("id", "")),
|
|
"issue": "ALLOW_ANY_PROTOCOL",
|
|
"severity": "HIGH",
|
|
})
|
|
return {
|
|
"total_rules": len(rules),
|
|
"allow_rules": sum(1 for r in rules if r.get("action", "").lower() == "allow"),
|
|
"deny_rules": sum(1 for r in rules if r.get("action", "").lower() in ("deny", "drop")),
|
|
"findings": findings[:20],
|
|
"dmz_rules": sum(1 for r in rules if "dmz" in r.get("src_zone", "").lower() or "dmz" in r.get("dst_zone", "").lower()),
|
|
}
|
|
|
|
|
|
def scan_ot_protocols(target_subnet):
|
|
"""Scan for common OT protocols on a subnet using nmap."""
|
|
ot_ports = "102,502,2222,4840,20000,44818,47808"
|
|
cmd = ["nmap", "-sV", "-p", ot_ports, target_subnet, "-oX", "-", "--open"]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
import xml.etree.ElementTree as ET
|
|
root = ET.fromstring(result.stdout)
|
|
hosts = []
|
|
for host in root.findall(".//host"):
|
|
addr = host.find("address").get("addr", "") if host.find("address") is not None else ""
|
|
ports = []
|
|
for port in host.findall(".//port"):
|
|
service = port.find("service")
|
|
ports.append({
|
|
"port": int(port.get("portid", 0)),
|
|
"protocol": service.get("name", "") if service is not None else "",
|
|
"product": service.get("product", "") if service is not None else "",
|
|
})
|
|
if ports:
|
|
hosts.append({"ip": addr, "ot_services": ports})
|
|
protocol_map = {502: "Modbus", 102: "S7Comm", 44818: "EtherNet/IP", 4840: "OPC-UA", 47808: "BACnet", 20000: "DNP3"}
|
|
return {"subnet": target_subnet, "hosts_with_ot": len(hosts), "hosts": hosts, "protocol_reference": protocol_map}
|
|
except FileNotFoundError:
|
|
return {"error": "nmap not installed"}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
def generate_assessment_report(asset_csv, firewall_csv=None):
|
|
"""Generate comprehensive OT security assessment."""
|
|
report = {
|
|
"generated": datetime.utcnow().isoformat(),
|
|
"frameworks": ["IEC 62443", "NIST SP 800-82", "NERC CIP"],
|
|
"asset_assessment": assess_asset_inventory(asset_csv),
|
|
}
|
|
if firewall_csv:
|
|
report["segmentation"] = assess_network_segmentation(firewall_csv)
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="OT Network Security Assessment Agent")
|
|
sub = parser.add_subparsers(dest="command")
|
|
a = sub.add_parser("assets", help="Assess OT asset inventory")
|
|
a.add_argument("--csv", required=True)
|
|
s = sub.add_parser("segmentation", help="Assess network segmentation")
|
|
s.add_argument("--csv", required=True, help="Firewall rules CSV")
|
|
p = sub.add_parser("protocols", help="Scan for OT protocols")
|
|
p.add_argument("--subnet", required=True)
|
|
r = sub.add_parser("report", help="Full assessment report")
|
|
r.add_argument("--assets", required=True)
|
|
r.add_argument("--firewall", help="Firewall rules CSV")
|
|
args = parser.parse_args()
|
|
if args.command == "assets":
|
|
result = assess_asset_inventory(args.csv)
|
|
elif args.command == "segmentation":
|
|
result = assess_network_segmentation(args.csv)
|
|
elif args.command == "protocols":
|
|
result = scan_ot_protocols(args.subnet)
|
|
elif args.command == "report":
|
|
result = generate_assessment_report(args.assets, args.firewall)
|
|
else:
|
|
parser.print_help()
|
|
return
|
|
print(json.dumps(result, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|