mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-30 07:56:52 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,68 @@
|
||||
# API Reference: Detecting DNP3 Protocol Anomalies
|
||||
|
||||
## DNP3 Function Codes
|
||||
|
||||
| Code | Name | Risk Level |
|
||||
|------|------|------------|
|
||||
| 0x01 | READ | Normal |
|
||||
| 0x02 | WRITE | Caution |
|
||||
| 0x03 | SELECT | Caution |
|
||||
| 0x04 | OPERATE | Critical |
|
||||
| 0x05 | DIRECT_OPERATE | Critical |
|
||||
| 0x0D | COLD_RESTART | Critical |
|
||||
| 0x0E | WARM_RESTART | Critical |
|
||||
| 0x10 | INITIALIZE_APPLICATION | Critical |
|
||||
| 0x12 | STOP_APPLICATION | Critical |
|
||||
|
||||
## Zeek DNP3 Log Fields
|
||||
|
||||
```
|
||||
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fc_request fc_reply
|
||||
```
|
||||
|
||||
## Zeek DNP3 Protocol Analyzer
|
||||
|
||||
```bash
|
||||
# Enable DNP3 analyzer in Zeek
|
||||
zeek -C -r capture.pcap protocols/dnp3
|
||||
|
||||
# Output: dnp3.log with function codes, objects, IIN bits
|
||||
```
|
||||
|
||||
## Suricata DNP3 Rules
|
||||
|
||||
```
|
||||
alert dnp3 any any -> any 20000 (msg:"DNP3 Cold Restart"; \
|
||||
dnp3_func:cold_restart; sid:1000001; rev:1;)
|
||||
|
||||
alert dnp3 any any -> any 20000 (msg:"DNP3 Direct Operate"; \
|
||||
dnp3_func:direct_operate; sid:1000002; rev:1;)
|
||||
```
|
||||
|
||||
## Scapy DNP3 Parsing
|
||||
|
||||
```python
|
||||
from scapy.all import rdpcap
|
||||
from scapy.contrib.dnp3 import DNP3
|
||||
|
||||
packets = rdpcap("dnp3_capture.pcap")
|
||||
for pkt in packets:
|
||||
if pkt.haslayer(DNP3):
|
||||
print(pkt[DNP3].func_code)
|
||||
```
|
||||
|
||||
## ICS-CERT Detection Indicators
|
||||
|
||||
| Anomaly | Detection Method |
|
||||
|---------|-----------------|
|
||||
| Unauthorized master | Source IP not in allowed list |
|
||||
| Burst traffic | >10 events/sec from single source |
|
||||
| Off-hours commands | Control operations outside maintenance windows |
|
||||
| Unknown function codes | Function codes not in normal baseline |
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python agent.py --zeek-log dnp3.log
|
||||
python agent.py --zeek-log dnp3.log --authorized-masters masters.txt
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DNP3 protocol anomaly detection agent for ICS/SCADA environments.
|
||||
|
||||
Analyzes DNP3 network traffic captures (via Zeek or pcap) to detect
|
||||
unauthorized control commands, protocol violations, and traffic anomalies.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
DNP3_FUNCTION_CODES = {
|
||||
0x01: ("READ", "normal"), 0x02: ("WRITE", "caution"),
|
||||
0x03: ("SELECT", "caution"), 0x04: ("OPERATE", "critical"),
|
||||
0x05: ("DIRECT_OPERATE", "critical"), 0x06: ("DIRECT_OPERATE_NR", "critical"),
|
||||
0x0D: ("COLD_RESTART", "critical"), 0x0E: ("WARM_RESTART", "critical"),
|
||||
0x0F: ("INITIALIZE_DATA", "critical"), 0x10: ("INITIALIZE_APPLICATION", "critical"),
|
||||
0x11: ("START_APPLICATION", "critical"), 0x12: ("STOP_APPLICATION", "critical"),
|
||||
0x14: ("ENABLE_UNSOLICITED", "caution"), 0x15: ("DISABLE_UNSOLICITED", "caution"),
|
||||
0x18: ("RECORD_CURRENT_TIME", "normal"),
|
||||
0x81: ("RESPONSE", "normal"), 0x82: ("UNSOLICITED_RESPONSE", "normal"),
|
||||
}
|
||||
|
||||
AUTHORIZED_MASTERS = set()
|
||||
AUTHORIZED_OUTSTATIONS = set()
|
||||
|
||||
|
||||
def load_authorized_hosts(filepath):
|
||||
hosts = set()
|
||||
if filepath:
|
||||
with open(filepath, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#"):
|
||||
hosts.add(line)
|
||||
return hosts
|
||||
|
||||
|
||||
def parse_zeek_dnp3_log(filepath):
|
||||
events = []
|
||||
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):
|
||||
evt = dict(zip(headers, fields))
|
||||
events.append(evt)
|
||||
return events
|
||||
|
||||
|
||||
def analyze_dnp3_traffic(events, authorized_masters, authorized_outstations):
|
||||
findings = []
|
||||
fc_counter = Counter()
|
||||
src_dst_pairs = defaultdict(int)
|
||||
critical_ops = []
|
||||
|
||||
for evt in events:
|
||||
src = evt.get("id.orig_h", evt.get("src_ip", ""))
|
||||
dst = evt.get("id.resp_h", evt.get("dst_ip", ""))
|
||||
fc_str = evt.get("fc_request", evt.get("function_code", ""))
|
||||
|
||||
try:
|
||||
fc = int(fc_str, 16) if fc_str.startswith("0x") else int(fc_str)
|
||||
except (ValueError, TypeError):
|
||||
fc = -1
|
||||
|
||||
fc_info = DNP3_FUNCTION_CODES.get(fc, ("UNKNOWN", "caution"))
|
||||
fc_counter[fc_info[0]] += 1
|
||||
src_dst_pairs[f"{src}->{dst}"] += 1
|
||||
|
||||
if authorized_masters and src not in authorized_masters:
|
||||
findings.append({
|
||||
"type": "unauthorized_master",
|
||||
"source": src, "destination": dst,
|
||||
"function_code": fc_info[0],
|
||||
"severity": "CRITICAL",
|
||||
"description": f"DNP3 command from unauthorized master {src}",
|
||||
})
|
||||
|
||||
if fc_info[1] == "critical":
|
||||
critical_ops.append({
|
||||
"timestamp": evt.get("ts", ""),
|
||||
"source": src, "destination": dst,
|
||||
"function_code": fc_info[0],
|
||||
"severity": "HIGH",
|
||||
})
|
||||
findings.append({
|
||||
"type": "critical_control_command",
|
||||
"source": src, "destination": dst,
|
||||
"function_code": fc_info[0],
|
||||
"severity": "HIGH",
|
||||
"description": f"Critical DNP3 operation: {fc_info[0]}",
|
||||
})
|
||||
|
||||
return {
|
||||
"total_events": len(events),
|
||||
"function_code_distribution": dict(fc_counter),
|
||||
"communication_pairs": dict(src_dst_pairs),
|
||||
"critical_operations": critical_ops,
|
||||
"findings": findings,
|
||||
}
|
||||
|
||||
|
||||
def detect_protocol_anomalies(events):
|
||||
anomalies = []
|
||||
burst_window = defaultdict(list)
|
||||
|
||||
for evt in events:
|
||||
ts = evt.get("ts", "0")
|
||||
src = evt.get("id.orig_h", "")
|
||||
try:
|
||||
timestamp = float(ts)
|
||||
except ValueError:
|
||||
continue
|
||||
burst_window[src].append(timestamp)
|
||||
|
||||
for src, timestamps in burst_window.items():
|
||||
timestamps.sort()
|
||||
for i in range(len(timestamps) - 10):
|
||||
window = timestamps[i + 10] - timestamps[i]
|
||||
if window < 1.0:
|
||||
anomalies.append({
|
||||
"type": "burst_traffic",
|
||||
"source": src,
|
||||
"events_per_second": round(10 / max(window, 0.001), 1),
|
||||
"severity": "HIGH",
|
||||
"description": f"DNP3 traffic burst from {src}: >10 events/sec",
|
||||
})
|
||||
break
|
||||
return anomalies
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="DNP3 Protocol Anomaly Detector")
|
||||
parser.add_argument("--zeek-log", required=True, help="Zeek DNP3 log file")
|
||||
parser.add_argument("--authorized-masters", help="File with authorized master IPs")
|
||||
parser.add_argument("--authorized-outstations", help="File with authorized outstation IPs")
|
||||
args = parser.parse_args()
|
||||
|
||||
masters = load_authorized_hosts(args.authorized_masters)
|
||||
outstations = load_authorized_hosts(args.authorized_outstations)
|
||||
events = parse_zeek_dnp3_log(args.zeek_log)
|
||||
traffic_analysis = analyze_dnp3_traffic(events, masters, outstations)
|
||||
anomalies = detect_protocol_anomalies(events)
|
||||
|
||||
results = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
**traffic_analysis,
|
||||
"protocol_anomalies": anomalies,
|
||||
"total_findings": len(traffic_analysis["findings"]) + len(anomalies),
|
||||
}
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user