From 71c74ca3e90acadcc27f57f2c43baf7f69134271 Mon Sep 17 00:00:00 2001 From: "dmytro.tarnopolskyi" Date: Wed, 6 Dec 2023 09:38:54 +0100 Subject: [PATCH 1/2] added mitre-attack parsing + mitre-attack render to platforms --- siem-converter/app/converter/core/mitre.py | 108 ++++++++++++++++++ .../app/converter/core/mixins/rule.py | 21 ++++ .../converter/core/models/parser_output.py | 4 +- .../converter/platforms/chronicle/const.py | 4 +- .../chronicle/renders/chronicle_rule.py | 2 + .../elasticsearch/renders/detection_rule.py | 46 +++++++- .../logscale/renders/logscale_alert.py | 8 +- .../renders/microsoft_sentinel_rule.py | 16 ++- .../platforms/roota/parsers/roota.py | 12 +- .../platforms/sigma/parsers/sigma.py | 12 +- .../app/converter/platforms/splunk/const.py | 1 + .../platforms/splunk/renders/splunk_alert.py | 16 +++ .../app/converter/tools/singleton_meta.py | 8 ++ siem-converter/app/converter/tools/utils.py | 4 +- siem-converter/app/routers/assistance.py | 2 + 15 files changed, 241 insertions(+), 23 deletions(-) create mode 100644 siem-converter/app/converter/core/mitre.py create mode 100644 siem-converter/app/converter/tools/singleton_meta.py diff --git a/siem-converter/app/converter/core/mitre.py b/siem-converter/app/converter/core/mitre.py new file mode 100644 index 00000000..262950b4 --- /dev/null +++ b/siem-converter/app/converter/core/mitre.py @@ -0,0 +1,108 @@ +import json +import os +import urllib.request +import ssl +from urllib.error import HTTPError + +from app.converter.tools.singleton_meta import SingletonMeta +from const import ROOT_PROJECT_PATH + + +class MitreConfig(metaclass=SingletonMeta): + config_url: str = 'https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json' + mitre_source_types: tuple = ('mitre-attack', ) + tactics: dict = {} + techniques: dict = {} + + @staticmethod + def __revoked_or_deprecated(entry: dict) -> bool: + if entry.get("revoked") or entry.get("x_mitre_deprecated"): + return True + return False + + def __get_mitre_json(self) -> dict: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + + try: + with urllib.request.urlopen(self.config_url, context=ctx) as cti_json: + return json.loads(cti_json.read().decode()) + except HTTPError: + return {} + def update_mitre_config(self) -> None: + if not (mitre_json := self.__get_mitre_json()): + self.__load_mitre_configs_from_files() + return + + tactic_map = {} + technique_map = {} + + # Map the tatics + for entry in mitre_json["objects"]: + if not entry["type"] == "x-mitre-tactic" or self.__revoked_or_deprecated(entry): + continue + for ref in entry["external_references"]: + if ref["source_name"] == 'mitre-attack': + tactic_map[entry["x_mitre_shortname"]] = entry["name"] + self.tactics[entry["name"].replace(' ', '_').lower()] = { + "external_id": ref["external_id"], + "url": ref["url"], + "tactic": entry["name"] + } + break + + # Map the techniques + for entry in mitre_json["objects"]: + if not entry["type"] == "attack-pattern" or self.__revoked_or_deprecated(entry): + continue + if entry.get("x_mitre_is_subtechnique"): + continue + for ref in entry["external_references"]: + if ref["source_name"] in self.mitre_source_types: + technique_map[ref["external_id"]] = entry["name"] + sub_tactics = [] + # Get Mitre Tactics (Kill-Chains) + for tactic in entry["kill_chain_phases"]: + if tactic["kill_chain_name"] in self.mitre_source_types: + # Map the short phase_name to tactic name + sub_tactics.append(tactic_map[tactic["phase_name"]]) + self.techniques[ref["external_id"].lower()] = { + "technique_id": ref["external_id"], + "technique": entry["name"], + "url": ref["url"], + "tactic": sub_tactics + } + break + + ## Map the sub-techniques + for entry in mitre_json["objects"]: + if not entry["type"] == "attack-pattern" or self.__revoked_or_deprecated(entry): + continue + if entry.get("x_mitre_is_subtechnique"): + for ref in entry["external_references"]: + if ref["source_name"] in self.mitre_source_types: + sub_technique_id = ref["external_id"] + sub_technique_name = entry["name"] + parent_technique_name = technique_map[sub_technique_id.split(".")[0]] + sub_technique_name = "{} : {}".format(parent_technique_name, sub_technique_name) + self.techniques[ref["external_id"].lower()] = { + "technique_id": ref["external_id"], + "technique": sub_technique_name, + "url": ref["url"], + } + break + + def __load_mitre_configs_from_files(self) -> None: + with open(os.path.join(ROOT_PROJECT_PATH, 'app/dictionaries/tactics.json'), 'r') as file: + self.tactics = json.load(file) + + with open(os.path.join(ROOT_PROJECT_PATH, 'app/dictionaries/techniques.json'), 'r') as file: + self.techniques = json.load(file) + + def get_tactic(self, tactic: str) -> dict: + tactic = tactic.replace('.', '_') + return self.tactics.get(tactic, {}) + + def get_technique(self, technique_id: str) -> dict: + return self.techniques.get(technique_id, {}) diff --git a/siem-converter/app/converter/core/mixins/rule.py b/siem-converter/app/converter/core/mixins/rule.py index c6d011be..4c6191f8 100644 --- a/siem-converter/app/converter/core/mixins/rule.py +++ b/siem-converter/app/converter/core/mixins/rule.py @@ -1,8 +1,10 @@ import json +from typing import List import yaml from app.converter.core.exceptions.core import InvalidYamlStructure, InvalidJSONStructure +from app.converter.core.mitre import MitreConfig class JsonRuleMixin: @@ -15,9 +17,28 @@ def load_rule(self, text): class YamlRuleMixin: + mitre_config: MitreConfig = MitreConfig() def load_rule(self, text): try: return yaml.safe_load(text) except yaml.YAMLError as err: raise InvalidYamlStructure(error=str(err)) + + def parse_mitre_attack(self, tags: List[str]) -> dict[str, list]: + result = { + 'tactics': [], + 'techniques': [] + } + for tag in tags: + tag = tag.lower() + if tag.startswith('attack.'): + tag = tag[7::] + if tag.startswith('t'): + if technique := self.mitre_config.get_technique(tag): + result['techniques'].append(technique) + else: + if tactic := self.mitre_config.get_tactic(tag): + result['tactics'].append(tactic) + + return result diff --git a/siem-converter/app/converter/core/models/parser_output.py b/siem-converter/app/converter/core/models/parser_output.py index cf3d3ca8..7d9f283f 100644 --- a/siem-converter/app/converter/core/models/parser_output.py +++ b/siem-converter/app/converter/core/models/parser_output.py @@ -17,8 +17,8 @@ def __init__(self, *, license_: str = None, severity: str = None, references: List[str] = None, - tags: List[str] = None, - mitre_attack: List[str] = None, + tags: list[str] = None, + mitre_attack: dict[str, list] = None, status: str = None, false_positives: List[str] = None, source_mapping_ids: List[str] = None diff --git a/siem-converter/app/converter/platforms/chronicle/const.py b/siem-converter/app/converter/platforms/chronicle/const.py index 2e332067..5842e515 100644 --- a/siem-converter/app/converter/platforms/chronicle/const.py +++ b/siem-converter/app/converter/platforms/chronicle/const.py @@ -7,8 +7,10 @@ version = "0.01" rule_id = "" status = "" - severity = "" + tags = "" falsepositives = "" + severity = "" + created = "" events: diff --git a/siem-converter/app/converter/platforms/chronicle/renders/chronicle_rule.py b/siem-converter/app/converter/platforms/chronicle/renders/chronicle_rule.py index 51fffabe..2a7bd928 100644 --- a/siem-converter/app/converter/platforms/chronicle/renders/chronicle_rule.py +++ b/siem-converter/app/converter/platforms/chronicle/renders/chronicle_rule.py @@ -96,4 +96,6 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met rule = rule.replace("", meta_info.severity) rule = rule.replace("", meta_info.status) rule = rule.replace("", ', '.join(meta_info.false_positives)) + rule = rule.replace("", ", ".join(meta_info.tags)) + rule = rule.replace("", str(meta_info.date)) return rule diff --git a/siem-converter/app/converter/platforms/elasticsearch/renders/detection_rule.py b/siem-converter/app/converter/platforms/elasticsearch/renders/detection_rule.py index 9380d6d3..20495889 100644 --- a/siem-converter/app/converter/platforms/elasticsearch/renders/detection_rule.py +++ b/siem-converter/app/converter/platforms/elasticsearch/renders/detection_rule.py @@ -19,6 +19,7 @@ import copy import json +from typing import Union from app.converter.platforms.elasticsearch.const import ELASTICSEARCH_DETECTION_RULE, elasticsearch_rule_details from app.converter.platforms.elasticsearch.mapping import ElasticSearchMappings, elasticsearch_mappings @@ -27,6 +28,7 @@ from app.converter.core.models.platform_details import PlatformDetails from app.converter.core.models.parser_output import MetaInfoContainer from app.converter.tools.utils import concatenate_str, get_mitre_attack_str +from app.converter.core.mitre import MitreConfig class ElasticSearchRuleFieldValue(ElasticSearchFieldValue): @@ -36,6 +38,7 @@ class ElasticSearchRuleFieldValue(ElasticSearchFieldValue): class ElasticSearchRuleRender(ElasticSearchQueryRender): details: PlatformDetails = elasticsearch_rule_details mappings: ElasticSearchMappings = elasticsearch_mappings + mitre: MitreConfig = MitreConfig() or_token = "OR" and_token = "AND" @@ -44,6 +47,46 @@ class ElasticSearchRuleRender(ElasticSearchQueryRender): field_value_map = ElasticSearchRuleFieldValue(or_token=or_token) query_pattern = "{prefix} {query} {functions}" + def __create_mitre_threat(self, mitre_attack: dict) -> Union[list, list[dict]]: + if not mitre_attack.get('techniques'): + return [] + threat = [] + + if not mitre_attack.get('tactics'): + for technique in mitre_attack.get('techniques'): + technique_name = technique['technique'] + if '.' in technique_name: + technique_name = technique_name[:technique_name.index('.')] + threat.append(technique_name) + return threat + + for tactic in mitre_attack['tactics']: + tactic_render = { + 'id': tactic['external_id'], + 'name': tactic['tactic'], + 'reference': tactic['url'] + } + sub_threat = { + 'tactic': tactic_render, + 'framework': 'MITRE ATT&CK', + 'technique': [] + } + for technique in mitre_attack['techniques']: + technique_id = technique['technique_id'].lower() + if '.' in technique_id: + technique_id = technique_id[:technique['technique_id'].index('.')] + main_technique = self.mitre.get_technique(technique_id) + if tactic['tactic'] in main_technique['tactic']: + sub_threat['technique'].append({ + "id": main_technique['technique_id'], + "name": main_technique['technique'], + "reference": main_technique['url'] + }) + if len(sub_threat['technique']) > 0: + threat.append(sub_threat) + + return threat + def finalize_query(self, prefix: str, query: str, functions: str, meta_info: MetaInfoContainer, source_mapping: SourceMapping = None, not_supported_functions: list = None): query = super().finalize_query(prefix=prefix, query=query, functions=functions, meta_info=meta_info) @@ -61,7 +104,8 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met "severity": meta_info.severity, "references": meta_info.references, "license": meta_info.license, - "tags": meta_info.mitre_attack, + "tags": meta_info.tags, + "threat": self.__create_mitre_threat(meta_info.mitre_attack), "false_positives": meta_info.false_positives }) rule_str = json.dumps(rule, indent=4, sort_keys=False, ensure_ascii=False) diff --git a/siem-converter/app/converter/platforms/logscale/renders/logscale_alert.py b/siem-converter/app/converter/platforms/logscale/renders/logscale_alert.py index 4c5ad106..8621328c 100644 --- a/siem-converter/app/converter/platforms/logscale/renders/logscale_alert.py +++ b/siem-converter/app/converter/platforms/logscale/renders/logscale_alert.py @@ -25,7 +25,7 @@ from app.converter.core.mapping import SourceMapping from app.converter.core.models.platform_details import PlatformDetails from app.converter.core.models.parser_output import MetaInfoContainer -from app.converter.tools.utils import get_rule_description_str +from app.converter.tools.utils import get_rule_description_str, get_mitre_attack_str _AUTOGENERATED_TITLE = "Autogenerated Falcon LogScale Alert" @@ -45,10 +45,14 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met rule = copy.deepcopy(DEFAULT_LOGSCALE_ALERT) rule['query']['queryString'] = query rule['name'] = meta_info.title or _AUTOGENERATED_TITLE + mitre_attack = [] + if meta_info.mitre_attack: + mitre_attack = [f"ATTACK.{i['tactic']}" for i in meta_info.mitre_attack.get('tactics', [])] + mitre_attack.extend([f"ATTACK.{i['technique_id']}" for i in meta_info.mitre_attack.get('techniques', [])]) rule['description'] = get_rule_description_str( description=meta_info.description, license=meta_info.license, - mitre_attack=meta_info.mitre_attack, + mitre_attack=mitre_attack, author=meta_info.author ) diff --git a/siem-converter/app/converter/platforms/microsoft/renders/microsoft_sentinel_rule.py b/siem-converter/app/converter/platforms/microsoft/renders/microsoft_sentinel_rule.py index ece9a812..c17ac290 100644 --- a/siem-converter/app/converter/platforms/microsoft/renders/microsoft_sentinel_rule.py +++ b/siem-converter/app/converter/platforms/microsoft/renders/microsoft_sentinel_rule.py @@ -40,6 +40,18 @@ class MicrosoftSentinelRuleRender(MicrosoftSentinelQueryRender): or_token = "or" field_value_map = MicrosoftSentinelRuleFieldValue(or_token=or_token) + def __create_mitre_threat(self, meta_info: MetaInfoContainer) -> tuple[list, list]: + tactics = [] + techniques = [] + + for tactic in meta_info.mitre_attack.get('tactics'): + tactics.append(tactic['tactic']) + + for technique in meta_info.mitre_attack.get('techniques'): + techniques.append(technique['technique_id']) + + return tactics, techniques + def finalize_query(self, prefix: str, query: str, functions: str, meta_info: MetaInfoContainer, source_mapping: SourceMapping = None, not_supported_functions: list = None): query = super().finalize_query(prefix=prefix, query=query, functions=functions, meta_info=meta_info) @@ -52,7 +64,9 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met license=meta_info.license ) rule["severity"] = meta_info.severity - rule["techniques"] = [el.upper() for el in meta_info.mitre_attack] + mitre_tactics, mitre_techniques = self.__create_mitre_threat(meta_info=meta_info) + rule['tactics'] = mitre_tactics + rule['techniques'] = mitre_techniques json_rule = json.dumps(rule, indent=4, sort_keys=False) if not_supported_functions: rendered_not_supported = self.render_not_supported_functions(not_supported_functions) diff --git a/siem-converter/app/converter/platforms/roota/parsers/roota.py b/siem-converter/app/converter/platforms/roota/parsers/roota.py index 73ae474f..6d66621e 100644 --- a/siem-converter/app/converter/platforms/roota/parsers/roota.py +++ b/siem-converter/app/converter/platforms/roota/parsers/roota.py @@ -15,6 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ----------------------------------------------------------------- """ +import re from app.converter.core.exceptions.core import UnsupportedRootAParser, RootARuleValidationException from app.converter.core.mixins.rule import YamlRuleMixin @@ -27,16 +28,19 @@ class RootAParser(YamlRuleMixin): parsers = parser_manager mandatory_fields = {"name", "details", "author", "severity", "mitre-attack", "detection", "references", "license"} - @staticmethod - def __update_meta_info(meta_info: MetaInfoContainer, rule: dict) -> MetaInfoContainer: + def __update_meta_info(self, meta_info: MetaInfoContainer, rule: dict) -> MetaInfoContainer: mitre_attack = rule.get("mitre-attack") or [] - mitre_attack = [i.strip("") for i in mitre_attack.split(",")] if isinstance(mitre_attack, str) else mitre_attack + mitre_tags = [i.strip("") for i in mitre_attack.split(",")] if isinstance(mitre_attack, str) else mitre_attack + mitre_attack = self.parse_mitre_attack(mitre_tags) + rule_tags = rule.get('tags', []) + rule_tags += mitre_tags + meta_info.title = rule.get("name") meta_info.description = rule.get("details") meta_info.id = rule.get("uuid", meta_info.id) meta_info.references = rule.get("references") meta_info.license = rule.get("license", meta_info.license) - meta_info.tags = rule.get("tags", meta_info.tags) + meta_info.tags = rule_tags or meta_info.tags meta_info.mitre_attack = mitre_attack meta_info.date = rule.get("date", meta_info.date) meta_info.author = rule.get("author", meta_info.author) diff --git a/siem-converter/app/converter/platforms/sigma/parsers/sigma.py b/siem-converter/app/converter/platforms/sigma/parsers/sigma.py index 206a5b5c..dcd1c806 100644 --- a/siem-converter/app/converter/platforms/sigma/parsers/sigma.py +++ b/siem-converter/app/converter/platforms/sigma/parsers/sigma.py @@ -38,15 +38,6 @@ class SigmaParser(YamlRuleMixin): mappings: SigmaMappings = sigma_mappings mandatory_fields = {"title", "description", "references", "logsource", "detection"} - @staticmethod - def __parse_mitre_attack(tags: List[str]) -> List[str]: - result = [] - for tag in tags: - if search := re.search(r"[tT]\d{4}(?:\.\d{3})?", tag): - result.append(search.group()) - - return result - @staticmethod def __parse_false_positives(false_positives: Union[str, List[str], None]) -> list: if isinstance(false_positives, str): @@ -62,9 +53,10 @@ def _get_meta_info(self, rule: dict, source_mapping_ids: List[str]) -> MetaInfoC date=rule.get("date"), references=rule.get("references", []), license_=rule.get("license"), - mitre_attack=self.__parse_mitre_attack(rule.get("tags", [])), + mitre_attack=self.parse_mitre_attack(rule.get("tags", [])), severity=rule.get("level"), status=rule.get("status"), + tags=rule.get("tags"), false_positives=self.__parse_false_positives(rule.get("falsepositives")), source_mapping_ids=source_mapping_ids ) diff --git a/siem-converter/app/converter/platforms/splunk/const.py b/siem-converter/app/converter/platforms/splunk/const.py index 8986b8fa..89303431 100644 --- a/siem-converter/app/converter/platforms/splunk/const.py +++ b/siem-converter/app/converter/platforms/splunk/const.py @@ -22,6 +22,7 @@ action.notable.param.rule_title = action.notable.param.rule_description = action.correlationsearch.label = + """ PLATFORM_DETAILS = { diff --git a/siem-converter/app/converter/platforms/splunk/renders/splunk_alert.py b/siem-converter/app/converter/platforms/splunk/renders/splunk_alert.py index 3284db71..c6b233de 100644 --- a/siem-converter/app/converter/platforms/splunk/renders/splunk_alert.py +++ b/siem-converter/app/converter/platforms/splunk/renders/splunk_alert.py @@ -16,6 +16,7 @@ limitations under the License. ----------------------------------------------------------------- """ +import json from app.converter.platforms.splunk.renders.splunk import SplunkQueryRender, SplunkFieldValue from app.converter.platforms.splunk.const import DEFAULT_SPLUNK_ALERT, splunk_alert_details @@ -37,6 +38,14 @@ class SplunkAlertRender(SplunkQueryRender): or_token = "OR" field_value_map = SplunkAlertFieldValue(or_token=or_token) + def __create_mitre_threat(self, meta_info: MetaInfoContainer) -> dict: + techniques = {'mitre_attack': []} + + for technique in meta_info.mitre_attack.get('techniques'): + techniques['mitre_attack'].append(technique['technique_id']) + + return techniques + def finalize_query(self, prefix: str, query: str, functions: str, meta_info: MetaInfoContainer, source_mapping: SourceMapping = None, not_supported_functions: list = None): query = super().finalize_query(prefix=prefix, query=query, functions=functions, meta_info=meta_info) @@ -51,6 +60,13 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met ) rule = rule.replace("", rule_description) + description = f"{meta_info.description or 'Autogenerated Splunk Alert.'} License: {meta_info.license}." + rule = rule.replace("", description) + mitre_techniques = self.__create_mitre_threat(meta_info=meta_info) + if mitre_techniques: + mitre_str = f"action.correlationsearch.annotations = {mitre_techniques})" + rule = rule.replace("", mitre_str) + if not_supported_functions: rendered_not_supported = self.render_not_supported_functions(not_supported_functions) return rule + rendered_not_supported diff --git a/siem-converter/app/converter/tools/singleton_meta.py b/siem-converter/app/converter/tools/singleton_meta.py new file mode 100644 index 00000000..7baa4a6c --- /dev/null +++ b/siem-converter/app/converter/tools/singleton_meta.py @@ -0,0 +1,8 @@ +class SingletonMeta(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] diff --git a/siem-converter/app/converter/tools/utils.py b/siem-converter/app/converter/tools/utils.py index c6a0f1a8..d1df5b22 100644 --- a/siem-converter/app/converter/tools/utils.py +++ b/siem-converter/app/converter/tools/utils.py @@ -1,5 +1,5 @@ import re -from typing import Optional, List +from typing import Optional, List, Union def get_match_group(match: re.Match, group_name: str) -> Optional[str]: @@ -44,7 +44,7 @@ def get_rule_description_str( author: str = None, rule_id: str = None, license: str = None, - mitre_attack: str = None, + mitre_attack: Union[str, list[str]] = None, references: str = None ) -> str: description_str = get_description_str(description) diff --git a/siem-converter/app/routers/assistance.py b/siem-converter/app/routers/assistance.py index 35078e97..1e4bac75 100644 --- a/siem-converter/app/routers/assistance.py +++ b/siem-converter/app/routers/assistance.py @@ -10,6 +10,7 @@ from const import ROOT_PROJECT_PATH +from app.converter.core.mitre import MitreConfig assistance_router = APIRouter() @@ -18,6 +19,7 @@ @asynccontextmanager async def lifespan(app: FastAPI): + MitreConfig().update_mitre_config() with open(os.path.join(ROOT_PROJECT_PATH, 'app/dictionaries/uncoder_meta_info_roota.json'), 'r') as file: json_f = json.load(file) suggestions['roota'] = json_f From 1f943a8c2aaa4265f2f497785fcc104acc2cfd2f Mon Sep 17 00:00:00 2001 From: "dmytro.tarnopolskyi" Date: Wed, 6 Dec 2023 09:59:48 +0100 Subject: [PATCH 2/2] remove mitre-attack tagging from kibana & elastalert --- .../converter/platforms/elasticsearch/renders/elast_alert.py | 3 +-- .../app/converter/platforms/elasticsearch/renders/kibana.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/siem-converter/app/converter/platforms/elasticsearch/renders/elast_alert.py b/siem-converter/app/converter/platforms/elasticsearch/renders/elast_alert.py index 89a6c4ba..ecc2021d 100644 --- a/siem-converter/app/converter/platforms/elasticsearch/renders/elast_alert.py +++ b/siem-converter/app/converter/platforms/elasticsearch/renders/elast_alert.py @@ -52,8 +52,7 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met "", get_rule_description_str( description=meta_info.description, - license=meta_info.license, - mitre_attack=meta_info.mitre_attack + license=meta_info.license ) ) rule = rule.replace("", meta_info.title) diff --git a/siem-converter/app/converter/platforms/elasticsearch/renders/kibana.py b/siem-converter/app/converter/platforms/elasticsearch/renders/kibana.py index ea0fb6b9..2cec1bfa 100644 --- a/siem-converter/app/converter/platforms/elasticsearch/renders/kibana.py +++ b/siem-converter/app/converter/platforms/elasticsearch/renders/kibana.py @@ -53,8 +53,7 @@ def finalize_query(self, prefix: str, query: str, functions: str, meta_info: Met author=meta_info.author, rule_id=meta_info.id, license=meta_info.license, - references=meta_info.references, - mitre_attack=meta_info.mitre_attack + references=meta_info.references ) rule_str = json.dumps(rule, indent=4, sort_keys=False) if not_supported_functions: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy