mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-15 15:34: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
206 lines
7.6 KiB
Python
206 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Network traffic incident analysis agent using scapy and tshark for PCAP investigation."""
|
|
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import json
|
|
import statistics
|
|
from collections import defaultdict
|
|
|
|
try:
|
|
from scapy.all import rdpcap, IP, TCP, DNS
|
|
HAS_SCAPY = True
|
|
except ImportError:
|
|
HAS_SCAPY = False
|
|
|
|
|
|
def run_tshark(pcap_path, display_filter, fields):
|
|
"""Run tshark with a display filter and extract specific fields."""
|
|
cmd = ["tshark", "-r", pcap_path, "-Y", display_filter, "-T", "fields"]
|
|
for f in fields:
|
|
cmd += ["-e", f]
|
|
cmd += ["-E", "separator=|"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
rows = []
|
|
if result.returncode == 0:
|
|
for line in result.stdout.strip().splitlines():
|
|
parts = line.split("|")
|
|
if len(parts) == len(fields):
|
|
rows.append(dict(zip(fields, parts)))
|
|
return rows
|
|
|
|
|
|
def get_pcap_summary(pcap_path):
|
|
"""Get high-level PCAP statistics."""
|
|
cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
return result.stdout if result.returncode == 0 else ""
|
|
|
|
|
|
def detect_lateral_movement(pcap_path):
|
|
"""Detect potential lateral movement patterns (SMB, RDP, WinRM, SSH)."""
|
|
lateral_ports = {"445": "SMB", "3389": "RDP", "5985": "WinRM", "5986": "WinRM-S",
|
|
"22": "SSH", "135": "RPC", "139": "NetBIOS"}
|
|
connections = run_tshark(pcap_path, "tcp.flags.syn==1 && tcp.flags.ack==0",
|
|
["ip.src", "ip.dst", "tcp.dstport"])
|
|
lateral = []
|
|
for conn in connections:
|
|
port = conn.get("tcp.dstport", "")
|
|
if port in lateral_ports:
|
|
lateral.append({
|
|
"src": conn["ip.src"],
|
|
"dst": conn["ip.dst"],
|
|
"port": port,
|
|
"service": lateral_ports[port],
|
|
})
|
|
return lateral
|
|
|
|
|
|
def detect_data_exfiltration(pcap_path, threshold_mb=10):
|
|
"""Detect potential data exfiltration based on outbound data volume."""
|
|
cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
suspects = []
|
|
if result.returncode == 0:
|
|
for line in result.stdout.splitlines():
|
|
parts = line.split()
|
|
if len(parts) >= 8 and "<->" in line:
|
|
try:
|
|
ip_a = parts[0]
|
|
ip_b = parts[2]
|
|
bytes_a_to_b = int(parts[4]) if parts[4].isdigit() else 0
|
|
bytes_b_to_a = int(parts[7]) if len(parts) > 7 and parts[7].isdigit() else 0
|
|
total_bytes = bytes_a_to_b + bytes_b_to_a
|
|
if total_bytes > threshold_mb * 1024 * 1024:
|
|
suspects.append({
|
|
"ip_a": ip_a,
|
|
"ip_b": ip_b,
|
|
"bytes_a_to_b": bytes_a_to_b,
|
|
"bytes_b_to_a": bytes_b_to_a,
|
|
"total_mb": round(total_bytes / (1024 * 1024), 2),
|
|
})
|
|
except (ValueError, IndexError):
|
|
continue
|
|
return suspects
|
|
|
|
|
|
def detect_beaconing(pcap_path, min_conns=10):
|
|
"""Detect periodic beaconing patterns from TCP connections."""
|
|
if not HAS_SCAPY:
|
|
return []
|
|
packets = rdpcap(pcap_path)
|
|
conn_times = defaultdict(list)
|
|
for pkt in packets:
|
|
if IP in pkt and TCP in pkt and (pkt[TCP].flags & 0x02):
|
|
key = f"{pkt[IP].src}->{pkt[IP].dst}:{pkt[TCP].dport}"
|
|
conn_times[key].append(float(pkt.time))
|
|
beacons = []
|
|
for key, times in conn_times.items():
|
|
if len(times) < min_conns:
|
|
continue
|
|
intervals = [times[i+1] - times[i] for i in range(len(times)-1)]
|
|
avg = statistics.mean(intervals)
|
|
std = statistics.stdev(intervals) if len(intervals) > 1 else 0
|
|
jitter = (std / avg * 100) if avg > 0 else 0
|
|
if 5 < avg < 7200 and jitter < 30:
|
|
beacons.append({
|
|
"flow": key,
|
|
"connections": len(times),
|
|
"avg_interval": round(avg, 1),
|
|
"jitter_pct": round(jitter, 1),
|
|
})
|
|
return beacons
|
|
|
|
|
|
def extract_dns_queries(pcap_path):
|
|
"""Extract DNS queries and identify suspicious patterns."""
|
|
queries = run_tshark(pcap_path, "dns.qr==0",
|
|
["ip.src", "dns.qry.name", "dns.qry.type"])
|
|
return queries
|
|
|
|
|
|
def detect_ids_alerts(pcap_path):
|
|
"""Run Suricata on the PCAP and extract alerts."""
|
|
import tempfile
|
|
suricata_output = os.environ.get("SURICATA_OUTPUT_DIR", os.path.join(tempfile.gettempdir(), "suricata_output"))
|
|
os.makedirs(suricata_output, exist_ok=True)
|
|
cmd = ["suricata", "-r", pcap_path, "-l", suricata_output, "-k", "none"]
|
|
subprocess.run(cmd, capture_output=True, timeout=120)
|
|
alerts = []
|
|
alert_file = os.path.join(suricata_output, "fast.log")
|
|
if os.path.exists(alert_file):
|
|
with open(alert_file, "r") as f:
|
|
for line in f:
|
|
alerts.append(line.strip())
|
|
return alerts
|
|
|
|
|
|
def extract_http_objects(pcap_path, output_dir):
|
|
"""Extract HTTP objects (files) from the PCAP."""
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
cmd = ["tshark", "-r", pcap_path, "--export-objects", f"http,{output_dir}"]
|
|
subprocess.run(cmd, capture_output=True, timeout=60)
|
|
exported = []
|
|
if os.path.exists(output_dir):
|
|
for f in os.listdir(output_dir):
|
|
filepath = os.path.join(output_dir, f)
|
|
exported.append({"filename": f, "size": os.path.getsize(filepath)})
|
|
return exported
|
|
|
|
|
|
def generate_incident_report(pcap_path, beacons, lateral, exfil, dns_queries):
|
|
"""Generate a network incident analysis report."""
|
|
report = {
|
|
"pcap": pcap_path,
|
|
"pcap_size_mb": round(os.path.getsize(pcap_path) / (1024*1024), 1),
|
|
"findings": {
|
|
"beacons_detected": len(beacons),
|
|
"lateral_movement_flows": len(lateral),
|
|
"exfiltration_suspects": len(exfil),
|
|
"dns_queries": len(dns_queries),
|
|
},
|
|
"beacons": beacons,
|
|
"lateral_movement": lateral[:10],
|
|
"exfiltration": exfil,
|
|
}
|
|
return report
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("Network Traffic Incident Analysis Agent")
|
|
print("Beaconing, lateral movement, exfiltration detection")
|
|
print("=" * 60)
|
|
|
|
pcap = sys.argv[1] if len(sys.argv) > 1 else None
|
|
|
|
if pcap and os.path.exists(pcap):
|
|
print(f"\n[*] Analyzing: {pcap}")
|
|
print(f"[*] Size: {os.path.getsize(pcap)/(1024*1024):.1f} MB")
|
|
|
|
print("\n--- Beacon Detection ---")
|
|
beacons = detect_beaconing(pcap)
|
|
for b in beacons:
|
|
print(f" [!] {b['flow']}: interval={b['avg_interval']}s "
|
|
f"jitter={b['jitter_pct']}% ({b['connections']} conns)")
|
|
|
|
print("\n--- Lateral Movement Detection ---")
|
|
lateral = detect_lateral_movement(pcap)
|
|
for l in lateral[:10]:
|
|
print(f" [!] {l['src']} -> {l['dst']}:{l['port']} ({l['service']})")
|
|
|
|
print("\n--- Data Exfiltration Detection ---")
|
|
exfil = detect_data_exfiltration(pcap, threshold_mb=5)
|
|
for e in exfil:
|
|
print(f" [!] {e['ip_a']} <-> {e['ip_b']}: {e['total_mb']} MB")
|
|
|
|
print("\n--- DNS Queries ---")
|
|
dns = extract_dns_queries(pcap)
|
|
print(f" Total queries: {len(dns)}")
|
|
|
|
report = generate_incident_report(pcap, beacons, lateral, exfil, dns)
|
|
print(f"\n[*] Report summary: {json.dumps(report['findings'], indent=2)}")
|
|
else:
|
|
print(f"\n[DEMO] Usage: python agent.py <capture.pcap>")
|