mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 06:34:57 +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.3 KiB
Python
206 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Threat campaign correlation agent using MISP and STIX."""
|
|
|
|
import json
|
|
import sys
|
|
import urllib.request
|
|
import ssl
|
|
from collections import Counter
|
|
from datetime import datetime
|
|
|
|
|
|
class MISPClient:
|
|
"""Client for MISP REST API for campaign correlation."""
|
|
|
|
def __init__(self, url, api_key, verify_ssl=False):
|
|
self.base_url = url.rstrip("/")
|
|
self.headers = {
|
|
"Authorization": api_key,
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
}
|
|
self.ctx = ssl.create_default_context()
|
|
if not verify_ssl:
|
|
self.ctx.check_hostname = False
|
|
self.ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
def _request(self, method, path, data=None):
|
|
url = f"{self.base_url}{path}"
|
|
body = json.dumps(data).encode() if data else None
|
|
req = urllib.request.Request(url, data=body, headers=self.headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, context=self.ctx, timeout=60) as resp:
|
|
return json.loads(resp.read().decode())
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
def search_attributes(self, attr_type, value):
|
|
"""Search MISP for attributes matching type and value."""
|
|
data = {"type": attr_type, "value": value, "searchall": 1}
|
|
return self._request("POST", "/attributes/restSearch", data)
|
|
|
|
def search_events(self, tags=None, date_from=None, date_to=None):
|
|
"""Search MISP events with filters."""
|
|
data = {}
|
|
if tags:
|
|
data["tags"] = tags
|
|
if date_from:
|
|
data["from"] = date_from
|
|
if date_to:
|
|
data["to"] = date_to
|
|
return self._request("POST", "/events/restSearch", data)
|
|
|
|
def get_event(self, event_id):
|
|
"""Get full event details by ID."""
|
|
return self._request("GET", f"/events/view/{event_id}")
|
|
|
|
def get_correlations(self, event_id):
|
|
"""Retrieve correlation data for a MISP event."""
|
|
event = self.get_event(event_id)
|
|
if "error" in event:
|
|
return event
|
|
correlations = []
|
|
ev = event.get("Event", event)
|
|
for attr in ev.get("Attribute", []):
|
|
if attr.get("RelatedAttribute"):
|
|
for rel in attr["RelatedAttribute"]:
|
|
correlations.append({
|
|
"source_event": event_id,
|
|
"source_attr": attr.get("value"),
|
|
"source_type": attr.get("type"),
|
|
"related_event": rel.get("event_id"),
|
|
"related_value": rel.get("value"),
|
|
})
|
|
return correlations
|
|
|
|
|
|
def calculate_campaign_confidence(events):
|
|
"""Calculate campaign attribution confidence from correlated events."""
|
|
if not events or len(events) < 2:
|
|
return {"confidence": "LOW", "score": 0, "reason": "Insufficient events"}
|
|
|
|
all_ips = []
|
|
all_domains = []
|
|
all_hashes = []
|
|
all_tags = []
|
|
|
|
for event in events:
|
|
ev = event.get("Event", event)
|
|
for attr in ev.get("Attribute", []):
|
|
atype = attr.get("type", "")
|
|
val = attr.get("value", "")
|
|
if atype in ("ip-src", "ip-dst"):
|
|
all_ips.append(val)
|
|
elif atype in ("domain", "hostname"):
|
|
all_domains.append(val)
|
|
elif "hash" in atype or "md5" in atype or "sha" in atype:
|
|
all_hashes.append(val)
|
|
for tag in ev.get("Tag", []):
|
|
all_tags.append(tag.get("name", ""))
|
|
|
|
ip_counts = Counter(all_ips)
|
|
domain_counts = Counter(all_domains)
|
|
hash_counts = Counter(all_hashes)
|
|
|
|
shared_ips = sum(1 for c in ip_counts.values() if c > 1)
|
|
shared_domains = sum(1 for c in domain_counts.values() if c > 1)
|
|
shared_hashes = sum(1 for c in hash_counts.values() if c > 1)
|
|
|
|
num_events = len(events)
|
|
infra_score = min(40, (shared_ips + shared_domains) / max(num_events, 1) * 40)
|
|
capability_score = min(35, shared_hashes / max(num_events, 1) * 35)
|
|
tag_overlap = len(set(all_tags)) / max(len(all_tags), 1)
|
|
ttp_score = min(15, tag_overlap * 15)
|
|
|
|
total = infra_score + capability_score + ttp_score
|
|
if total >= 70:
|
|
confidence = "HIGH"
|
|
elif total >= 45:
|
|
confidence = "MEDIUM"
|
|
else:
|
|
confidence = "LOW"
|
|
|
|
return {
|
|
"confidence": confidence,
|
|
"score": round(total, 1),
|
|
"shared_infrastructure": {"ips": shared_ips, "domains": shared_domains},
|
|
"shared_capabilities": {"hashes": shared_hashes},
|
|
"events_analyzed": num_events,
|
|
}
|
|
|
|
|
|
def extract_campaign_iocs(events):
|
|
"""Extract shared IOCs across correlated events for blocking."""
|
|
ioc_events = {}
|
|
for event in events:
|
|
ev = event.get("Event", event)
|
|
eid = ev.get("id", "unknown")
|
|
for attr in ev.get("Attribute", []):
|
|
val = attr.get("value", "")
|
|
atype = attr.get("type", "")
|
|
key = f"{atype}:{val}"
|
|
if key not in ioc_events:
|
|
ioc_events[key] = []
|
|
ioc_events[key].append(eid)
|
|
|
|
shared = {k: v for k, v in ioc_events.items() if len(v) > 1}
|
|
return {
|
|
"total_unique_iocs": len(ioc_events),
|
|
"shared_iocs": len(shared),
|
|
"shared_indicators": [
|
|
{"type": k.split(":")[0], "value": ":".join(k.split(":")[1:]), "event_count": len(v)}
|
|
for k, v in sorted(shared.items(), key=lambda x: len(x[1]), reverse=True)
|
|
][:50],
|
|
}
|
|
|
|
|
|
def build_campaign_report(campaign_name, events, attribution=None):
|
|
"""Build a structured campaign intelligence report."""
|
|
confidence = calculate_campaign_confidence(events)
|
|
iocs = extract_campaign_iocs(events)
|
|
|
|
dates = []
|
|
targets = []
|
|
for event in events:
|
|
ev = event.get("Event", event)
|
|
if ev.get("date"):
|
|
dates.append(ev["date"])
|
|
info = ev.get("info", "")
|
|
if info:
|
|
targets.append(info)
|
|
|
|
return {
|
|
"campaign_name": campaign_name,
|
|
"report_date": datetime.utcnow().isoformat() + "Z",
|
|
"timeline": {"first_seen": min(dates) if dates else None, "last_seen": max(dates) if dates else None},
|
|
"attribution": attribution or "Unattributed",
|
|
"confidence": confidence,
|
|
"shared_indicators": iocs,
|
|
"events_correlated": len(events),
|
|
"target_summary": targets[:10],
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import os
|
|
misp_url = os.environ.get("MISP_URL", "https://misp.example.com")
|
|
misp_key = os.environ.get("MISP_KEY", "")
|
|
|
|
action = sys.argv[1] if len(sys.argv) > 1 else "help"
|
|
if action == "search" and len(sys.argv) > 3:
|
|
client = MISPClient(misp_url, misp_key)
|
|
result = client.search_attributes(sys.argv[2], sys.argv[3])
|
|
print(json.dumps(result, indent=2, default=str))
|
|
elif action == "correlations" and len(sys.argv) > 2:
|
|
client = MISPClient(misp_url, misp_key)
|
|
result = client.get_correlations(sys.argv[2])
|
|
print(json.dumps(result, indent=2, default=str))
|
|
elif action == "events":
|
|
client = MISPClient(misp_url, misp_key)
|
|
tags = sys.argv[2] if len(sys.argv) > 2 else None
|
|
result = client.search_events(tags=tags)
|
|
print(json.dumps(result, indent=2, default=str))
|
|
else:
|
|
print("Usage: agent.py [search <type> <value>|correlations <event_id>|events [tag]]")
|
|
print("Env: MISP_URL, MISP_KEY")
|