Skip to content

Commit 6b1d79d

Browse files
committed
upd mitre process
1 parent 874f580 commit 6b1d79d

File tree

17 files changed

+168
-80
lines changed

17 files changed

+168
-80
lines changed

uncoder-core/app/translator/core/mitre.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,36 @@
22
import os
33
import ssl
44
import urllib.request
5+
from dataclasses import dataclass
56
from json import JSONDecodeError
7+
from typing import Optional, Union
68
from urllib.error import HTTPError
79

810
from app.translator.tools.singleton_meta import SingletonMeta
911
from const import ROOT_PROJECT_PATH
1012

1113

14+
@dataclass
15+
class MitreTechniqueContainer:
16+
technique_id: str
17+
name: str
18+
url: str
19+
tactic: list[str]
20+
21+
22+
@dataclass
23+
class MitreTacticContainer:
24+
external_id: str
25+
url: str
26+
name: str
27+
28+
29+
@dataclass
30+
class MitreInfoContainer:
31+
tactics: Union[list[MitreTacticContainer], list]
32+
techniques: Union[list[MitreTechniqueContainer], list]
33+
34+
1235
class MitreConfig(metaclass=SingletonMeta):
1336
config_url: str = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"
1437
mitre_source_types: tuple = ("mitre-attack",)
@@ -116,9 +139,34 @@ def __load_mitre_configs_from_files(self) -> None:
116139
except JSONDecodeError:
117140
self.techniques = {}
118141

119-
def get_tactic(self, tactic: str) -> dict:
142+
def get_tactic(self, tactic: str) -> Optional[MitreTacticContainer]:
120143
tactic = tactic.replace(".", "_")
121-
return self.tactics.get(tactic, {})
122-
123-
def get_technique(self, technique_id: str) -> dict:
124-
return self.techniques.get(technique_id, {})
144+
if tactic_found := self.tactics.get(tactic):
145+
return MitreTacticContainer(
146+
external_id=tactic_found["external_id"], url=tactic_found["url"], name=tactic_found["tactic"]
147+
)
148+
149+
def get_technique(self, technique_id: str) -> Optional[MitreTechniqueContainer]:
150+
if technique_found := self.techniques.get(technique_id):
151+
return MitreTechniqueContainer(
152+
technique_id=technique_found["technique_id"],
153+
name=technique_found["technique"],
154+
url=technique_found["url"],
155+
tactic=technique_found["tactic"],
156+
)
157+
158+
def get_mitre_info(
159+
self, tactics: Optional[list[str]] = None, techniques: Optional[list[str]] = None
160+
) -> Optional[MitreInfoContainer]:
161+
tactics_list = []
162+
techniques_list = []
163+
if tactics:
164+
for tactic in tactics:
165+
if tactic_found := self.get_tactic(tactic=tactic.lower()):
166+
tactics_list.append(tactic_found)
167+
if techniques:
168+
for technique in techniques:
169+
if technique_found := self.get_technique(technique_id=technique.lower()):
170+
techniques_list.append(technique_found)
171+
if tactics_list or techniques_list:
172+
return MitreInfoContainer(tactics=tactics_list, techniques=techniques_list)

uncoder-core/app/translator/core/mixins/rule.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import json
2-
from typing import Union
2+
from typing import Optional, Union
33

44
import xmltodict
55
import yaml
66

77
from app.translator.core.exceptions.core import InvalidJSONStructure, InvalidXMLStructure, InvalidYamlStructure
8-
from app.translator.core.mitre import MitreConfig
8+
from app.translator.core.mitre import MitreConfig, MitreInfoContainer
99

1010

1111
class JsonRuleMixin:
@@ -29,18 +29,20 @@ def load_rule(text: str) -> dict:
2929
except yaml.YAMLError as err:
3030
raise InvalidYamlStructure(error=str(err)) from err
3131

32-
def parse_mitre_attack(self, tags: list[str]) -> dict[str, list]:
33-
result = {"tactics": [], "techniques": []}
32+
def parse_mitre_attack(self, tags: list[str]) -> Optional[MitreInfoContainer]:
33+
parsed_techniques = []
34+
parsed_tactics = []
3435
for tag in set(tags):
3536
tag = tag.lower()
3637
if tag.startswith("attack."):
3738
tag = tag[7::]
3839
if tag.startswith("t"):
3940
if technique := self.mitre_config.get_technique(tag):
40-
result["techniques"].append(technique)
41+
parsed_techniques.append(technique)
4142
elif tactic := self.mitre_config.get_tactic(tag):
42-
result["tactics"].append(tactic)
43-
return result
43+
parsed_tactics.append(tactic)
44+
if parsed_techniques or parsed_tactics:
45+
return MitreInfoContainer(tactics=parsed_tactics, techniques=parsed_techniques)
4446

