mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
220 lines
7.9 KiB
Python
220 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""MISP Threat Intelligence Sharing agent - creates events, manages attributes, searches IOCs, and validates sharing configuration via PyMISP"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from collections import Counter, defaultdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from pymisp import PyMISP, MISPEvent, MISPAttribute, MISPTag
|
|
HAS_PYMISP = True
|
|
except ImportError:
|
|
HAS_PYMISP = False
|
|
|
|
MISP_ATTRIBUTE_TYPES = {
|
|
"ip-dst", "ip-src", "domain", "hostname", "url", "md5", "sha1",
|
|
"sha256", "filename", "email-src", "email-dst", "mutex", "regkey",
|
|
"user-agent", "vulnerability", "link", "text", "comment",
|
|
}
|
|
|
|
TLP_TAGS = {
|
|
"white": "tlp:white",
|
|
"green": "tlp:green",
|
|
"amber": "tlp:amber",
|
|
"amber+strict": "tlp:amber+strict",
|
|
"red": "tlp:red",
|
|
}
|
|
|
|
|
|
def load_data(path):
|
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
|
|
|
|
def connect_misp(url, api_key, ssl=True):
|
|
"""Initialize PyMISP connection."""
|
|
if not HAS_PYMISP:
|
|
return None, "pymisp not installed (pip install pymisp)"
|
|
misp = PyMISP(url, api_key, ssl=ssl)
|
|
return misp, "connected"
|
|
|
|
|
|
def create_event_from_data(misp, event_data):
|
|
"""Create a MISP event with attributes and tags."""
|
|
event = MISPEvent()
|
|
event.info = event_data.get("info", "Untitled Event")
|
|
event.distribution = event_data.get("distribution", 1) # 0=org, 1=community, 2=connected, 3=all
|
|
event.threat_level_id = event_data.get("threat_level", 2) # 1=high, 2=medium, 3=low, 4=undefined
|
|
event.analysis = event_data.get("analysis", 0) # 0=initial, 1=ongoing, 2=complete
|
|
for attr in event_data.get("attributes", []):
|
|
attr_type = attr.get("type", "text")
|
|
value = attr.get("value", "")
|
|
category = attr.get("category", "")
|
|
to_ids = attr.get("to_ids", True)
|
|
comment = attr.get("comment", "")
|
|
if attr_type in MISP_ATTRIBUTE_TYPES and value:
|
|
event.add_attribute(type=attr_type, value=value, category=category,
|
|
to_ids=to_ids, comment=comment)
|
|
for tag_name in event_data.get("tags", []):
|
|
event.add_tag(tag_name)
|
|
tlp = event_data.get("tlp", "").lower()
|
|
if tlp in TLP_TAGS:
|
|
event.add_tag(TLP_TAGS[tlp])
|
|
if misp:
|
|
result = misp.add_event(event)
|
|
return result
|
|
return event.to_dict()
|
|
|
|
|
|
def validate_event_quality(event_data):
|
|
"""Validate event data quality for sharing readiness."""
|
|
findings = []
|
|
eid = event_data.get("id", event_data.get("info", "unknown"))
|
|
if not event_data.get("info"):
|
|
findings.append({
|
|
"type": "missing_event_info",
|
|
"severity": "high",
|
|
"resource": str(eid),
|
|
"detail": "Event lacks descriptive info/title",
|
|
})
|
|
attrs = event_data.get("attributes", event_data.get("Attribute", []))
|
|
if not attrs:
|
|
findings.append({
|
|
"type": "no_attributes",
|
|
"severity": "high",
|
|
"resource": str(eid),
|
|
"detail": "Event has no IOC attributes",
|
|
})
|
|
attr_types = Counter(a.get("type", "unknown") for a in attrs)
|
|
if len(attr_types) == 1 and len(attrs) > 1:
|
|
findings.append({
|
|
"type": "single_attribute_type",
|
|
"severity": "low",
|
|
"resource": str(eid),
|
|
"detail": f"All {len(attrs)} attributes are type '{list(attr_types.keys())[0]}' - consider enriching",
|
|
})
|
|
tags = event_data.get("tags", event_data.get("Tag", []))
|
|
tag_names = [t.get("name", t) if isinstance(t, dict) else t for t in tags]
|
|
has_tlp = any("tlp:" in t.lower() for t in tag_names)
|
|
if not has_tlp:
|
|
findings.append({
|
|
"type": "missing_tlp_tag",
|
|
"severity": "high",
|
|
"resource": str(eid),
|
|
"detail": "Event lacks TLP classification tag",
|
|
})
|
|
has_mitre = any("mitre-attack" in t.lower() or "attack-pattern" in t.lower() for t in tag_names)
|
|
if not has_mitre and len(attrs) > 0:
|
|
findings.append({
|
|
"type": "missing_mitre_mapping",
|
|
"severity": "medium",
|
|
"resource": str(eid),
|
|
"detail": "Event lacks MITRE ATT&CK technique mapping",
|
|
})
|
|
dist = event_data.get("distribution", -1)
|
|
if dist == 3:
|
|
findings.append({
|
|
"type": "unrestricted_distribution",
|
|
"severity": "medium",
|
|
"resource": str(eid),
|
|
"detail": "Event set to 'All communities' distribution - verify this is intentional",
|
|
})
|
|
for attr in attrs:
|
|
val = attr.get("value", "")
|
|
atype = attr.get("type", "")
|
|
if atype in ("ip-dst", "ip-src") and val in ("127.0.0.1", "0.0.0.0", "10.0.0.1", "192.168.1.1"):
|
|
findings.append({
|
|
"type": "private_ip_ioc",
|
|
"severity": "high",
|
|
"resource": str(eid),
|
|
"detail": f"Private/localhost IP '{val}' used as IOC - will generate false positives",
|
|
})
|
|
if atype in ("md5", "sha1", "sha256") and len(val) < 32:
|
|
findings.append({
|
|
"type": "invalid_hash_length",
|
|
"severity": "high",
|
|
"resource": str(eid),
|
|
"detail": f"Hash attribute '{val}' is too short for type {atype}",
|
|
})
|
|
return findings
|
|
|
|
|
|
def validate_sharing_config(config):
|
|
"""Validate MISP sharing and feed configuration."""
|
|
findings = []
|
|
servers = config.get("sync_servers", [])
|
|
if not servers:
|
|
findings.append({
|
|
"type": "no_sync_servers",
|
|
"severity": "medium",
|
|
"resource": "misp_config",
|
|
"detail": "No synchronization servers configured for intelligence sharing",
|
|
})
|
|
for srv in servers:
|
|
if not srv.get("pull", False) and not srv.get("push", False):
|
|
findings.append({
|
|
"type": "inactive_sync_server",
|
|
"severity": "medium",
|
|
"resource": srv.get("name", srv.get("url", "")),
|
|
"detail": "Sync server has neither pull nor push enabled",
|
|
})
|
|
feeds = config.get("feeds", [])
|
|
enabled_feeds = [f for f in feeds if f.get("enabled", False)]
|
|
if not enabled_feeds:
|
|
findings.append({
|
|
"type": "no_active_feeds",
|
|
"severity": "medium",
|
|
"resource": "misp_config",
|
|
"detail": "No active threat intelligence feeds configured",
|
|
})
|
|
return findings
|
|
|
|
|
|
def analyze(data):
|
|
findings = []
|
|
events = data.get("events", [data] if "info" in data or "Attribute" in data else [])
|
|
if isinstance(data, list):
|
|
events = data
|
|
for evt in events:
|
|
findings.extend(validate_event_quality(evt))
|
|
if "sync_servers" in data or "feeds" in data:
|
|
findings.extend(validate_sharing_config(data))
|
|
return findings
|
|
|
|
|
|
def generate_report(input_path):
|
|
data = load_data(input_path)
|
|
findings = analyze(data)
|
|
sev = Counter(f["severity"] for f in findings)
|
|
cats = Counter(f["type"] for f in findings)
|
|
return {
|
|
"report": "misp_threat_intelligence_sharing",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"total_findings": len(findings),
|
|
"severity_summary": dict(sev),
|
|
"finding_categories": dict(cats),
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="MISP Threat Intelligence Sharing Agent")
|
|
ap.add_argument("--input", required=True, help="Input JSON with MISP events or config")
|
|
ap.add_argument("--output", help="Output JSON report path")
|
|
ap.add_argument("--misp-url", help="MISP instance URL for live operations")
|
|
ap.add_argument("--api-key", help="MISP API key")
|
|
args = ap.parse_args()
|
|
report = generate_report(args.input)
|
|
out = json.dumps(report, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(out, encoding="utf-8")
|
|
print(f"Report written to {args.output}")
|
|
else:
|
|
print(out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|