mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44: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
211 lines
7.0 KiB
Python
211 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""SCADA system attack detection agent."""
|
|
|
|
import json
|
|
import sys
|
|
import argparse
|
|
import socket
|
|
from datetime import datetime
|
|
|
|
try:
|
|
from pymodbus.client import ModbusTcpClient
|
|
except ImportError:
|
|
ModbusTcpClient = None
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
print("Install: pip install requests")
|
|
sys.exit(1)
|
|
|
|
|
|
SCADA_PORTS = {
|
|
502: ("Modbus TCP", "CRITICAL"),
|
|
102: ("Siemens S7comm", "CRITICAL"),
|
|
44818: ("EtherNet/IP CIP", "CRITICAL"),
|
|
20000: ("DNP3", "CRITICAL"),
|
|
4840: ("OPC-UA", "HIGH"),
|
|
47808: ("BACnet", "HIGH"),
|
|
2222: ("EtherNet/IP implicit", "HIGH"),
|
|
1089: ("Foundation Fieldbus HSE", "MEDIUM"),
|
|
34962: ("PROFINET RT", "HIGH"),
|
|
}
|
|
|
|
|
|
def scan_scada_services(host):
|
|
"""Scan for exposed SCADA protocol ports."""
|
|
results = []
|
|
for port, (proto, severity) in SCADA_PORTS.items():
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(3)
|
|
if sock.connect_ex((host, port)) == 0:
|
|
results.append({
|
|
"host": host, "port": port, "protocol": proto,
|
|
"accessible": True, "severity": severity,
|
|
"finding": f"{proto} service exposed on port {port}",
|
|
})
|
|
sock.close()
|
|
except socket.error:
|
|
pass
|
|
return results
|
|
|
|
|
|
def detect_modbus_anomalies(host, port=502, unit_id=1):
|
|
"""Detect Modbus protocol anomalies indicating attack."""
|
|
if ModbusTcpClient is None:
|
|
return {"error": "Install pymodbus: pip install pymodbus"}
|
|
|
|
client = ModbusTcpClient(host, port=port, timeout=10)
|
|
findings = []
|
|
try:
|
|
if not client.connect():
|
|
return {"error": "Connection failed"}
|
|
|
|
rr = client.read_holding_registers(0, count=10, slave=unit_id)
|
|
if not rr.isError():
|
|
findings.append({
|
|
"check": "Read holding registers",
|
|
"status": "accessible",
|
|
"severity": "HIGH" if unit_id == 0 else "MEDIUM",
|
|
"detail": f"Registers 0-9 readable: {rr.registers}",
|
|
})
|
|
|
|
for test_unit in [0, 255]:
|
|
rr = client.read_holding_registers(0, count=1, slave=test_unit)
|
|
if not rr.isError():
|
|
findings.append({
|
|
"check": f"Broadcast unit ID {test_unit}",
|
|
"status": "accessible",
|
|
"severity": "CRITICAL",
|
|
"detail": f"Unit ID {test_unit} responds — broadcast address accessible",
|
|
})
|
|
|
|
rr = client.read_coils(0, count=100, slave=unit_id)
|
|
if not rr.isError():
|
|
findings.append({
|
|
"check": "Bulk coil read",
|
|
"status": "accessible",
|
|
"severity": "MEDIUM",
|
|
"detail": f"100 coils readable from address 0",
|
|
})
|
|
|
|
except Exception as e:
|
|
findings.append({"check": "error", "detail": str(e)})
|
|
finally:
|
|
client.close()
|
|
|
|
return {"host": host, "findings": findings}
|
|
|
|
|
|
def detect_s7comm_access(host, port=102):
|
|
"""Test Siemens S7comm accessibility (basic connection test)."""
|
|
result = {"host": host, "port": port, "protocol": "S7comm"}
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(5)
|
|
sock.connect((host, port))
|
|
cotp_cr = bytes([
|
|
0x03, 0x00, 0x00, 0x16,
|
|
0x11, 0xe0, 0x00, 0x00,
|
|
0x00, 0x01, 0x00, 0xc0,
|
|
0x01, 0x0a, 0xc1, 0x02,
|
|
0x01, 0x00, 0xc2, 0x02,
|
|
0x01, 0x02,
|
|
])
|
|
sock.send(cotp_cr)
|
|
resp = sock.recv(1024)
|
|
sock.close()
|
|
if len(resp) > 0:
|
|
result["accessible"] = True
|
|
result["finding"] = "S7comm COTP connection accepted — PLC accessible"
|
|
result["severity"] = "CRITICAL"
|
|
else:
|
|
result["accessible"] = False
|
|
except Exception as e:
|
|
result["accessible"] = False
|
|
result["error"] = str(e)
|
|
return result
|
|
|
|
|
|
def query_scada_siem(siem_url, api_key, hours=24):
|
|
"""Query SIEM for SCADA-related security events."""
|
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
try:
|
|
resp = requests.get(f"{siem_url}/api/v1/events", headers=headers,
|
|
params={"category": "scada", "hours": hours}, timeout=15)
|
|
resp.raise_for_status()
|
|
events = resp.json().get("events", [])
|
|
findings = []
|
|
for evt in events:
|
|
if evt.get("severity", 0) >= 7:
|
|
findings.append({
|
|
"event_id": evt.get("id", ""),
|
|
"source": evt.get("source_ip", ""),
|
|
"target": evt.get("dest_ip", ""),
|
|
"description": evt.get("description", ""),
|
|
"severity": "CRITICAL" if evt["severity"] >= 9 else "HIGH",
|
|
})
|
|
return findings
|
|
except Exception as e:
|
|
return [{"error": str(e)}]
|
|
|
|
|
|
def run_audit(args):
|
|
"""Execute SCADA attack detection audit."""
|
|
print(f"\n{'='*60}")
|
|
print(f" SCADA SYSTEM ATTACK DETECTION")
|
|
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
|
print(f"{'='*60}\n")
|
|
|
|
report = {}
|
|
|
|
if args.host:
|
|
services = scan_scada_services(args.host)
|
|
report["scada_services"] = services
|
|
print(f"--- SCADA SERVICE SCAN ({args.host}) ---")
|
|
if services:
|
|
for s in services:
|
|
print(f" [{s['severity']}] {s['protocol']} on port {s['port']}")
|
|
else:
|
|
print(" No SCADA ports detected (good segmentation)")
|
|
|
|
if args.modbus_host:
|
|
modbus = detect_modbus_anomalies(args.modbus_host, args.modbus_port or 502)
|
|
report["modbus_audit"] = modbus
|
|
print(f"\n--- MODBUS ANOMALY DETECTION ---")
|
|
for f in modbus.get("findings", []):
|
|
print(f" [{f.get('severity','')}] {f['check']}: {f.get('detail','')[:80]}")
|
|
|
|
if args.s7_host:
|
|
s7 = detect_s7comm_access(args.s7_host)
|
|
report["s7comm_check"] = s7
|
|
print(f"\n--- S7COMM ACCESS CHECK ---")
|
|
print(f" Accessible: {s7.get('accessible', False)}")
|
|
if s7.get("finding"):
|
|
print(f" [{s7['severity']}] {s7['finding']}")
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="SCADA Attack Detection Agent")
|
|
parser.add_argument("--host", help="SCADA host to scan for services")
|
|
parser.add_argument("--modbus-host", help="Modbus device to audit")
|
|
parser.add_argument("--modbus-port", type=int, default=502)
|
|
parser.add_argument("--s7-host", help="Siemens S7 PLC to check")
|
|
parser.add_argument("--siem-url", help="SIEM API URL for SCADA events")
|
|
parser.add_argument("--siem-key", help="SIEM API key")
|
|
parser.add_argument("--output", help="Save report to JSON file")
|
|
args = parser.parse_args()
|
|
|
|
report = run_audit(args)
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|