mal-toolbox 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/METADATA +25 -2
- mal_toolbox-1.1.3.dist-info/RECORD +32 -0
- maltoolbox/__init__.py +6 -7
- maltoolbox/__main__.py +17 -9
- maltoolbox/attackgraph/__init__.py +2 -3
- maltoolbox/attackgraph/attackgraph.py +379 -362
- maltoolbox/attackgraph/node.py +14 -19
- maltoolbox/exceptions.py +7 -10
- maltoolbox/file_utils.py +10 -4
- maltoolbox/language/__init__.py +1 -1
- maltoolbox/language/compiler/__init__.py +4 -4
- maltoolbox/language/compiler/mal_lexer.py +154 -154
- maltoolbox/language/compiler/mal_parser.py +784 -1136
- maltoolbox/language/languagegraph.py +487 -639
- maltoolbox/model.py +64 -77
- maltoolbox/patternfinder/attackgraph_patterns.py +17 -8
- maltoolbox/translators/__init__.py +8 -0
- maltoolbox/translators/networkx.py +42 -0
- maltoolbox/translators/updater.py +18 -25
- maltoolbox/visualization/__init__.py +4 -4
- maltoolbox/visualization/draw_io_utils.py +6 -5
- maltoolbox/visualization/graphviz_utils.py +4 -2
- maltoolbox/visualization/neo4j_utils.py +13 -14
- maltoolbox/visualization/utils.py +2 -3
- mal_toolbox-1.1.1.dist-info/RECORD +0 -32
- maltoolbox/translators/securicad.py +0 -179
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/WHEEL +0 -0
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/entry_points.txt +0 -0
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/licenses/AUTHORS +0 -0
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/licenses/LICENSE +0 -0
- {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/top_level.txt +0 -0
maltoolbox/attackgraph/node.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MAL-Toolbox Attack Graph Node
|
|
1
|
+
"""MAL-Toolbox Attack Graph Node
|
|
3
2
|
"""
|
|
4
3
|
|
|
5
4
|
from __future__ import annotations
|
|
5
|
+
|
|
6
6
|
import copy
|
|
7
7
|
from functools import cached_property
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
|
|
12
|
-
from ..language import LanguageGraphAttackStep
|
|
11
|
+
|
|
12
|
+
from ..language import LanguageGraphAttackStep
|
|
13
13
|
from ..model import ModelAsset
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
class AttackGraphNode:
|
|
16
17
|
"""Node part of AttackGraph"""
|
|
17
18
|
|
|
@@ -19,10 +20,10 @@ class AttackGraphNode:
|
|
|
19
20
|
self,
|
|
20
21
|
node_id: int,
|
|
21
22
|
lg_attack_step: LanguageGraphAttackStep,
|
|
22
|
-
model_asset:
|
|
23
|
-
ttc_dist:
|
|
24
|
-
existence_status:
|
|
25
|
-
full_name:
|
|
23
|
+
model_asset: ModelAsset | None = None,
|
|
24
|
+
ttc_dist: dict | None = None,
|
|
25
|
+
existence_status: bool | None = None,
|
|
26
|
+
full_name: str | None = None
|
|
26
27
|
):
|
|
27
28
|
self.lg_attack_step = lg_attack_step
|
|
28
29
|
self.name = lg_attack_step.name
|
|
@@ -68,12 +69,10 @@ class AttackGraphNode:
|
|
|
68
69
|
|
|
69
70
|
return node_dict
|
|
70
71
|
|
|
71
|
-
|
|
72
72
|
def __repr__(self) -> str:
|
|
73
73
|
return (f'AttackGraphNode(name: "{self.full_name}", id: {self.id}, '
|
|
74
74
|
f'type: {self.type})')
|
|
75
75
|
|
|
76
|
-
|
|
77
76
|
def __deepcopy__(self, memo) -> AttackGraphNode:
|
|
78
77
|
"""Deep copy an attackgraph node
|
|
79
78
|
|
|
@@ -83,15 +82,14 @@ class AttackGraphNode:
|
|
|
83
82
|
should be recreated when deepcopying the attack graph itself.
|
|
84
83
|
|
|
85
84
|
"""
|
|
86
|
-
|
|
87
85
|
# Check if the object is already in the memo dictionary
|
|
88
86
|
if id(self) in memo:
|
|
89
87
|
return memo[id(self)]
|
|
90
88
|
|
|
91
89
|
copied_node = AttackGraphNode(
|
|
92
|
-
node_id
|
|
93
|
-
model_asset
|
|
94
|
-
lg_attack_step
|
|
90
|
+
node_id=self.id,
|
|
91
|
+
model_asset=self.model_asset,
|
|
92
|
+
lg_attack_step=self.lg_attack_step
|
|
95
93
|
)
|
|
96
94
|
|
|
97
95
|
copied_node.tags = copy.deepcopy(self.tags, memo)
|
|
@@ -105,11 +103,9 @@ class AttackGraphNode:
|
|
|
105
103
|
|
|
106
104
|
return copied_node
|
|
107
105
|
|
|
108
|
-
|
|
109
106
|
@property
|
|
110
107
|
def full_name(self) -> str:
|
|
111
|
-
"""
|
|
112
|
-
Return the full name of the attack step. This is normally a
|
|
108
|
+
"""Return the full name of the attack step. This is normally a
|
|
113
109
|
combination of the asset name to which the attack step
|
|
114
110
|
belongs and attack step name itself, but can also be
|
|
115
111
|
explicitly set or a combination of the step id and step name.
|
|
@@ -117,7 +113,7 @@ class AttackGraphNode:
|
|
|
117
113
|
if self._full_name:
|
|
118
114
|
# Explicitly set
|
|
119
115
|
return self._full_name
|
|
120
|
-
|
|
116
|
+
if self.model_asset:
|
|
121
117
|
# Inherited from model asset
|
|
122
118
|
full_name = self.model_asset.name + ':' + self.name
|
|
123
119
|
else:
|
|
@@ -125,7 +121,6 @@ class AttackGraphNode:
|
|
|
125
121
|
full_name = str(self.id) + ':' + self.name
|
|
126
122
|
return full_name
|
|
127
123
|
|
|
128
|
-
|
|
129
124
|
@cached_property
|
|
130
125
|
def info(self) -> dict[str, str]:
|
|
131
126
|
return self.lg_attack_step.info
|
maltoolbox/exceptions.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
class MalToolboxException(Exception):
|
|
2
2
|
"""Base exception for all other maltoolbox exceptions to inherit from."""
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
class LanguageGraphException(MalToolboxException):
|
|
6
6
|
"""Base exception for all language-graph related exceptions."""
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
|
|
9
9
|
class LanguageGraphSuperAssetNotFoundError(LanguageGraphException):
|
|
10
10
|
"""Asset's super asset not found in language graph during attack graph construction."""
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
class LanguageGraphAssociationError(LanguageGraphException):
|
|
14
14
|
"""Error in building an association.
|
|
@@ -16,30 +16,27 @@ class LanguageGraphAssociationError(LanguageGraphException):
|
|
|
16
16
|
For example, right or left-hand side asset of association missing in
|
|
17
17
|
language graph.
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
|
|
21
21
|
class LanguageGraphStepExpressionError(LanguageGraphException):
|
|
22
22
|
"""A target asset cannot be linked with for a step expression."""
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
class AttackGraphException(MalToolboxException):
|
|
26
26
|
"""Base exception for all attack-graph related exceptions."""
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
|
|
29
29
|
class AttackGraphStepExpressionError(AttackGraphException):
|
|
30
30
|
"""A target attack step cannot be linked with for a step expression."""
|
|
31
|
-
pass
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
class ModelException(MalToolboxException):
|
|
35
34
|
"""Base Exception for all Model related exceptions"""
|
|
36
|
-
pass
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
class ModelAssociationException(ModelException):
|
|
40
38
|
"""Exception related to associations in Model"""
|
|
41
|
-
|
|
39
|
+
|
|
42
40
|
|
|
43
41
|
class DuplicateModelAssociationError(ModelException):
|
|
44
42
|
"""Associations should be unique as part of Model"""
|
|
45
|
-
pass
|
maltoolbox/file_utils.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"""Utilty functions for file handling"""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
|
|
4
5
|
import yaml
|
|
5
6
|
|
|
7
|
+
|
|
6
8
|
def save_dict_to_json_file(filename: str, serialized_object: dict) -> None:
|
|
7
9
|
"""Save serialized object to a json file.
|
|
8
10
|
|
|
9
11
|
Arguments:
|
|
12
|
+
---------
|
|
10
13
|
filename - the name of the output file
|
|
11
14
|
data - dict to output as json
|
|
12
|
-
"""
|
|
13
15
|
|
|
16
|
+
"""
|
|
14
17
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
15
18
|
json.dump(serialized_object, f, indent=4)
|
|
16
19
|
|
|
@@ -19,8 +22,10 @@ def save_dict_to_yaml_file(filename: str, serialized_object: dict) -> None:
|
|
|
19
22
|
"""Save serialized object to a yaml file.
|
|
20
23
|
|
|
21
24
|
Arguments:
|
|
25
|
+
---------
|
|
22
26
|
filename - the name of the output file
|
|
23
27
|
data - dict to output as yaml
|
|
28
|
+
|
|
24
29
|
"""
|
|
25
30
|
|
|
26
31
|
class NoAliasSafeDumper(yaml.SafeDumper):
|
|
@@ -33,14 +38,14 @@ def save_dict_to_yaml_file(filename: str, serialized_object: dict) -> None:
|
|
|
33
38
|
|
|
34
39
|
def load_dict_from_yaml_file(filename: str) -> dict:
|
|
35
40
|
"""Open json file and read as dict"""
|
|
36
|
-
with open(filename,
|
|
41
|
+
with open(filename, encoding='utf-8') as file:
|
|
37
42
|
object_dict = yaml.safe_load(file)
|
|
38
43
|
return object_dict
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
def load_dict_from_json_file(filename: str) -> dict:
|
|
42
47
|
"""Open yaml file and read as dict"""
|
|
43
|
-
with open(filename,
|
|
48
|
+
with open(filename, encoding='utf-8') as file:
|
|
44
49
|
object_dict = json.loads(file.read())
|
|
45
50
|
return object_dict
|
|
46
51
|
|
|
@@ -50,10 +55,11 @@ def save_dict_to_file(filename: str, dictionary: dict) -> None:
|
|
|
50
55
|
depending on file extension.
|
|
51
56
|
|
|
52
57
|
Arguments:
|
|
58
|
+
---------
|
|
53
59
|
filename - the name of the output file
|
|
54
60
|
dictionary - the dict to save to the file
|
|
55
|
-
"""
|
|
56
61
|
|
|
62
|
+
"""
|
|
57
63
|
if filename.endswith(('.yml', '.yaml')):
|
|
58
64
|
save_dict_to_yaml_file(filename, dictionary)
|
|
59
65
|
elif filename.endswith('.json'):
|
maltoolbox/language/__init__.py
CHANGED
|
@@ -3,7 +3,7 @@ import sys
|
|
|
3
3
|
from collections.abc import MutableMapping, MutableSequence
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from antlr4 import
|
|
6
|
+
from antlr4 import CommonTokenStream, FileStream, ParseTreeVisitor
|
|
7
7
|
from antlr4.error.ErrorListener import ConsoleErrorListener
|
|
8
8
|
|
|
9
9
|
from .mal_lexer import malLexer
|
|
@@ -16,7 +16,7 @@ from .mal_parser import malParser
|
|
|
16
16
|
|
|
17
17
|
def patched_antrl_syntax_error(self, recognizer, offendingSymbol, line, column, msg, e):
|
|
18
18
|
file = patched_antrl_syntax_error.file
|
|
19
|
-
print(f"{file}:{
|
|
19
|
+
print(f"{file}:{line!s}:{column!s}: {msg}", file=sys.stderr)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
ConsoleErrorListener.syntaxError = patched_antrl_syntax_error
|
|
@@ -88,9 +88,9 @@ class MalCompiler(ParseTreeVisitor):
|
|
|
88
88
|
included_file = self.compile(value)
|
|
89
89
|
for k, v in langspec.items():
|
|
90
90
|
if isinstance(v, MutableMapping):
|
|
91
|
-
|
|
91
|
+
v.update(included_file.get(k, {}))
|
|
92
92
|
if isinstance(v, MutableSequence) and k in included_file:
|
|
93
|
-
|
|
93
|
+
v.extend(included_file[k])
|
|
94
94
|
|
|
95
95
|
for key in ("categories", "assets", "associations"):
|
|
96
96
|
unique = []
|