Files
sigma-rules/detection_rules/schemas/v7_8.py
T
Justin Ibarra 065bcd8018 Refresh ATT&CK data to v7.2 and expand threat validation (#330)
* refresh to latest ATT&CK 7.2
* add new unit test to further validate threat mappings
* updated threat mappings in rules to reflect changes
* new func to download and refresh mitre data based on version
2020-09-23 22:03:29 -08:00

133 lines
5.5 KiB
Python

# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
"""Definitions for rule metadata and schemas."""
import jsl
from .base import BaseApiSchema, MarkdownField
from ..attack import tactics, tactics_map, technique_lookup
INTERVAL_PATTERN = r'\d+[mshd]'
MITRE_URL_PATTERN = r'https://attack.mitre.org/{type}/T[A-Z0-9]+/'
# kibana/.../siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts
# /detection_engine/routes/schemas/schemas.ts
# rule_id is required here
# output_index is not allowed (and instead the space index must be used)
# immutable defaults to true instead of to false and if it is there can only be true
# enabled defaults to false instead of true
# version is a required field that must exist
# rule types
MACHINE_LEARNING = 'machine_learning'
SAVED_QUERY = 'saved_query'
QUERY = 'query'
class Filters(jsl.Document):
"""Intermediate schema for handling DSL-like filters."""
class FilterMetadata(jsl.Document):
negate = jsl.BooleanField()
type = jsl.StringField()
key = jsl.StringField()
value = jsl.StringField()
disabled = jsl.BooleanField()
indexRefName = jsl.StringField()
alias = jsl.StringField() # null acceptable
params = jsl.DictField(properties={'query': jsl.StringField()})
class FilterQuery(jsl.Document):
match = jsl.DictField({
'event.action': jsl.DictField(properties={
'query': jsl.StringField(),
'type': jsl.StringField()
})
})
class FilterState(jsl.Document):
store = jsl.StringField()
class FilterExists(jsl.Document):
field = jsl.StringField()
exists = jsl.DocumentField(FilterExists)
meta = jsl.DocumentField(FilterMetadata)
state = jsl.DocumentField(FilterState, name='$state')
query = jsl.DocumentField(FilterQuery)
class Threat(jsl.Document):
"""Threat framework mapping such as MITRE ATT&CK."""
class ThreatTactic(jsl.Document):
id = jsl.StringField(enum=tactics_map.values())
name = jsl.StringField(enum=tactics)
reference = jsl.StringField(MITRE_URL_PATTERN.format(type='tactics'))
class ThreatTechnique(jsl.Document):
id = jsl.StringField(enum=list(technique_lookup))
name = jsl.StringField()
reference = jsl.StringField(MITRE_URL_PATTERN.format(type='techniques'))
framework = jsl.StringField(default='MITRE ATT&CK', required=True)
tactic = jsl.DocumentField(ThreatTactic, required=True)
technique = jsl.ArrayField(jsl.DocumentField(ThreatTechnique), required=True)
class ApiSchema78(BaseApiSchema):
"""Schema for siem rule in API format."""
STACK_VERSION = "7.8"
RULE_TYPES = [MACHINE_LEARNING, SAVED_QUERY, QUERY]
actions = jsl.ArrayField(required=False)
description = jsl.StringField(required=True)
# api defaults to false if blank
enabled = jsl.BooleanField(default=False, required=False)
# _ required since `from` is a reserved word in python
from_ = jsl.StringField(required=False, default='now-6m', name='from')
false_positives = jsl.ArrayField(jsl.StringField(), required=False)
filters = jsl.ArrayField(jsl.DocumentField(Filters))
interval = jsl.StringField(pattern=INTERVAL_PATTERN, default='5m', required=False)
max_signals = jsl.IntField(minimum=1, required=False, default=100) # cap a max?
meta = jsl.DictField(required=False)
name = jsl.StringField(required=True)
note = MarkdownField(required=False)
# output_index =jsl.StringField(required=False) # this is NOT allowed!
references = jsl.ArrayField(jsl.StringField(), required=False)
risk_score = jsl.IntField(minimum=0, maximum=100, required=True, default=21)
severity = jsl.StringField(enum=['low', 'medium', 'high', 'critical'], default='low', required=True)
tags = jsl.ArrayField(jsl.StringField(), required=False)
throttle = jsl.StringField(required=False)
timeline_id = jsl.StringField(required=False)
timeline_title = jsl.StringField(required=False)
to = jsl.StringField(required=False, default='now')
type = jsl.StringField(enum=[MACHINE_LEARNING, QUERY, SAVED_QUERY], required=True)
threat = jsl.ArrayField(jsl.DocumentField(Threat), required=False, min_items=1)
with jsl.Scope(MACHINE_LEARNING) as ml_scope:
ml_scope.anomaly_threshold = jsl.IntField(required=True, minimum=0)
ml_scope.machine_learning_job_id = jsl.StringField(required=True)
ml_scope.type = jsl.StringField(enum=[MACHINE_LEARNING], required=True, default=MACHINE_LEARNING)
with jsl.Scope(SAVED_QUERY) as saved_id_scope:
saved_id_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
saved_id_scope.saved_id = jsl.StringField(required=True)
saved_id_scope.type = jsl.StringField(enum=[SAVED_QUERY], required=True, default=SAVED_QUERY)
with jsl.Scope(QUERY) as query_scope:
query_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
# this is not required per the API but we will enforce it here
query_scope.language = jsl.StringField(enum=['kuery', 'lucene'], required=True, default='kuery')
query_scope.query = jsl.StringField(required=True)
query_scope.type = jsl.StringField(enum=[QUERY], required=True, default=QUERY)
with jsl.Scope(jsl.DEFAULT_ROLE) as default_scope:
default_scope.type = type