misp_analyzer.py aktualisiert

This commit is contained in:
Mario Stöckl 2025-07-30 11:43:18 +00:00
parent 812bc2376d
commit e9745c0c86

View File

@ -31,6 +31,8 @@ class MispAnalyzer(interface.BaseAnalyzer):
self._attr = kwargs.get("attr") self._attr = kwargs.get("attr")
self._timesketch_attr = kwargs.get("timesketch_attr") self._timesketch_attr = kwargs.get("timesketch_attr")
self.ip_pattern = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b') self.ip_pattern = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b')
# Track marked events to prevent duplicates
self.marked_events = set()
@staticmethod @staticmethod
def get_kwargs(): def get_kwargs():
@ -58,17 +60,12 @@ class MispAnalyzer(interface.BaseAnalyzer):
}, },
{ {
"query_string": "message:*", "query_string": "message:*",
"attr": "ip-src", "attr": "ip", # Generic IP instead of ip-src/ip-dst
"timesketch_attr": "message",
},
{
"query_string": "message:*",
"attr": "ip-dst",
"timesketch_attr": "message", "timesketch_attr": "message",
}, },
{ {
"query_string": "source_ip:*", "query_string": "source_ip:*",
"attr": "ip-src", "attr": "ip",
"timesketch_attr": "source_ip", "timesketch_attr": "source_ip",
}, },
] ]
@ -80,7 +77,6 @@ class MispAnalyzer(interface.BaseAnalyzer):
import ipaddress import ipaddress
ip_str = ip_str.strip() ip_str = ip_str.strip()
ipaddress.ip_address(ip_str) ipaddress.ip_address(ip_str)
# Filter out invalid ranges
if ip_str.startswith(('0.', '127.', '255.255.255.255')): if ip_str.startswith(('0.', '127.', '255.255.255.255')):
return False return False
return True return True
@ -103,41 +99,96 @@ class MispAnalyzer(interface.BaseAnalyzer):
return False return False
def query_misp_single(self, value, attr): def query_misp_single(self, value, attr):
"""Query MISP for a single value.""" """Query MISP for a single value - ENHANCED for cross-org visibility."""
results = []
# Query both ip-src and ip-dst for IPs, include cross-org events
if attr == "ip":
search_types = ["ip-src", "ip-dst"]
else:
search_types = [attr]
for search_type in search_types:
try: try:
# Include events from other organizations
payload = {
"returnFormat": "json",
"value": value,
"type": search_type,
# Include events from all organizations with proper distribution
"enforceWarninglist": False, # Don't filter known-good IPs
"includeDecayScore": False, # Skip decay scores for speed
"includeFullModel": False, # Skip full model for speed
"decayingModel": [], # No decaying model filters
"excludeDecayed": False, # Include older indicators
# Distribution levels: 0=Own org, 1=Community, 2=Connected, 3=All, 5=Inherit
"distribution": [0, 1, 2, 3, 5] # Include all distribution levels
}
response = requests.post( response = requests.post(
f"{self.misp_url}/attributes/restSearch/", f"{self.misp_url}/attributes/restSearch/",
json={"returnFormat": "json", "value": value, "type": attr}, json=payload,
headers={"Authorization": self.misp_api_key}, headers={"Authorization": self.misp_api_key},
verify=False, verify=False,
timeout=30, timeout=30,
) )
if response.status_code != 200: if response.status_code == 200:
return []
data = response.json() data = response.json()
return data.get("response", {}).get("Attribute", []) attributes = data.get("response", {}).get("Attribute", [])
results.extend(attributes)
except Exception: except Exception:
return [] continue
return results
def mark_event(self, event, result, attr): def mark_event(self, event, result, attr):
"""Add MISP intelligence to event.""" """Add MISP intelligence to event - FIXED to prevent duplicates."""
try: try:
if attr.startswith("ip-"): # Check if event already marked
msg = "MISP: Malicious IP - " event_id = event.source.get('_id', '')
if event_id in self.marked_events:
return
self.marked_events.add(event_id)
# Show organization info for cross-org awareness
if attr == "ip":
msg = "MISP: Malicious IP detected - "
else: else:
msg = "MISP: Known indicator - " msg = "MISP: Known indicator - "
event_info = result[0].get("Event", {}).get("info", "Unknown") # Collect unique events and organizations
msg += event_info events_info = {}
orgs_info = set()
if len(result) > 1: for misp_attr in result:
msg += f" (+{len(result)-1} more)" event_info = misp_attr.get("Event", {})
event_id = event_info.get("id", "")
event_desc = event_info.get("info", "Unknown")
org_name = event_info.get("Orgc", {}).get("name", "Unknown Org")
events_info[event_id] = f'"{event_desc}"'
orgs_info.add(org_name)
# Build message with org info
event_descriptions = list(events_info.values())[:2] # First 2 events
msg += " | ".join(event_descriptions)
if len(result) > 2:
msg += f" | +{len(result)-2} more"
# Add organization information
if len(orgs_info) > 1:
msg += f" | Orgs: {', '.join(list(orgs_info)[:3])}"
elif orgs_info:
org_name = list(orgs_info)[0]
if org_name != "Unknown Org":
msg += f" | Org: {org_name}"
event.add_comment(msg) event.add_comment(msg)
event.add_tags([f"MISP-{attr}", "threat-intel"]) event.add_tags([f"MISP-{attr}", "threat-intel", "cross-org-intel"])
event.commit() event.commit()
except Exception as e: except Exception as e:
@ -145,7 +196,7 @@ class MispAnalyzer(interface.BaseAnalyzer):
def query_misp(self, query, attr, timesketch_attr): def query_misp(self, query, attr, timesketch_attr):
"""Extract indicators and query MISP.""" """Extract indicators and query MISP."""
events = self.event_stream(query_string=query, return_fields=[timesketch_attr]) events = self.event_stream(query_string=query, return_fields=[timesketch_attr, '_id'])
query_list = [] query_list = []
events_list = [] events_list = []
processed = 0 processed = 0
@ -153,7 +204,7 @@ class MispAnalyzer(interface.BaseAnalyzer):
# Extract indicators from events # Extract indicators from events
for event in events: for event in events:
processed += 1 processed += 1
if processed > 5000: # Reasonable limit if processed > 5000:
break break
loc = event.source.get(timesketch_attr) loc = event.source.get(timesketch_attr)
@ -163,24 +214,20 @@ class MispAnalyzer(interface.BaseAnalyzer):
events_list.append(event) events_list.append(event)
indicators = [] indicators = []
# Extract based on attribute type - STRICT VALIDATION # Extract based on attribute type
if attr.startswith("ip-") and timesketch_attr == "message": if attr == "ip" and timesketch_attr == "message":
# Extract IPs from message field
ip_matches = self.ip_pattern.findall(str(loc)) ip_matches = self.ip_pattern.findall(str(loc))
indicators = [ip for ip in ip_matches if self._is_valid_ip(ip)] indicators = [ip for ip in ip_matches if self._is_valid_ip(ip)]
elif attr.startswith("ip-") and timesketch_attr in ["source_ip", "src_ip", "client_ip"]: elif attr == "ip" and timesketch_attr in ["source_ip", "src_ip", "client_ip"]:
# Direct IP field
if self._is_valid_ip(str(loc)): if self._is_valid_ip(str(loc)):
indicators = [str(loc)] indicators = [str(loc)]
elif attr in ["md5", "sha1", "sha256"]: elif attr in ["md5", "sha1", "sha256"]:
# Hash fields
if self._is_valid_hash(str(loc), attr): if self._is_valid_hash(str(loc), attr):
indicators = [str(loc)] indicators = [str(loc)]
elif attr == "filename": elif attr == "filename":
# Filename extraction
filename = ntpath.basename(str(loc)) filename = ntpath.basename(str(loc))
if filename and len(filename) > 1: if filename and len(filename) > 1:
indicators = [filename] indicators = [filename]
@ -201,7 +248,12 @@ class MispAnalyzer(interface.BaseAnalyzer):
result = self.query_misp_single(indicator, attr) result = self.query_misp_single(indicator, attr)
if result: if result:
self.result_dict[f"{attr}:{indicator}"] = result self.result_dict[f"{attr}:{indicator}"] = result
logger.info(f"MISP hit: {indicator}") # Log organization diversity
orgs = set()
for r in result:
org = r.get("Event", {}).get("Orgc", {}).get("name", "Unknown")
orgs.add(org)
logger.info(f"MISP hit: {indicator} ({len(result)} indicators from {len(orgs)} orgs)")
# Mark matching events # Mark matching events
for event in events_list: for event in events_list:
@ -209,13 +261,18 @@ class MispAnalyzer(interface.BaseAnalyzer):
if not loc: if not loc:
continue continue
# Check if event already processed
event_id = event.source.get('_id', '')
if event_id in self.marked_events:
continue
# Re-extract indicators from this event # Re-extract indicators from this event
event_indicators = [] event_indicators = []
if attr.startswith("ip-") and timesketch_attr == "message": if attr == "ip" and timesketch_attr == "message":
ip_matches = self.ip_pattern.findall(str(loc)) ip_matches = self.ip_pattern.findall(str(loc))
event_indicators = [ip for ip in ip_matches if self._is_valid_ip(ip)] event_indicators = [ip for ip in ip_matches if self._is_valid_ip(ip)]
elif attr.startswith("ip-") and timesketch_attr in ["source_ip", "src_ip", "client_ip"]: elif attr == "ip" and timesketch_attr in ["source_ip", "src_ip", "client_ip"]:
if self._is_valid_ip(str(loc)): if self._is_valid_ip(str(loc)):
event_indicators = [str(loc)] event_indicators = [str(loc)]
elif attr in ["md5", "sha1", "sha256"]: elif attr in ["md5", "sha1", "sha256"]:
@ -237,9 +294,9 @@ class MispAnalyzer(interface.BaseAnalyzer):
# Create view if we found matches # Create view if we found matches
if self.total_event_counter > 0: if self.total_event_counter > 0:
self.sketch.add_view( self.sketch.add_view(
view_name="MISP Threat Intelligence", view_name="MISP Cross-Org Threat Intel",
analyzer_name=self.NAME, analyzer_name=self.NAME,
query_string='tag:"MISP-*" OR tag:"threat-intel"', query_string='tag:"MISP-*" OR tag:"threat-intel" OR tag:"cross-org-intel"',
) )
def run(self): def run(self):