#!/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()