mal-toolbox 0.1.12__py3-none-any.whl → 0.3.0__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-0.1.12.dist-info → mal_toolbox-0.3.0.dist-info}/METADATA +43 -25
- mal_toolbox-0.3.0.dist-info/RECORD +29 -0
- mal_toolbox-0.3.0.dist-info/entry_points.txt +2 -0
- maltoolbox/__init__.py +38 -57
- maltoolbox/__main__.py +43 -14
- maltoolbox/attackgraph/__init__.py +1 -1
- maltoolbox/attackgraph/analyzers/apriori.py +10 -6
- maltoolbox/attackgraph/attacker.py +26 -13
- maltoolbox/attackgraph/attackgraph.py +431 -355
- maltoolbox/attackgraph/node.py +72 -54
- maltoolbox/attackgraph/query.py +4 -2
- maltoolbox/file_utils.py +4 -8
- maltoolbox/ingestors/neo4j.py +146 -157
- maltoolbox/language/__init__.py +10 -2
- maltoolbox/language/compiler/__init__.py +485 -17
- maltoolbox/language/compiler/mal_lexer.py +172 -152
- maltoolbox/language/compiler/mal_parser.py +1370 -663
- maltoolbox/language/languagegraph.py +1096 -545
- maltoolbox/model.py +312 -485
- maltoolbox/translators/securicad.py +164 -163
- maltoolbox/translators/updater.py +231 -108
- mal_toolbox-0.1.12.dist-info/RECORD +0 -32
- maltoolbox/default.conf +0 -17
- maltoolbox/language/classes_factory.py +0 -243
- maltoolbox/language/compiler/mal_visitor.py +0 -416
- maltoolbox/wrappers.py +0 -62
- {mal_toolbox-0.1.12.dist-info → mal_toolbox-0.3.0.dist-info}/AUTHORS +0 -0
- {mal_toolbox-0.1.12.dist-info → mal_toolbox-0.3.0.dist-info}/LICENSE +0 -0
- {mal_toolbox-0.1.12.dist-info → mal_toolbox-0.3.0.dist-info}/WHEEL +0 -0
- {mal_toolbox-0.1.12.dist-info → mal_toolbox-0.3.0.dist-info}/top_level.txt +0 -0
maltoolbox/attackgraph/node.py
CHANGED
|
@@ -1,55 +1,68 @@
|
|
|
1
1
|
"""
|
|
2
|
-
MAL-Toolbox Attack Graph Node
|
|
2
|
+
MAL-Toolbox Attack Graph Node
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
import copy
|
|
7
|
-
from
|
|
8
|
-
from typing import
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
from . import Attacker
|
|
13
|
+
from ..language import LanguageGraphAttackStep, Detector
|
|
14
|
+
from ..model import ModelAsset
|
|
11
15
|
|
|
12
|
-
@dataclass
|
|
13
16
|
class AttackGraphNode:
|
|
14
17
|
"""Node part of AttackGraph"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
node_id: int,
|
|
22
|
+
lg_attack_step: LanguageGraphAttackStep,
|
|
23
|
+
model_asset: Optional[ModelAsset] = None,
|
|
24
|
+
defense_status: Optional[float] = None,
|
|
25
|
+
existence_status: Optional[bool] = None
|
|
26
|
+
):
|
|
27
|
+
self.lg_attack_step = lg_attack_step
|
|
28
|
+
self.name = lg_attack_step.name
|
|
29
|
+
self.type = lg_attack_step.type
|
|
30
|
+
self.ttc = lg_attack_step.ttc
|
|
31
|
+
self.tags = lg_attack_step.tags
|
|
32
|
+
self.detectors = lg_attack_step.detectors
|
|
33
|
+
|
|
34
|
+
self.id = node_id
|
|
35
|
+
self.model_asset = model_asset
|
|
36
|
+
self.defense_status = defense_status
|
|
37
|
+
self.existence_status = existence_status
|
|
38
|
+
|
|
39
|
+
self.children: set[AttackGraphNode] = set()
|
|
40
|
+
self.parents: set[AttackGraphNode] = set()
|
|
41
|
+
self.is_viable: bool = True
|
|
42
|
+
self.is_necessary: bool = True
|
|
43
|
+
self.compromised_by: set[Attacker] = set()
|
|
44
|
+
self.extras: dict = {}
|
|
33
45
|
|
|
34
46
|
def to_dict(self) -> dict:
|
|
35
47
|
"""Convert node to dictionary"""
|
|
36
48
|
node_dict: dict = {
|
|
37
49
|
'id': self.id,
|
|
38
50
|
'type': self.type,
|
|
51
|
+
'lang_graph_attack_step': self.lg_attack_step.full_name,
|
|
39
52
|
'name': self.name,
|
|
40
53
|
'ttc': self.ttc,
|
|
41
|
-
'children': {
|
|
42
|
-
|
|
54
|
+
'children': {child.id: child.full_name for child in
|
|
55
|
+
self.children},
|
|
56
|
+
'parents': {parent.id: parent.full_name for parent in
|
|
57
|
+
self.parents},
|
|
43
58
|
'compromised_by': [attacker.name for attacker in \
|
|
44
59
|
self.compromised_by]
|
|
45
60
|
}
|
|
46
61
|
|
|
47
|
-
for
|
|
48
|
-
node_dict
|
|
49
|
-
|
|
50
|
-
node_dict['
|
|
51
|
-
if self.asset is not None:
|
|
52
|
-
node_dict['asset'] = str(self.asset.name)
|
|
62
|
+
for detector in self.detectors.values():
|
|
63
|
+
node_dict.setdefault('detectors', {})[detector.name] = detector.to_dict()
|
|
64
|
+
if self.model_asset is not None:
|
|
65
|
+
node_dict['asset'] = str(self.model_asset.name)
|
|
53
66
|
if self.defense_status is not None:
|
|
54
67
|
node_dict['defense_status'] = str(self.defense_status)
|
|
55
68
|
if self.existence_status is not None:
|
|
@@ -58,17 +71,18 @@ class AttackGraphNode:
|
|
|
58
71
|
node_dict['is_viable'] = str(self.is_viable)
|
|
59
72
|
if self.is_necessary is not None:
|
|
60
73
|
node_dict['is_necessary'] = str(self.is_necessary)
|
|
61
|
-
if self.mitre_info is not None:
|
|
62
|
-
node_dict['mitre_info'] = str(self.mitre_info)
|
|
63
74
|
if self.tags:
|
|
64
|
-
node_dict['tags'] =
|
|
75
|
+
node_dict['tags'] = list(self.tags)
|
|
65
76
|
if self.extras:
|
|
66
77
|
node_dict['extras'] = self.extras
|
|
67
78
|
|
|
68
79
|
return node_dict
|
|
69
80
|
|
|
81
|
+
|
|
70
82
|
def __repr__(self) -> str:
|
|
71
|
-
return
|
|
83
|
+
return (f'AttackGraphNode(name: "{self.full_name}", id: {self.id}, '
|
|
84
|
+
f'type: {self.type})')
|
|
85
|
+
|
|
72
86
|
|
|
73
87
|
def __deepcopy__(self, memo) -> AttackGraphNode:
|
|
74
88
|
"""Deep copy an attackgraph node
|
|
@@ -85,33 +99,26 @@ class AttackGraphNode:
|
|
|
85
99
|
return memo[id(self)]
|
|
86
100
|
|
|
87
101
|
copied_node = AttackGraphNode(
|
|
88
|
-
self.
|
|
89
|
-
self.
|
|
90
|
-
self.
|
|
91
|
-
self.id,
|
|
92
|
-
self.asset,
|
|
93
|
-
[],
|
|
94
|
-
[],
|
|
95
|
-
self.defense_status,
|
|
96
|
-
self.existence_status,
|
|
97
|
-
self.is_viable,
|
|
98
|
-
self.is_necessary,
|
|
99
|
-
[],
|
|
100
|
-
self.mitre_info,
|
|
101
|
-
[],
|
|
102
|
-
{},
|
|
103
|
-
{}
|
|
102
|
+
node_id = self.id,
|
|
103
|
+
model_asset = self.model_asset,
|
|
104
|
+
lg_attack_step = self.lg_attack_step
|
|
104
105
|
)
|
|
105
106
|
|
|
106
107
|
copied_node.tags = copy.deepcopy(self.tags, memo)
|
|
107
|
-
copied_node.attributes = copy.deepcopy(self.attributes, memo)
|
|
108
108
|
copied_node.extras = copy.deepcopy(self.extras, memo)
|
|
109
|
+
copied_node.ttc = copy.deepcopy(self.ttc, memo)
|
|
110
|
+
|
|
111
|
+
copied_node.defense_status = self.defense_status
|
|
112
|
+
copied_node.existence_status = self.existence_status
|
|
113
|
+
copied_node.is_viable = self.is_viable
|
|
114
|
+
copied_node.is_necessary = self.is_necessary
|
|
109
115
|
|
|
110
116
|
# Remember that self was already copied
|
|
111
117
|
memo[id(self)] = copied_node
|
|
112
118
|
|
|
113
119
|
return copied_node
|
|
114
120
|
|
|
121
|
+
|
|
115
122
|
def is_compromised(self) -> bool:
|
|
116
123
|
"""
|
|
117
124
|
Return True if any attackers have compromised this node.
|
|
@@ -119,6 +126,7 @@ class AttackGraphNode:
|
|
|
119
126
|
"""
|
|
120
127
|
return len(self.compromised_by) > 0
|
|
121
128
|
|
|
129
|
+
|
|
122
130
|
def is_compromised_by(self, attacker: Attacker) -> bool:
|
|
123
131
|
"""
|
|
124
132
|
Return True if the attacker given as an argument has compromised this
|
|
@@ -130,6 +138,7 @@ class AttackGraphNode:
|
|
|
130
138
|
"""
|
|
131
139
|
return attacker in self.compromised_by
|
|
132
140
|
|
|
141
|
+
|
|
133
142
|
def compromise(self, attacker: Attacker) -> None:
|
|
134
143
|
"""
|
|
135
144
|
Have the attacker given as a parameter compromise this node.
|
|
@@ -139,6 +148,7 @@ class AttackGraphNode:
|
|
|
139
148
|
"""
|
|
140
149
|
attacker.compromise(self)
|
|
141
150
|
|
|
151
|
+
|
|
142
152
|
def undo_compromise(self, attacker: Attacker) -> None:
|
|
143
153
|
"""
|
|
144
154
|
Remove the attacker given as a parameter from the list of attackers
|
|
@@ -150,6 +160,7 @@ class AttackGraphNode:
|
|
|
150
160
|
"""
|
|
151
161
|
attacker.undo_compromise(self)
|
|
152
162
|
|
|
163
|
+
|
|
153
164
|
def is_enabled_defense(self) -> bool:
|
|
154
165
|
"""
|
|
155
166
|
Return True if this node is a defense node and it is enabled and not
|
|
@@ -160,6 +171,7 @@ class AttackGraphNode:
|
|
|
160
171
|
'suppress' not in self.tags and \
|
|
161
172
|
self.defense_status == 1.0
|
|
162
173
|
|
|
174
|
+
|
|
163
175
|
def is_available_defense(self) -> bool:
|
|
164
176
|
"""
|
|
165
177
|
Return True if this node is a defense node and it is not fully enabled
|
|
@@ -169,6 +181,7 @@ class AttackGraphNode:
|
|
|
169
181
|
'suppress' not in self.tags and \
|
|
170
182
|
self.defense_status != 1.0
|
|
171
183
|
|
|
184
|
+
|
|
172
185
|
@property
|
|
173
186
|
def full_name(self) -> str:
|
|
174
187
|
"""
|
|
@@ -176,8 +189,13 @@ class AttackGraphNode:
|
|
|
176
189
|
asset name to which the attack step belongs and attack step name
|
|
177
190
|
itself.
|
|
178
191
|
"""
|
|
179
|
-
if self.
|
|
180
|
-
full_name = self.
|
|
192
|
+
if self.model_asset:
|
|
193
|
+
full_name = self.model_asset.name + ':' + self.name
|
|
181
194
|
else:
|
|
182
195
|
full_name = str(self.id) + ':' + self.name
|
|
183
196
|
return full_name
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@cached_property
|
|
200
|
+
def info(self) -> dict[str, str]:
|
|
201
|
+
return self.lg_attack_step.info
|
maltoolbox/attackgraph/query.py
CHANGED
|
@@ -186,7 +186,8 @@ def get_defense_surface(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
|
186
186
|
graph - the attack graph
|
|
187
187
|
"""
|
|
188
188
|
logger.debug('Get the defense surface.')
|
|
189
|
-
return [node for node in graph.nodes
|
|
189
|
+
return [node for node in graph.nodes.values()
|
|
190
|
+
if node.is_available_defense()]
|
|
190
191
|
|
|
191
192
|
def get_enabled_defenses(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
192
193
|
"""
|
|
@@ -197,4 +198,5 @@ def get_enabled_defenses(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
|
197
198
|
graph - the attack graph
|
|
198
199
|
"""
|
|
199
200
|
logger.debug('Get the enabled defenses.')
|
|
200
|
-
return [node for node in graph.nodes
|
|
201
|
+
return [node for node in graph.nodes.values()
|
|
202
|
+
if node.is_enabled_defense()]
|
maltoolbox/file_utils.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import yaml
|
|
5
|
-
from python_jsonschema_objects.literals import LiteralValue
|
|
6
5
|
|
|
7
6
|
def save_dict_to_json_file(filename: str, serialized_object: dict) -> None:
|
|
8
7
|
"""Save serialized object to a json file.
|
|
@@ -24,15 +23,12 @@ def save_dict_to_yaml_file(filename: str, serialized_object: dict) -> None:
|
|
|
24
23
|
data - dict to output as yaml
|
|
25
24
|
"""
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
lambda dumper, data: dumper.represent_data(data._value),
|
|
31
|
-
yaml.SafeDumper
|
|
32
|
-
)
|
|
26
|
+
class NoAliasSafeDumper(yaml.SafeDumper):
|
|
27
|
+
def ignore_aliases(self, data):
|
|
28
|
+
return True
|
|
33
29
|
|
|
34
30
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
35
|
-
yaml.dump(serialized_object, f, Dumper=
|
|
31
|
+
yaml.dump(serialized_object, f, Dumper=NoAliasSafeDumper)
|
|
36
32
|
|
|
37
33
|
|
|
38
34
|
def load_dict_from_yaml_file(filename: str) -> dict:
|
maltoolbox/ingestors/neo4j.py
CHANGED
|
@@ -7,9 +7,6 @@ import logging
|
|
|
7
7
|
|
|
8
8
|
from py2neo import Graph, Node, Relationship, Subgraph
|
|
9
9
|
|
|
10
|
-
from ..model import AttackerAttachment, Model
|
|
11
|
-
from ..language import LanguageGraph, LanguageClassesFactory
|
|
12
|
-
|
|
13
10
|
logger = logging.getLogger(__name__)
|
|
14
11
|
|
|
15
12
|
def ingest_attack_graph(graph,
|
|
@@ -90,26 +87,24 @@ def ingest_model(model,
|
|
|
90
87
|
nodes = {}
|
|
91
88
|
rels = []
|
|
92
89
|
|
|
93
|
-
for asset in model.assets:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for assoc in model.associations:
|
|
101
|
-
firstElementName, secondElementName = assoc._properties.keys()
|
|
102
|
-
firstElements = getattr(assoc, firstElementName)
|
|
103
|
-
secondElements = getattr(assoc, secondElementName)
|
|
104
|
-
for first_asset in firstElements:
|
|
105
|
-
for second_asset in secondElements:
|
|
106
|
-
rels.append(Relationship(nodes[str(first_asset.id)],
|
|
107
|
-
str(firstElementName),
|
|
108
|
-
nodes[str(second_asset.id)]))
|
|
109
|
-
rels.append(Relationship(nodes[str(second_asset.id)],
|
|
110
|
-
str(secondElementName),
|
|
111
|
-
nodes[str(first_asset.id)]))
|
|
90
|
+
for asset in model.assets.values():
|
|
91
|
+
nodes[str(asset.id)] = Node(
|
|
92
|
+
str(asset.type),
|
|
93
|
+
name=str(asset.name),
|
|
94
|
+
asset_id=str(asset.id),
|
|
95
|
+
type=str(asset.type)
|
|
96
|
+
)
|
|
112
97
|
|
|
98
|
+
for asset in model.assets.values():
|
|
99
|
+
for fieldname, other_assets in asset.associated_assets.items():
|
|
100
|
+
for other_asset in other_assets:
|
|
101
|
+
rels.append(
|
|
102
|
+
Relationship(
|
|
103
|
+
nodes[str(asset.id)],
|
|
104
|
+
str(fieldname),
|
|
105
|
+
nodes[str(other_asset.id)]
|
|
106
|
+
)
|
|
107
|
+
)
|
|
113
108
|
|
|
114
109
|
subgraph = Subgraph(list(nodes.values()), rels)
|
|
115
110
|
|
|
@@ -118,138 +113,132 @@ def ingest_model(model,
|
|
|
118
113
|
g.commit(tx)
|
|
119
114
|
|
|
120
115
|
|
|
121
|
-
def get_model(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
right_asset,
|
|
251
|
-
left_asset
|
|
252
|
-
)):
|
|
253
|
-
instance_model.add_association(assoc)
|
|
254
|
-
|
|
255
|
-
return instance_model
|
|
116
|
+
# def get_model(
|
|
117
|
+
# uri: str,
|
|
118
|
+
# username: str,
|
|
119
|
+
# password: str,
|
|
120
|
+
# dbname: str,
|
|
121
|
+
# lang_graph: LanguageGraph,
|
|
122
|
+
# ) -> Model:
|
|
123
|
+
# """Load a model from Neo4j"""
|
|
124
|
+
|
|
125
|
+
# g = Graph(uri=uri, user=username, password=password, name=dbname)
|
|
126
|
+
|
|
127
|
+
# instance_model = Model('Neo4j imported model', lang_graph)
|
|
128
|
+
# # Get all assets
|
|
129
|
+
# assets_results = g.run('MATCH (a) WHERE a.type IS NOT NULL RETURN DISTINCT a').data()
|
|
130
|
+
# for asset in assets_results:
|
|
131
|
+
# asset_data = dict(asset['a'])
|
|
132
|
+
# logger.debug(
|
|
133
|
+
# 'Loading asset from Neo4j instance:\n%s', str(asset_data)
|
|
134
|
+
# )
|
|
135
|
+
# if asset_data['type'] == 'Attacker':
|
|
136
|
+
# attacker_id = int(asset_data['asset_id'])
|
|
137
|
+
# attacker = AttackerAttachment()
|
|
138
|
+
# attacker.entry_points = []
|
|
139
|
+
# instance_model.add_attacker(attacker, attacker_id = attacker_id)
|
|
140
|
+
# continue
|
|
141
|
+
|
|
142
|
+
# asset_id = int(asset_data['asset_id'])
|
|
143
|
+
|
|
144
|
+
# #TODO Process defense values when they are included in Neo4j
|
|
145
|
+
# instance_model.add_asset(asset_data['type'], asset_id=asset_id)
|
|
146
|
+
|
|
147
|
+
# # Get all relationships
|
|
148
|
+
# assocs_results = g.run(
|
|
149
|
+
# 'MATCH (a)-[r1]->(b),(a)<-[r2]-(b) WHERE a.type IS NOT NULL RETURN DISTINCT a, r1, r2, b'
|
|
150
|
+
# ).data()
|
|
151
|
+
|
|
152
|
+
# for assoc in assocs_results:
|
|
153
|
+
# left_field = list(assoc['r1'].types())[0]
|
|
154
|
+
# right_field = list(assoc['r2'].types())[0]
|
|
155
|
+
# left_asset = dict(assoc['a'])
|
|
156
|
+
# right_asset = dict(assoc['b'])
|
|
157
|
+
|
|
158
|
+
# logger.debug(
|
|
159
|
+
# 'Load association ("%s", "%s", "%s", "%s") from Neo4j instance.',
|
|
160
|
+
# left_field, right_field, left_asset["type"], right_asset["type"]
|
|
161
|
+
# )
|
|
162
|
+
|
|
163
|
+
# left_id = int(left_asset['asset_id'])
|
|
164
|
+
# right_id = int(right_asset['asset_id'])
|
|
165
|
+
|
|
166
|
+
# attacker_id = None
|
|
167
|
+
# if left_field == 'firstSteps':
|
|
168
|
+
# attacker_id = right_id
|
|
169
|
+
# target_id = left_id
|
|
170
|
+
# target_prop = right_field
|
|
171
|
+
# elif right_field == 'firstSteps':
|
|
172
|
+
# attacker_id = left_id
|
|
173
|
+
# target_id = right_id
|
|
174
|
+
# target_prop = left_field
|
|
175
|
+
|
|
176
|
+
# if attacker_id is not None:
|
|
177
|
+
# attacker = instance_model.get_attacker_by_id(attacker_id)
|
|
178
|
+
# if not attacker:
|
|
179
|
+
# msg = 'Failed to find attacker with id %s in model!'
|
|
180
|
+
# logger.error(msg, attacker_id)
|
|
181
|
+
# raise LookupError(msg % attacker_id)
|
|
182
|
+
# target_asset = instance_model.get_asset_by_id(target_id)
|
|
183
|
+
# if not target_asset:
|
|
184
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
185
|
+
# logger.error(msg, target_id)
|
|
186
|
+
# raise LookupError(msg % target_id)
|
|
187
|
+
# attacker.entry_points.append((target_asset,
|
|
188
|
+
# [target_prop]))
|
|
189
|
+
# continue
|
|
190
|
+
|
|
191
|
+
# left_asset = instance_model.get_asset_by_id(left_id)
|
|
192
|
+
# if left_asset is None:
|
|
193
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
194
|
+
# logger.error(msg, left_id)
|
|
195
|
+
# raise LookupError(msg % left_id)
|
|
196
|
+
# right_asset = instance_model.get_asset_by_id(right_id)
|
|
197
|
+
# if right_asset is None:
|
|
198
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
199
|
+
# logger.error(msg, right_id)
|
|
200
|
+
# raise LookupError(msg % right_id)
|
|
201
|
+
|
|
202
|
+
# assoc = lang_graph.get_association_by_fields_and_assets(
|
|
203
|
+
# left_field,
|
|
204
|
+
# right_field,
|
|
205
|
+
# left_asset.type,
|
|
206
|
+
# right_asset.type)
|
|
207
|
+
|
|
208
|
+
# if not assoc:
|
|
209
|
+
# logger.error(
|
|
210
|
+
# 'Failed to find ("%s", "%s", "%s", "%s")'
|
|
211
|
+
# 'association in language specification!',
|
|
212
|
+
# left_asset.type, right_asset.type,
|
|
213
|
+
# left_field, right_field
|
|
214
|
+
# )
|
|
215
|
+
# return None
|
|
216
|
+
|
|
217
|
+
# logger.debug('Found "%s" association.', assoc.name)
|
|
218
|
+
|
|
219
|
+
# assoc_name = lang_classes_factory.get_association_by_signature(
|
|
220
|
+
# assoc.name,
|
|
221
|
+
# left_asset.type,
|
|
222
|
+
# right_asset.type
|
|
223
|
+
# )
|
|
224
|
+
|
|
225
|
+
# if not assoc_name:
|
|
226
|
+
# msg = 'Failed to find \"%s\" association in language specification!'
|
|
227
|
+
# logger.error(msg, assoc.name)
|
|
228
|
+
# raise LookupError(msg % assoc.name)
|
|
229
|
+
|
|
230
|
+
# assoc = getattr(lang_classes_factory.ns, assoc_name)()
|
|
231
|
+
# setattr(assoc, left_field, [left_asset])
|
|
232
|
+
# setattr(assoc, right_field, [right_asset])
|
|
233
|
+
# if not (instance_model.association_exists_between_assets(
|
|
234
|
+
# assoc_name,
|
|
235
|
+
# left_asset,
|
|
236
|
+
# right_asset
|
|
237
|
+
# ) or instance_model.association_exists_between_assets(
|
|
238
|
+
# assoc_name,
|
|
239
|
+
# right_asset,
|
|
240
|
+
# left_asset
|
|
241
|
+
# )):
|
|
242
|
+
# instance_model.add_association(assoc)
|
|
243
|
+
|
|
244
|
+
# return instance_model
|
maltoolbox/language/__init__.py
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
"""Contains tools to process MAL languages"""
|
|
2
2
|
|
|
3
|
-
from .languagegraph import
|
|
4
|
-
|
|
3
|
+
from .languagegraph import (
|
|
4
|
+
Context,
|
|
5
|
+
Detector,
|
|
6
|
+
ExpressionsChain,
|
|
7
|
+
LanguageGraph,
|
|
8
|
+
LanguageGraphAsset,
|
|
9
|
+
LanguageGraphAssociation,
|
|
10
|
+
LanguageGraphAttackStep,
|
|
11
|
+
disaggregate_attack_step_full_name,
|
|
12
|
+
)
|