Produce and consume STIX 2 JSON content for cyber threat intelligence
Utility functions for working with STIX objects including type checking, timestamp handling, object deduplication, and specification version detection.
Functions to identify and validate STIX object types.
def is_sdo(value, stix_version="2.1"):
"""
Check if value is a STIX Domain Object.
Parameters:
- value: Object to check (STIX object, dict, or string)
- stix_version (str): STIX specification version ("2.0" or "2.1")
Returns:
bool: True if value is an SDO, False otherwise
"""
def is_sco(value, stix_version="2.1"):
"""
Check if value is a STIX Cyber Observable Object.
Parameters:
- value: Object to check (STIX object, dict, or string)
- stix_version (str): STIX specification version ("2.0" or "2.1")
Returns:
bool: True if value is an SCO, False otherwise
"""
def is_sro(value, stix_version="2.1"):
"""
Check if value is a STIX Relationship Object.
Parameters:
- value: Object to check (STIX object, dict, or string)
- stix_version (str): STIX specification version ("2.0" or "2.1")
Returns:
bool: True if value is an SRO, False otherwise
"""
def is_object(value, stix_version="2.1"):
"""
Check if value is any STIX object (SDO, SRO, or marking).
Parameters:
- value: Object to check (STIX object, dict, or string)
- stix_version (str): STIX specification version ("2.0" or "2.1")
Returns:
bool: True if value is a STIX object, False otherwise
"""
def is_marking(value, stix_version="2.1"):
"""
Check if value is a STIX marking definition.
Parameters:
- value: Object to check (STIX object, dict, or string)
- stix_version (str): STIX specification version ("2.0" or "2.1")
Returns:
bool: True if value is a marking definition, False otherwise
"""
def is_stix_type(value, stix_version="2.1", *types):
"""
Check if value is one of the specified STIX types.
Parameters:
- value: Object to check
- stix_version (str): STIX specification version
- *types: Variable number of STIXTypeClass values to check against
Returns:
bool: True if value matches any of the specified types
"""Usage examples:
from stix2 import is_sdo, is_sco, is_sro, is_object, is_marking
from stix2 import Indicator, Malware, File, Relationship, MarkingDefinition
# Create test objects
indicator = Indicator(
name="Test Indicator",
indicator_types=["malicious-activity"],
pattern_type="stix",
pattern="[file:hashes.MD5 = 'abc123']"
)
malware = Malware(
name="Test Malware",
malware_types=["trojan"]
)
file_obj = File(
hashes={"MD5": "abc123"},
name="test.exe"
)
relationship = Relationship(
relationship_type="indicates",
source_ref=indicator.id,
target_ref=malware.id
)
marking = MarkingDefinition(
definition_type="statement",
definition={"statement": "Internal Use Only"}
)
# Type checking
print(f"Indicator is SDO: {is_sdo(indicator)}") # True
print(f"Malware is SDO: {is_sdo(malware)}") # True
print(f"File is SDO: {is_sdo(file_obj)}") # False
print(f"File is SCO: {is_sco(file_obj)}") # True
print(f"Indicator is SCO: {is_sco(indicator)}") # False
print(f"Relationship is SRO: {is_sro(relationship)}") # True
print(f"Indicator is SRO: {is_sro(indicator)}") # False
print(f"Marking is marking: {is_marking(marking)}") # True
print(f"Indicator is marking: {is_marking(indicator)}")# False
# Check any STIX object
print(f"Indicator is STIX object: {is_object(indicator)}") # True
print(f"File is STIX object: {is_object(file_obj)}") # True
print(f"Relationship is STIX object: {is_object(relationship)}")# True
# Check with dictionaries
indicator_dict = {
"type": "indicator",
"id": "indicator--12345678-1234-1234-1234-123456789012",
"name": "Test Indicator"
}
print(f"Dict is SDO: {is_sdo(indicator_dict)}") # True
# Check with type strings
print(f"'indicator' is SDO: {is_sdo('indicator')}") # True
print(f"'file' is SCO: {is_sco('file')}") # True
print(f"'relationship' is SRO: {is_sro('relationship')}")# TrueFunctions for working with STIX timestamps and datetime objects.
def get_timestamp():
"""
Get current timestamp in STIX format.
Returns:
str: Current timestamp in STIX format (ISO 8601)
"""
def format_datetime(dttm):
"""
Format datetime object to STIX timestamp string.
Parameters:
- dttm (datetime): Datetime object to format
Returns:
str: Formatted timestamp string
"""
def parse_into_datetime(value, precision=None, allow_none=False):
"""
Parse string into datetime object with STIX-specific handling.
Parameters:
- value: String or datetime to parse
- precision (Precision): Timestamp precision level
- allow_none (bool): Allow None values
Returns:
STIXdatetime: Parsed datetime object
Raises:
ValueError: If timestamp format is invalid
"""
class STIXdatetime:
"""
STIX-specific datetime class with precision handling.
Inherits from datetime.datetime with additional STIX-specific
formatting and precision methods.
"""
class Precision:
"""
Enumeration of timestamp precision levels.
Values:
- YEAR
- MONTH
- DAY
- HOUR
- MINUTE
- SECOND
- MILLISECOND
"""Usage examples:
from stix2 import get_timestamp, format_datetime, parse_into_datetime, STIXdatetime
from datetime import datetime
# Get current timestamp in STIX format
current_time = get_timestamp()
print(f"Current STIX timestamp: {current_time}")
# Output: "2021-04-23T10:30:45.123Z"
# Format datetime object
dt = datetime(2021, 4, 23, 10, 30, 45, 123000)
stix_formatted = format_datetime(dt)
print(f"Formatted datetime: {stix_formatted}")
# Output: "2021-04-23T10:30:45.123Z"
# Parse timestamp strings
timestamp_str = "2021-04-23T10:30:45.123Z"
parsed_dt = parse_into_datetime(timestamp_str)
print(f"Parsed datetime: {parsed_dt}")
print(f"Type: {type(parsed_dt)}") # STIXdatetime
# Parse with different precision
from stix2.utils import Precision
year_precision = parse_into_datetime("2021", precision=Precision.YEAR)
day_precision = parse_into_datetime("2021-04-23", precision=Precision.DAY)
print(f"Year precision: {year_precision}") # 2021-01-01T00:00:00.000Z
print(f"Day precision: {day_precision}") # 2021-04-23T00:00:00.000Z
# STIX datetime usage
stix_dt = STIXdatetime(2021, 4, 23, 10, 30, 45, 123000)
print(f"STIX datetime: {stix_dt}")
# Parse various timestamp formats
timestamps = [
"2021-04-23T10:30:45Z",
"2021-04-23T10:30:45.123Z",
"2021-04-23T10:30:45+00:00",
"2021-04-23 10:30:45"
]
for ts in timestamps:
try:
parsed = parse_into_datetime(ts)
print(f"{ts} -> {parsed}")
except ValueError as e:
print(f"{ts} -> Error: {e}")Utilities for working with STIX objects and their properties.
def deduplicate(stix_obj_list):
"""
Remove duplicate STIX objects from a list.
Parameters:
- stix_obj_list (list): List of STIX objects
Returns:
list: List with duplicates removed (keeps most recent version)
"""
def get_class_hierarchy_names(obj):
"""
Get class hierarchy names for a STIX object.
Parameters:
- obj: STIX object
Returns:
list: List of class names in inheritance hierarchy
"""
def get_type_from_id(stix_id):
"""
Extract STIX type from STIX identifier.
Parameters:
- stix_id (str): STIX object identifier
Returns:
str: STIX object type or None if invalid ID
"""
def detect_spec_version(stix_dict):
"""
Detect STIX specification version from object dictionary.
Parameters:
- stix_dict (dict): STIX object as dictionary
Returns:
str: Detected STIX version ("2.0" or "2.1")
"""
def to_enum(value, enum_type, enum_default=None):
"""
Convert value to enumeration type with default fallback.
Parameters:
- value: Value to convert
- enum_type: Target enumeration type
- enum_default: Default value if conversion fails
Returns:
Enum value or default
"""Usage examples:
from stix2 import deduplicate, get_type_from_id, detect_spec_version
from stix2 import Indicator, new_version
# Create objects with duplicates
indicator_v1 = Indicator(
name="Test Indicator",
indicator_types=["malicious-activity"],
pattern_type="stix",
pattern="[file:hashes.MD5 = 'abc123']"
)
indicator_v2 = new_version(indicator_v1, confidence=85)
indicator_v3 = new_version(indicator_v2, confidence=95)
# Different indicator
other_indicator = Indicator(
name="Other Indicator",
indicator_types=["malicious-activity"],
pattern_type="stix",
pattern="[ip-addr:value = '192.168.1.1']"
)
# List with duplicates (multiple versions of same object)
object_list = [indicator_v1, indicator_v2, indicator_v3, other_indicator, indicator_v1]
# Remove duplicates (keeps most recent version)
deduplicated = deduplicate(object_list)
print(f"Original list: {len(object_list)} objects")
print(f"Deduplicated: {len(deduplicated)} objects")
# Should contain indicator_v3 (most recent) and other_indicator
for obj in deduplicated:
if obj.id == indicator_v1.id:
print(f"Kept version modified: {obj.modified}") # Should be v3
# Extract type from STIX ID
stix_id = "indicator--12345678-1234-1234-1234-123456789012"
obj_type = get_type_from_id(stix_id)
print(f"Type from ID: {obj_type}") # "indicator"
# Test various ID formats
test_ids = [
"indicator--12345678-1234-1234-1234-123456789012",
"malware--abcdef12-3456-7890-abcd-ef1234567890",
"relationship--fedcba98-7654-3210-fedc-ba9876543210",
"invalid-id-format"
]
for test_id in test_ids:
obj_type = get_type_from_id(test_id)
print(f"{test_id} -> {obj_type}")
# Detect STIX specification version
stix_2_0_dict = {
"type": "indicator",
"id": "indicator--12345678-1234-1234-1234-123456789012",
"created": "2021-04-23T10:30:00.000Z",
"modified": "2021-04-23T10:30:00.000Z",
"labels": ["malicious-activity"],
"pattern": "[file:hashes.MD5 = 'abc123']"
}
stix_2_1_dict = {
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--12345678-1234-1234-1234-123456789012",
"created": "2021-04-23T10:30:00.000Z",
"modified": "2021-04-23T10:30:00.000Z",
"indicator_types": ["malicious-activity"],
"pattern_type": "stix",
"pattern": "[file:hashes.MD5 = 'abc123']"
}
version_2_0 = detect_spec_version(stix_2_0_dict)
version_2_1 = detect_spec_version(stix_2_1_dict)
print(f"First dict version: {version_2_0}") # "2.0"
print(f"Second dict version: {version_2_1}") # "2.1"
# Class hierarchy inspection
hierarchy = get_class_hierarchy_names(indicator_v1)
print(f"Indicator class hierarchy: {hierarchy}")More sophisticated type checking and classification utilities.
from stix2.utils import STIXTypeClass, is_stix_type
# STIXTypeClass enumeration values
print("Available STIX type classes:")
for type_class in STIXTypeClass:
print(f" - {type_class.name}: {type_class.value}")
# Check specific type classes
print(f"Indicator is SDO: {is_stix_type(indicator, '2.1', STIXTypeClass.SDO)}")
print(f"File is SCO: {is_stix_type(file_obj, '2.1', STIXTypeClass.SCO)}")
print(f"Relationship is SRO: {is_stix_type(relationship, '2.1', STIXTypeClass.SRO)}")
# Check multiple type classes
is_domain_or_relationship = is_stix_type(
indicator,
'2.1',
STIXTypeClass.SDO,
STIXTypeClass.SRO
)
print(f"Indicator is SDO or SRO: {is_domain_or_relationship}") # True
# Custom type classification function
def classify_stix_object(obj, version="2.1"):
"""Classify STIX object into detailed categories."""
if is_sdo(obj, version):
obj_type = obj.type if hasattr(obj, 'type') else str(obj)
threat_objects = ['threat-actor', 'intrusion-set', 'campaign']
malware_objects = ['malware', 'tool']
indicator_objects = ['indicator', 'observed-data']
target_objects = ['identity', 'location', 'infrastructure']
context_objects = ['attack-pattern', 'course-of-action', 'vulnerability']
if obj_type in threat_objects:
return "Threat Intelligence"
elif obj_type in malware_objects:
return "Malware/Tools"
elif obj_type in indicator_objects:
return "Indicators/Observables"
elif obj_type in target_objects:
return "Targets/Assets"
elif obj_type in context_objects:
return "Context/Mitigation"
else:
return "Other SDO"
elif is_sco(obj, version):
return "Cyber Observable"
elif is_sro(obj, version):
return "Relationship"
elif is_marking(obj, version):
return "Marking Definition"
else:
return "Unknown"
# Classify objects
test_objects = [indicator, malware, file_obj, relationship, marking]
for obj in test_objects:
classification = classify_stix_object(obj)
obj_name = getattr(obj, 'name', getattr(obj, 'type', str(obj)))
print(f"{obj_name}: {classification}")Functions for converting between different confidence scale representations. STIX 2.1 supports multiple confidence scales, and these functions provide standardized conversions between common scale formats.
def none_low_med_high_to_value(scale_value):
"""
Transform string value from None/Low/Med/High scale to integer.
Parameters:
- scale_value (str): Scale value ("None", "Low", "Med", "High")
Returns:
int: Confidence value (0=None, 15=Low, 50=Med, 85=High)
Raises:
ValueError: If scale_value is not recognized
"""
def value_to_none_low_medium_high(confidence_value):
"""
Transform integer confidence value to None/Low/Med/High scale.
Parameters:
- confidence_value (int): Integer value between 0-100
Returns:
str: Scale string (0="None", 1-29="Low", 30-69="Med", 70-100="High")
Raises:
ValueError: If confidence_value is out of bounds
"""
def zero_ten_to_value(scale_value):
"""
Transform string value from 0-10 scale to confidence integer.
Parameters:
- scale_value (str): Scale value from "0" to "10"
Returns:
int: Confidence value (0-100, mapped from 0-10 scale)
Raises:
ValueError: If scale_value is not valid 0-10 string
"""
def value_to_zero_ten(confidence_value):
"""
Transform integer confidence value to 0-10 scale.
Parameters:
- confidence_value (int): Integer value between 0-100
Returns:
str: Scale string from "0" to "10"
Raises:
ValueError: If confidence_value is out of bounds
"""
def admiralty_credibility_to_value(scale_value):
"""
Transform Admiralty Credibility scale to confidence integer.
Parameters:
- scale_value (str): Admiralty scale ("A" through "F")
Returns:
int: Confidence value mapped from Admiralty scale
Raises:
ValueError: If scale_value is not valid Admiralty scale
"""
def value_to_admiralty_credibility(confidence_value):
"""
Transform confidence integer to Admiralty Credibility scale.
Parameters:
- confidence_value (int): Integer value between 0-100
Returns:
str: Admiralty scale value ("A" through "F")
Raises:
ValueError: If confidence_value is out of bounds
"""Usage examples:
from stix2.confidence.scales import (
none_low_med_high_to_value, value_to_none_low_medium_high,
zero_ten_to_value, value_to_zero_ten,
admiralty_credibility_to_value, value_to_admiralty_credibility
)
# None/Low/Med/High scale conversions
print("None/Low/Med/High Scale:")
scales = ["None", "Low", "Med", "High"]
for scale in scales:
value = none_low_med_high_to_value(scale)
print(f" {scale} -> {value}")
# Convert confidence values back to scale
confidence_values = [0, 15, 35, 50, 75, 85, 100]
print("\nConfidence to None/Low/Med/High:")
for conf in confidence_values:
try:
scale = value_to_none_low_medium_high(conf)
print(f" {conf} -> {scale}")
except ValueError as e:
print(f" {conf} -> Error: {e}")
# 0-10 scale conversions
print("\n0-10 Scale:")
for i in range(11):
str_scale = str(i)
value = zero_ten_to_value(str_scale)
print(f" {str_scale} -> {value}")
# Convert back to 0-10 scale
print("\nConfidence to 0-10 Scale:")
test_values = [0, 25, 50, 75, 100]
for conf in test_values:
try:
scale = value_to_zero_ten(conf)
print(f" {conf} -> {scale}")
except ValueError as e:
print(f" {conf} -> Error: {e}")
# Admiralty Credibility scale
print("\nAdmiralty Credibility Scale:")
admiralty_scales = ["A", "B", "C", "D", "E", "F"]
for scale in admiralty_scales:
try:
value = admiralty_credibility_to_value(scale)
print(f" {scale} -> {value}")
except ValueError as e:
print(f" {scale} -> Error: {e}")
# Practical usage in STIX objects
from stix2 import Indicator
# Create indicators with confidence from different scales
high_confidence = none_low_med_high_to_value("High") # 85
medium_confidence = zero_ten_to_value("5") # 50
low_confidence = value_to_none_low_medium_high(20) # "Low"
indicator_high = Indicator(
name="High Confidence Indicator",
indicator_types=["malicious-activity"],
pattern_type="stix",
pattern="[file:hashes.md5 = 'abc123']",
confidence=high_confidence
)
indicator_medium = Indicator(
name="Medium Confidence Indicator",
indicator_types=["malicious-activity"],
pattern_type="stix",
pattern="[ip-addr:value = '192.168.1.1']",
confidence=medium_confidence
)
print(f"\nHigh confidence indicator: {indicator_high.confidence}")
print(f"Medium confidence indicator: {indicator_medium.confidence}")
# Convert indicator confidence back to human-readable scales
high_scale = value_to_none_low_medium_high(indicator_high.confidence)
medium_scale = value_to_zero_ten(indicator_medium.confidence)
print(f"High confidence as scale: {high_scale}") # "High"
print(f"Medium confidence as 0-10: {medium_scale}") # "5"
# Error handling for invalid inputs
try:
invalid_scale = none_low_med_high_to_value("Invalid")
except ValueError as e:
print(f"Invalid scale error: {e}")
try:
invalid_confidence = value_to_none_low_medium_high(150)
except ValueError as e:
print(f"Invalid confidence error: {e}")Utilities for handling and validating STIX objects with error checking.
from stix2.exceptions import STIXError
def safe_type_check(obj, check_func, default=False):
"""Safely perform type check with error handling."""
try:
return check_func(obj)
except (STIXError, AttributeError, TypeError) as e:
print(f"Type check error: {e}")
return default
def validate_stix_object(obj):
"""Validate STIX object structure and properties."""
validation_results = {
'valid': True,
'errors': [],
'warnings': []
}
# Check if it's a recognized STIX object
if not is_object(obj):
validation_results['valid'] = False
validation_results['errors'].append("Not a valid STIX object")
return validation_results
# Check required properties
if hasattr(obj, 'id'):
obj_type = get_type_from_id(obj.id)
if obj_type != getattr(obj, 'type', None):
validation_results['warnings'].append(
f"ID type '{obj_type}' doesn't match object type '{obj.type}'"
)
else:
validation_results['errors'].append("Missing required 'id' property")
validation_results['valid'] = False
# Check timestamps
if hasattr(obj, 'created') and hasattr(obj, 'modified'):
try:
created = parse_into_datetime(obj.created)
modified = parse_into_datetime(obj.modified)
if modified < created:
validation_results['warnings'].append(
"Modified timestamp is before created timestamp"
)
except ValueError as e:
validation_results['errors'].append(f"Invalid timestamp format: {e}")
# Type-specific validations
if is_sdo(obj):
validation_results['warnings'].append("SDO validation passed")
elif is_sco(obj):
validation_results['warnings'].append("SCO validation passed")
elif is_sro(obj):
# Check relationship references
if hasattr(obj, 'source_ref') and hasattr(obj, 'target_ref'):
source_type = get_type_from_id(obj.source_ref)
target_type = get_type_from_id(obj.target_ref)
if not source_type or not target_type:
validation_results['warnings'].append(
"Invalid source_ref or target_ref format"
)
return validation_results
# Test validation
validation_result = validate_stix_object(indicator)
print(f"Validation result: {validation_result}")
# Safe type checking
unknown_object = {"type": "unknown-type"}
is_sdo_safe = safe_type_check(unknown_object, is_sdo, default=False)
print(f"Unknown object is SDO: {is_sdo_safe}") # FalseInstall with Tessl CLI
npx tessl i tessl/pypi-stix2