mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 22:54:53 +03:00
347 lines
12 KiB
Python
347 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MISP Threat Intelligence Collection Script
|
|
|
|
Automates IOC collection from MISP instance including:
|
|
- Feed management and scheduled fetching
|
|
- Event search and attribute extraction
|
|
- IOC export in multiple formats (STIX, CSV, Suricata)
|
|
- Warninglist filtering to reduce false positives
|
|
- Correlation summary generation
|
|
|
|
Requirements:
|
|
pip install pymisp requests stix2
|
|
|
|
Usage:
|
|
python process.py --url https://misp.local --key YOUR_API_KEY --action collect
|
|
python process.py --url https://misp.local --key YOUR_API_KEY --action export --format stix2
|
|
python process.py --url https://misp.local --key YOUR_API_KEY --action feeds --enable-defaults
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import csv
|
|
import sys
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from typing import Optional
|
|
|
|
try:
|
|
from pymisp import PyMISP, MISPEvent, MISPAttribute
|
|
except ImportError:
|
|
print("ERROR: pymisp not installed. Run: pip install pymisp")
|
|
sys.exit(1)
|
|
|
|
|
|
class MISPCollector:
|
|
"""Automated threat intelligence collector for MISP."""
|
|
|
|
def __init__(self, url: str, api_key: str, ssl_verify: bool = True):
|
|
self.misp = PyMISP(url, api_key, ssl=ssl_verify)
|
|
self.url = url
|
|
self.stats = {
|
|
"events_processed": 0,
|
|
"attributes_collected": 0,
|
|
"iocs_exported": 0,
|
|
"feeds_enabled": 0,
|
|
"warninglist_filtered": 0,
|
|
}
|
|
|
|
def enable_default_feeds(self) -> dict:
|
|
"""Enable and fetch default MISP community feeds."""
|
|
default_feeds = [
|
|
"CIRCL OSINT Feed",
|
|
"Botvrij.eu",
|
|
"abuse.ch URLhaus",
|
|
"The Botnet Channel",
|
|
"Phishtank online valid phishing",
|
|
]
|
|
|
|
feeds = self.misp.feeds()
|
|
enabled = []
|
|
|
|
for feed in feeds:
|
|
feed_name = feed.get("Feed", {}).get("name", "")
|
|
feed_id = feed.get("Feed", {}).get("id")
|
|
|
|
if any(default in feed_name for default in default_feeds):
|
|
try:
|
|
self.misp.enable_feed(feed_id)
|
|
self.misp.fetch_feed(feed_id)
|
|
enabled.append(feed_name)
|
|
self.stats["feeds_enabled"] += 1
|
|
print(f"[+] Enabled feed: {feed_name}")
|
|
except Exception as e:
|
|
print(f"[-] Failed to enable {feed_name}: {e}")
|
|
|
|
return {"enabled_feeds": enabled, "count": len(enabled)}
|
|
|
|
def add_custom_feed(self, name: str, url: str, provider: str,
|
|
source_format: str = "csv") -> dict:
|
|
"""Add a custom threat intelligence feed."""
|
|
feed_config = {
|
|
"name": name,
|
|
"provider": provider,
|
|
"url": url,
|
|
"source_format": source_format,
|
|
"input_source": "network",
|
|
"publish": False,
|
|
"enabled": True,
|
|
"distribution": 0,
|
|
"default": False,
|
|
"lookup_visible": True,
|
|
}
|
|
|
|
result = self.misp.add_feed(feed_config)
|
|
print(f"[+] Added custom feed: {name} from {provider}")
|
|
return result
|
|
|
|
def collect_recent_iocs(self, days: int = 7,
|
|
ioc_types: Optional[list] = None) -> list:
|
|
"""Collect IOCs from recent events."""
|
|
if ioc_types is None:
|
|
ioc_types = [
|
|
"ip-dst", "ip-src", "domain", "hostname",
|
|
"url", "md5", "sha1", "sha256", "email-src",
|
|
]
|
|
|
|
date_from = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
all_iocs = []
|
|
|
|
for ioc_type in ioc_types:
|
|
try:
|
|
results = self.misp.search(
|
|
controller="attributes",
|
|
type_attribute=ioc_type,
|
|
date_from=date_from,
|
|
to_ids=True,
|
|
pythonify=True,
|
|
)
|
|
|
|
for attr in results:
|
|
ioc_entry = {
|
|
"type": attr.type,
|
|
"value": attr.value,
|
|
"category": attr.category,
|
|
"event_id": attr.event_id,
|
|
"timestamp": str(attr.timestamp),
|
|
"to_ids": attr.to_ids,
|
|
"comment": attr.comment or "",
|
|
}
|
|
all_iocs.append(ioc_entry)
|
|
self.stats["attributes_collected"] += 1
|
|
|
|
print(f"[+] Collected {len(results)} {ioc_type} IOCs")
|
|
|
|
except Exception as e:
|
|
print(f"[-] Error collecting {ioc_type}: {e}")
|
|
|
|
return all_iocs
|
|
|
|
def collect_events_by_tag(self, tags: list, limit: int = 100) -> list:
|
|
"""Collect events matching specific tags."""
|
|
events = self.misp.search(
|
|
controller="events",
|
|
tags=tags,
|
|
limit=limit,
|
|
pythonify=True,
|
|
)
|
|
|
|
collected = []
|
|
for event in events:
|
|
event_data = {
|
|
"id": event.id,
|
|
"info": event.info,
|
|
"date": str(event.date),
|
|
"threat_level": event.threat_level_id,
|
|
"analysis": event.analysis,
|
|
"attribute_count": len(event.attributes),
|
|
"tags": [tag.name for tag in event.tags] if event.tags else [],
|
|
"attributes": [],
|
|
}
|
|
|
|
for attr in event.attributes:
|
|
event_data["attributes"].append({
|
|
"type": attr.type,
|
|
"value": attr.value,
|
|
"category": attr.category,
|
|
"to_ids": attr.to_ids,
|
|
})
|
|
|
|
collected.append(event_data)
|
|
self.stats["events_processed"] += 1
|
|
|
|
print(f"[+] Collected {len(collected)} events with tags: {tags}")
|
|
return collected
|
|
|
|
def filter_warninglists(self, iocs: list) -> list:
|
|
"""Filter IOCs against MISP warninglists to remove known-good indicators."""
|
|
filtered = []
|
|
|
|
for ioc in iocs:
|
|
result = self.misp.values_in_warninglist([ioc["value"]])
|
|
|
|
if not result or not result.get(ioc["value"]):
|
|
filtered.append(ioc)
|
|
else:
|
|
self.stats["warninglist_filtered"] += 1
|
|
print(f"[!] Filtered (warninglist): {ioc['value']}")
|
|
|
|
print(f"[+] Filtered {self.stats['warninglist_filtered']} IOCs via warninglists")
|
|
return filtered
|
|
|
|
def export_stix2(self, event_ids: Optional[list] = None,
|
|
tags: Optional[list] = None) -> dict:
|
|
"""Export events as STIX 2.1 bundles."""
|
|
search_params = {
|
|
"controller": "events",
|
|
"return_format": "stix2",
|
|
}
|
|
|
|
if event_ids:
|
|
search_params["eventid"] = event_ids
|
|
if tags:
|
|
search_params["tags"] = tags
|
|
|
|
stix_bundle = self.misp.search(**search_params)
|
|
self.stats["iocs_exported"] += len(
|
|
stix_bundle.get("objects", []) if isinstance(stix_bundle, dict) else []
|
|
)
|
|
|
|
print(f"[+] Exported STIX 2.1 bundle")
|
|
return stix_bundle
|
|
|
|
def export_csv(self, iocs: list, output_path: str) -> str:
|
|
"""Export IOCs to CSV file."""
|
|
if not iocs:
|
|
print("[-] No IOCs to export")
|
|
return ""
|
|
|
|
fieldnames = ["type", "value", "category", "event_id", "timestamp",
|
|
"to_ids", "comment"]
|
|
|
|
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
writer.writeheader()
|
|
for ioc in iocs:
|
|
writer.writerow({k: ioc.get(k, "") for k in fieldnames})
|
|
|
|
self.stats["iocs_exported"] = len(iocs)
|
|
print(f"[+] Exported {len(iocs)} IOCs to {output_path}")
|
|
return output_path
|
|
|
|
def export_suricata(self, days: int = 7) -> str:
|
|
"""Export network IOCs as Suricata rules."""
|
|
rules = self.misp.search(
|
|
controller="attributes",
|
|
return_format="suricata",
|
|
to_ids=True,
|
|
type_attribute=["ip-dst", "ip-src", "domain", "url"],
|
|
date_from=(datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d"),
|
|
)
|
|
|
|
print(f"[+] Generated Suricata rules")
|
|
return rules
|
|
|
|
def get_correlation_summary(self, event_id: int) -> dict:
|
|
"""Get correlation summary for a specific event."""
|
|
event = self.misp.get_event(event_id, pythonify=True)
|
|
correlations = {}
|
|
|
|
for attr in event.attributes:
|
|
if hasattr(attr, "RelatedAttribute") and attr.RelatedAttribute:
|
|
correlations[attr.value] = {
|
|
"type": attr.type,
|
|
"related_events": [
|
|
rel["Event"]["id"] for rel in attr.RelatedAttribute
|
|
],
|
|
}
|
|
|
|
return {
|
|
"event_id": event_id,
|
|
"event_info": event.info,
|
|
"total_attributes": len(event.attributes),
|
|
"correlated_attributes": len(correlations),
|
|
"correlations": correlations,
|
|
}
|
|
|
|
def print_stats(self):
|
|
"""Print collection statistics."""
|
|
print("\n=== MISP Collection Statistics ===")
|
|
for key, value in self.stats.items():
|
|
print(f" {key.replace('_', ' ').title()}: {value}")
|
|
print("=================================\n")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="MISP Threat Intelligence Collection Tool"
|
|
)
|
|
parser.add_argument("--url", required=True, help="MISP instance URL")
|
|
parser.add_argument("--key", required=True, help="MISP API key")
|
|
parser.add_argument("--no-ssl", action="store_true", help="Disable SSL verification")
|
|
parser.add_argument(
|
|
"--action",
|
|
choices=["collect", "export", "feeds", "correlate"],
|
|
required=True,
|
|
help="Action to perform",
|
|
)
|
|
parser.add_argument("--days", type=int, default=7, help="Lookback period in days")
|
|
parser.add_argument(
|
|
"--format",
|
|
choices=["csv", "stix2", "suricata"],
|
|
default="csv",
|
|
help="Export format",
|
|
)
|
|
parser.add_argument("--output", default="misp_iocs_export.csv", help="Output file path")
|
|
parser.add_argument("--tags", nargs="+", help="Filter by tags")
|
|
parser.add_argument(
|
|
"--enable-defaults",
|
|
action="store_true",
|
|
help="Enable default community feeds",
|
|
)
|
|
parser.add_argument("--event-id", type=int, help="Event ID for correlation")
|
|
|
|
args = parser.parse_args()
|
|
|
|
collector = MISPCollector(args.url, args.key, ssl_verify=not args.no_ssl)
|
|
|
|
if args.action == "feeds":
|
|
if args.enable_defaults:
|
|
result = collector.enable_default_feeds()
|
|
print(json.dumps(result, indent=2))
|
|
|
|
elif args.action == "collect":
|
|
iocs = collector.collect_recent_iocs(days=args.days)
|
|
if args.tags:
|
|
events = collector.collect_events_by_tag(args.tags)
|
|
print(json.dumps(events[:5], indent=2, default=str))
|
|
filtered = collector.filter_warninglists(iocs)
|
|
collector.export_csv(filtered, args.output)
|
|
|
|
elif args.action == "export":
|
|
if args.format == "stix2":
|
|
bundle = collector.export_stix2(tags=args.tags)
|
|
with open(args.output.replace(".csv", ".json"), "w") as f:
|
|
json.dump(bundle, f, indent=2, default=str)
|
|
elif args.format == "suricata":
|
|
rules = collector.export_suricata(days=args.days)
|
|
with open(args.output.replace(".csv", ".rules"), "w") as f:
|
|
f.write(str(rules))
|
|
else:
|
|
iocs = collector.collect_recent_iocs(days=args.days)
|
|
collector.export_csv(iocs, args.output)
|
|
|
|
elif args.action == "correlate":
|
|
if args.event_id:
|
|
summary = collector.get_correlation_summary(args.event_id)
|
|
print(json.dumps(summary, indent=2, default=str))
|
|
else:
|
|
print("[-] --event-id required for correlation action")
|
|
|
|
collector.print_stats()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|