We have identified a critical issue with the OpenCTI Splunk Add-On where it incorrectly updates existing OpenCTI incidents instead of creating new ones for distinct Splunk security events. This occurs when multiple Splunk events trigger the same use case at the exact same second, leading to a collision in the generated STIX Incident ID.
This behavior results in data loss, corruption, and incorrect incident tracking, especially prevalent in high-volume environments like MSSPs where multiple similar alerts can fire concurrently.
Affected Components & Versions
- OpenCTI Splunk Add-On: Version 1.1.6 (and likely earlier/later versions using the same ID generation logic)
- OpenCTI Platform: Version 6.9.28 (behavior is likely platform-agnostic regarding STIX ID updates)
Steps to Reproduce
- Configure Splunk Add-On: Ensure the Splunk Add-On for OpenCTI is configured to create incidents based on Splunk alerts.
- Trigger Use Case (Event A): A Splunk use case is triggered for
Customer A at a specific time (e.g., _time=1771487340 (epoch without milliseconds), name="Malicious IP Observed").
- OpenCTI Incident Created: A new case/incident is created in OpenCTI for
Customer A. (Optionally, a custom connector might trigger to add a short ID to the title).
- Trigger Use Case (Event B): A different, distinct Splunk event for
Customer B (or even a separate event for Customer A) triggers the same use case (name="Malicious IP Observed") at the exact same second as Event A (_time=1771487340).
- Observe Incorrect Behavior: Instead of creating a new incident for
Customer B, the Splunk Add-On updates the existing incident (originally created for Customer A) with information from Event B.
Expected Behavior
For each distinct Splunk security event that triggers an OpenCTI incident creation, a new and unique OpenCTI incident should be created, regardless of whether other events with the same use case name occur within the same second.
Actual Behavior
When name (Splunk use case name) and created (derived from Splunk's _time field, lacking millisecond precision) are identical for two or more distinct Splunk events, the Splunk Add-On generates the same STIX Incident ID. OpenCTI, upon receiving an object with an existing STIX ID, performs an update operation, leading to the merging of distinct security incidents.
Root Cause Analysis
The issue stems from the deterministic generation of the STIX Incident ID using uuid.uuid5 in the Splunk Add-On's utils.py.
Relevant Code Snippets:
ta_opencti_add_on/stix_converter.py` (line 143, or similar):**
stix_case_incident = CustomObjectCaseIncident(
id=generate_case_incident_id(alert_params.get("name"), event_date), # <--- ID generated here
name=alert_params.get("name"),
description=alert_params.get("description"),
severity=alert_params.get("severity"),
priority=alert_params.get("priority"),
labels=alert_params.get("labels"),
created=event_date,
external_references=[],
created_by_ref=stix_author.id,
object_marking_refs=[marking_id],
object_refs=observable_ref_ids
)
ta_opencti_add_on/utils.py (line 143, or similar):
def generate_incident_id(name: str, created):
"""
:param name:
:param created:
:return:
"""
if isinstance(created, datetime.datetime):
created = created.isoformat() # <--- Converts to ISO format, but _time often lacks milliseconds
data = {"name": name.lower().strip(), "created": created}
data = canonicalize(data, utf8=False)
entity_id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) # <--- Deterministic UUID
return "incident--" + entity_id
The name parameter typically corresponds to the Splunk alert/use case name, which can be identical across many events. The created parameter is derived from Splunk's _time field, which is often an epoch timestamp without millisecond precision. Consequently, if two distinct events share the same name and occur within the same second, the data dictionary passed to uuid.uuid5 will be identical, leading to the generation of the exact same STIX ID (incident--...).
Impact
Data Integrity Issues: Information from separate security events is merged, leading to an incomplete or inaccurate representation of each incident.
Operational Inefficiency: SOC/CSIRT analysts might miss critical details or misinterpret incidents due to merged data.
Misleading Metrics: Incident counts and resolution times can be skewed.
Compliance Risk: Inability to accurately track and report on distinct security events.
Proposed Solutions / Enhancements
-
Introduce a Configuration Option for Incident Creation Strategy:
Add a new configuration parameter to the Splunk Add-On (e.g., INCIDENT_CREATION_STRATEGY) that allows users to define the desired behavior:
ALWAYS_CREATE_NEW (default/recommended for most use cases)
UPSERT (current behavior, for specific scenarios where updating by name+time is desired)
-
Modify generate_incident_id for Unique ID Generation (for ALWAYS_CREATE_NEW strategy):
When the strategy is ALWAYS_CREATE_NEW, the generate_incident_id function must incorporate a truly unique identifier from the Splunk event to ensure a unique STIX ID for each distinct event.
Option A (Splunk Event ID): If Splunk provides a unique event ID (e.g., _id field in some contexts), this should be included in the data dictionary for uuid.uuid5.
Option B (High-Precision Timestamp + Random Salt): If a unique Splunk event ID is not reliably available or accessible, modify the function to include a high-precision timestamp (if available from Splunk, e.g., _time with microseconds if possible to extract) combined with a randomly generated UUID or string.
`import uuid
import datetime
def generate_incident_id_unique(name: str, created: datetime.datetime, splunk_event_id: str = None):
if isinstance(created, datetime.datetime):
# Use ISO format with microseconds for better granularity
created_str = created.isoformat(timespec='microseconds')
else:
created_str = created
data = {"name": name.lower().strip(), "created": created_str}
if splunk_event_id:
data["splunk_event_id"] = splunk_event_id
else:
# Fallback to a random UUID if no specific Splunk ID is available,
# ensuring uniqueness even if name and created_str are identical
data["unique_salt"] = str(uuid.uuid4())
# Ensure canonicalization for consistent UUID5 generation
data = canonicalize(data, utf8=False)
entity_id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
return "incident--" + entity_id`
ref: OCTI2-1984
We have identified a critical issue with the OpenCTI Splunk Add-On where it incorrectly updates existing OpenCTI incidents instead of creating new ones for distinct Splunk security events. This occurs when multiple Splunk events trigger the same use case at the exact same second, leading to a collision in the generated STIX Incident ID.
This behavior results in data loss, corruption, and incorrect incident tracking, especially prevalent in high-volume environments like MSSPs where multiple similar alerts can fire concurrently.
Affected Components & Versions
Steps to Reproduce
Customer Aat a specific time (e.g.,_time=1771487340(epoch without milliseconds),name="Malicious IP Observed").Customer A. (Optionally, a custom connector might trigger to add a short ID to the title).Customer B(or even a separate event forCustomer A) triggers the same use case (name="Malicious IP Observed") at the exact same second as Event A (_time=1771487340).Customer B, the Splunk Add-On updates the existing incident (originally created forCustomer A) with information fromEvent B.Expected Behavior
For each distinct Splunk security event that triggers an OpenCTI incident creation, a new and unique OpenCTI incident should be created, regardless of whether other events with the same use case name occur within the same second.
Actual Behavior
When
name(Splunk use case name) andcreated(derived from Splunk's_timefield, lacking millisecond precision) are identical for two or more distinct Splunk events, the Splunk Add-On generates the same STIX Incident ID. OpenCTI, upon receiving an object with an existing STIX ID, performs an update operation, leading to the merging of distinct security incidents.Root Cause Analysis
The issue stems from the deterministic generation of the STIX Incident ID using
uuid.uuid5in the Splunk Add-On'sutils.py.Relevant Code Snippets:
ta_opencti_add_on/stix_converter.py` (line 143, or similar):**
ta_opencti_add_on/utils.py (line 143, or similar):
The name parameter typically corresponds to the Splunk alert/use case name, which can be identical across many events. The created parameter is derived from Splunk's _time field, which is often an epoch timestamp without millisecond precision. Consequently, if two distinct events share the same name and occur within the same second, the data dictionary passed to uuid.uuid5 will be identical, leading to the generation of the exact same STIX ID (incident--...).
Impact
Proposed Solutions / Enhancements
Introduce a Configuration Option for Incident Creation Strategy:
Add a new configuration parameter to the Splunk Add-On (e.g., INCIDENT_CREATION_STRATEGY) that allows users to define the desired behavior:
ALWAYS_CREATE_NEW (default/recommended for most use cases)
UPSERT (current behavior, for specific scenarios where updating by name+time is desired)
Modify generate_incident_id for Unique ID Generation (for ALWAYS_CREATE_NEW strategy):
When the strategy is ALWAYS_CREATE_NEW, the generate_incident_id function must incorporate a truly unique identifier from the Splunk event to ensure a unique STIX ID for each distinct event.
Option A (Splunk Event ID): If Splunk provides a unique event ID (e.g., _id field in some contexts), this should be included in the data dictionary for uuid.uuid5.
Option B (High-Precision Timestamp + Random Salt): If a unique Splunk event ID is not reliably available or accessible, modify the function to include a high-precision timestamp (if available from Splunk, e.g., _time with microseconds if possible to extract) combined with a randomly generated UUID or string.
`import uuid
import datetime
def generate_incident_id_unique(name: str, created: datetime.datetime, splunk_event_id: str = None):
if isinstance(created, datetime.datetime):
# Use ISO format with microseconds for better granularity
created_str = created.isoformat(timespec='microseconds')
else:
created_str = created
ref: OCTI2-1984