4547

4648
class XMLRuleMixin:

uncoder-core/app/translator/core/models/query_container.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.translator.core.const import QUERY_TOKEN_TYPE
77
from app.translator.core.custom_types.meta_info import SeverityType
88
from app.translator.core.mapping import DEFAULT_MAPPING_NAME
9+
from app.translator.core.mitre import MitreInfoContainer
910
from app.translator.core.models.functions.base import ParsedFunctions
1011
from app.translator.core.models.query_tokens.field import Field
1112

@@ -17,15 +18,15 @@ def __init__(
1718
id_: Optional[str] = None,
1819
title: Optional[str] = None,
1920
description: Optional[str] = None,
20-
author: Optional[str] = None,
21+
author: Optional[list[str]] = None,
2122
date: Optional[str] = None,
2223
output_table_fields: Optional[list[Field]] = None,
2324
query_fields: Optional[list[Field]] = None,
2425
license_: Optional[str] = None,
2526
severity: Optional[str] = None,
2627
references: Optional[list[str]] = None,
2728
tags: Optional[list[str]] = None,
28-
mitre_attack: Optional[dict[str, list]] = None,
29+
mitre_attack: Optional[MitreInfoContainer] = None,
2930
raw_mitre_attack: Optional[list[str]] = None,
3031
status: Optional[str] = None,
3132
false_positives: Optional[list[str]] = None,
@@ -44,7 +45,7 @@ def __init__(
4445
self.severity = severity or SeverityType.low
4546
self.references = references or []
4647
self.tags = tags or []
47-
self.mitre_attack = mitre_attack or {}
48+
self.mitre_attack = mitre_attack or None
4849
self.raw_mitre_attack = raw_mitre_attack or []
4950
self.status = status or "stable"
5051
self.false_positives = false_positives or []

uncoder-core/app/translator/platforms/chronicle/renders/chronicle_rule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def finalize_query(
119119
rule = DEFAULT_CHRONICLE_SECURITY_RULE.replace("<query_placeholder>", query)
120120
rule = rule.replace("<title_place_holder>", self.prepare_title(meta_info.title) or _AUTOGENERATED_TEMPLATE)
121121
description = meta_info.description or _AUTOGENERATED_TEMPLATE
122-
rule = rule.replace("<author_place_holder>", meta_info.author)
122+
rule = rule.replace("<author_place_holder>", ", ".join(meta_info.author))
123123
rule = rule.replace("<description_place_holder>", description)
124124
rule = rule.replace("<licence_place_holder>", meta_info.license)
125125
rule = rule.replace("<rule_id_place_holder>", meta_info.id)

uncoder-core/app/translator/platforms/elasticsearch/parsers/detection_rule.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ class ElasticSearchRuleParser(ElasticSearchQueryParser, JsonRuleMixin):
3131

3232
def parse_raw_query(self, text: str, language: str) -> RawQueryContainer:
3333
rule = self.load_rule(text=text)
34-
mitre_attack = {"tactics": [], "techniques": []}
3534
parsed_description = parse_rule_description_str(rule.get("description", ""))
35+
36+
mitre_attack = None
3637
if rule_mitre_attack_data := rule.get("threat"):
37-
for threat_data in rule_mitre_attack_data:
38-
if technique := self.mitre_config.get_technique(threat_data["technique"][0]["id"].lower()):
39-
mitre_attack["techniques"].append(technique)
40-
if tactic := self.mitre_config.get_tactic(threat_data["tactic"]["name"].replace(" ", "_").lower()):
41-
mitre_attack["tactics"].append(tactic)
38+
mitre_attack = self.mitre_config.get_mitre_info(
39+
tactics=[
40+
threat_data["tactic"]["name"].replace(" ", "_").lower() for threat_data in rule_mitre_attack_data
41+
],
42+
techniques=[threat_data["technique"][0]["id"].lower() for threat_data in rule_mitre_attack_data],
43+
)
4244

4345
return RawQueryContainer(
4446
query=rule["query"],
@@ -52,6 +54,6 @@ def parse_raw_query(self, text: str, language: str) -> RawQueryContainer:
5254
severity=rule.get("severity"),
5355
license_=parsed_description.get("license"),
5456
tags=rule.get("tags"),
55-
mitre_attack=mitre_attack if mitre_attack["tactics"] or mitre_attack["techniques"] else None,
57+
mitre_attack=mitre_attack,
5658
),
5759
)

uncoder-core/app/translator/platforms/elasticsearch/renders/detection_rule.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from typing import Optional, Union
2323

2424
from app.translator.core.mapping import SourceMapping
25-
from app.translator.core.mitre import MitreConfig
25+
from app.translator.core.mitre import MitreConfig, MitreInfoContainer
2626
from app.translator.core.models.platform_details import PlatformDetails
2727
from app.translator.core.models.query_container import MetaInfoContainer
2828
from app.translator.managers import render_manager
@@ -53,25 +53,25 @@ class ElasticSearchRuleRender(ElasticSearchQueryRender):
5353

5454
field_value_render = ElasticSearchRuleFieldValue(or_token=or_token)
5555

56-
def __create_mitre_threat(self, mitre_attack: dict) -> Union[list, list[dict]]:
57-
if not mitre_attack.get("techniques"):
56+
def __create_mitre_threat(self, mitre_attack: MitreInfoContainer) -> Union[list, list[dict]]:
57+
if not mitre_attack.techniques:
5858
return []
5959
threat = []
6060

61-
for tactic in mitre_attack["tactics"]:
62-
tactic_render = {"id": tactic["external_id"], "name": tactic["tactic"], "reference": tactic["url"]}
61+
for tactic in mitre_attack.tactics:
62+
tactic_render = {"id": tactic.external_id, "name": tactic.name, "reference": tactic.url}
6363
sub_threat = {"tactic": tactic_render, "framework": "MITRE ATT&CK", "technique": []}
64-
for technique in mitre_attack["techniques"]:
65-
technique_id = technique["technique_id"].lower()
64+
for technique in mitre_attack.techniques:
65+
technique_id = technique.technique_id.lower()
6666
if "." in technique_id:
67-
technique_id = technique_id[: technique["technique_id"].index(".")]
67+
technique_id = technique_id[: technique.technique_id.index(".")]
6868
main_technique = self.mitre.get_technique(technique_id)
69-
if tactic["tactic"] in main_technique["tactic"]:
69+
if tactic.name in main_technique.tactic:
7070
sub_threat["technique"].append(
7171
{
72-
"id": main_technique["technique_id"],
73-
"name": main_technique["technique"],
74-
"reference": main_technique["url"],
72+
"id": main_technique.technique_id,
73+
"name": main_technique.name,
74+
"reference": main_technique.url,
7575
}
7676
)
7777
if len(sub_threat["technique"]) > 0:
@@ -100,7 +100,7 @@ def finalize_query(
100100
"description": meta_info.description or rule["description"] or _AUTOGENERATED_TEMPLATE,
101101
"name": meta_info.title or _AUTOGENERATED_TEMPLATE,
102102
"rule_id": meta_info.id,
103-
"author": [meta_info.author],
103+
"author": meta_info.author,
104104
"severity": meta_info.severity,
105105
"references": meta_info.references,
106106
"license": meta_info.license,

uncoder-core/app/translator/platforms/elasticsearch/renders/elast_alert.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,18 @@ def finalize_query(
6666
) -> str:
6767
query = super().finalize_query(prefix=prefix, query=query, functions=functions)
6868
rule = ELASTICSEARCH_ALERT.replace("<query_placeholder>", query)
69+
mitre_attack = []
70+
if meta_info and meta_info.mitre_attack:
71+
mitre_attack.extend([technique.technique_id for technique in meta_info.mitre_attack.techniques])
72+
mitre_attack.extend([tactic.name for tactic in meta_info.mitre_attack.tactics])
6973
rule = rule.replace(
7074
"<description_place_holder>",
7175
get_rule_description_str(
7276
author=meta_info.author,
7377
description=meta_info.description or _AUTOGENERATED_TEMPLATE,
7478
license_=meta_info.license,
7579
rule_id=meta_info.id,
80+
mitre_attack=mitre_attack,
7681
),
7782
)
7883
rule = rule.replace("<title_place_holder>", meta_info.title or _AUTOGENERATED_TEMPLATE)

uncoder-core/app/translator/platforms/elasticsearch/renders/kibana.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,17 @@ def finalize_query(
6868
rule["_source"]["kibanaSavedObjectMeta"]["searchSourceJSON"] = dumped_rule
6969
rule["_source"]["title"] = meta_info.title or _AUTOGENERATED_TEMPLATE
7070
descr = meta_info.description or rule["_source"]["description"] or _AUTOGENERATED_TEMPLATE
71+
mitre_attack = []
72+
if meta_info and meta_info.mitre_attack:
73+
mitre_attack.extend([technique.technique_id for technique in meta_info.mitre_attack.techniques])
74+
mitre_attack.extend([tactic.name for tactic in meta_info.mitre_attack.tactics])
7175
rule["_source"]["description"] = get_rule_description_str(
7276
description=descr,
7377
author=meta_info.author,
7478
rule_id=meta_info.id,
7579
license_=meta_info.license,
7680
references=meta_info.references,
81+
mitre_attack=mitre_attack,
7782
)
7883
rule_str = json.dumps(rule, indent=4, sort_keys=False)
7984
rule_str = self.wrap_with_unmapped_fields(rule_str, unmapped_fields)

uncoder-core/app/translator/platforms/elasticsearch/renders/xpack_watcher.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def finalize_query(
6262
) -> str:
6363
query = super().finalize_query(prefix=prefix, query=query, functions=functions)
6464
rule = copy.deepcopy(XPACK_WATCHER_RULE)
65+
mitre_attack = []
66+
if meta_info and meta_info.mitre_attack:
67+
mitre_attack.extend([technique.technique_id for technique in meta_info.mitre_attack.techniques])
68+
mitre_attack.extend([tactic.name for tactic in meta_info.mitre_attack.tactics])
6569
rule["metadata"].update(
6670
{
6771
"query": query,
@@ -70,9 +74,9 @@ def finalize_query(
7074
description=meta_info.description or _AUTOGENERATED_TEMPLATE,
7175
author=meta_info.author,
7276
license_=meta_info.license,
73-
mitre_attack=meta_info.mitre_attack,
77+
mitre_attack=mitre_attack,
7478
),
75-
"tags": meta_info.mitre_attack,
79+
"tags": meta_info.tags,
7680
}
7781
)
7882
rule["input"]["search"]["request"]["body"]["query"]["bool"]["must"][0]["query_string"]["query"] = query

uncoder-core/app/translator/platforms/forti_siem/renders/forti_siem_rule.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from app.translator.core.custom_types.values import ValueType
2525
from app.translator.core.exceptions.render import UnsupportedRenderMethod
2626
from app.translator.core.mapping import SourceMapping
27+
from app.translator.core.mitre import MitreInfoContainer
2728
from app.translator.core.models.platform_details import PlatformDetails
2829
from app.translator.core.models.query_container import MetaInfoContainer, TokenizedQueryContainer
2930
from app.translator.core.models.query_tokens.field_value import FieldValue
@@ -363,16 +364,18 @@ def generate_rule_name(title: str) -> str:
363364
return re.sub(r'[\'"()+,]*', "", rule_name)
364365

365366
@staticmethod
366-
def get_mitre_info(mitre_attack: dict) -> tuple[str, str]:
367+
def get_mitre_info(mitre_attack: Union[MitreInfoContainer, None]) -> tuple[str, str]:
368+
if not mitre_attack:
369+
return "", ""
367370
tactics = set()
368371
techniques = set()
369-
for tactic in mitre_attack.get("tactics", []):
370-
if tactic_name := tactic.get("tactic"):
372+
for tactic in mitre_attack.tactics:
373+
if tactic_name := tactic.name:
371374
tactics.add(tactic_name)
372375

373-
for tech in mitre_attack.get("techniques", []):
374-
techniques.add(tech["technique_id"])
375-
tactics = tactics.union(set(tech.get("tactic", [])))
376+
for tech in mitre_attack.techniques:
377+
techniques.add(tech.technique_id)
378+
tactics = tactics.union(set(tech.tactic))
376379

377380
return ", ".join(sorted(tactics)), ", ".join(sorted(techniques))
378381

0 commit comments

Comments
 (0)