mal-toolbox 0.0.28__py3-none-any.whl → 0.1.12__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.0.28.dist-info → mal_toolbox-0.1.12.dist-info}/METADATA +60 -28
- mal_toolbox-0.1.12.dist-info/RECORD +32 -0
- {mal_toolbox-0.0.28.dist-info → mal_toolbox-0.1.12.dist-info}/WHEEL +1 -1
- maltoolbox/__init__.py +31 -31
- maltoolbox/__main__.py +80 -4
- maltoolbox/attackgraph/__init__.py +8 -0
- maltoolbox/attackgraph/analyzers/__init__.py +0 -0
- maltoolbox/attackgraph/analyzers/apriori.py +173 -27
- maltoolbox/attackgraph/attacker.py +84 -25
- maltoolbox/attackgraph/attackgraph.py +503 -215
- maltoolbox/attackgraph/node.py +92 -31
- maltoolbox/attackgraph/query.py +125 -19
- maltoolbox/default.conf +8 -7
- maltoolbox/exceptions.py +45 -0
- maltoolbox/file_utils.py +66 -0
- maltoolbox/ingestors/__init__.py +0 -0
- maltoolbox/ingestors/neo4j.py +95 -84
- maltoolbox/language/__init__.py +4 -0
- maltoolbox/language/classes_factory.py +145 -64
- maltoolbox/language/{lexer_parser/__main__.py → compiler/__init__.py} +5 -12
- maltoolbox/language/{lexer_parser → compiler}/mal_lexer.py +1 -1
- maltoolbox/language/{lexer_parser → compiler}/mal_parser.py +1 -1
- maltoolbox/language/{lexer_parser → compiler}/mal_visitor.py +4 -5
- maltoolbox/language/languagegraph.py +569 -168
- maltoolbox/model.py +858 -0
- maltoolbox/translators/__init__.py +0 -0
- maltoolbox/translators/securicad.py +76 -52
- maltoolbox/translators/updater.py +132 -0
- maltoolbox/wrappers.py +62 -0
- mal_toolbox-0.0.28.dist-info/RECORD +0 -26
- maltoolbox/cl_parser.py +0 -89
- maltoolbox/language/specification.py +0 -265
- maltoolbox/main.py +0 -84
- maltoolbox/model/model.py +0 -282
- {mal_toolbox-0.0.28.dist-info → mal_toolbox-0.1.12.dist-info}/AUTHORS +0 -0
- {mal_toolbox-0.0.28.dist-info → mal_toolbox-0.1.12.dist-info}/LICENSE +0 -0
- {mal_toolbox-0.0.28.dist-info → mal_toolbox-0.1.12.dist-info}/top_level.txt +0 -0
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MAL-Toolbox Language Specification Module
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
import json
|
|
8
|
-
import zipfile
|
|
9
|
-
import copy
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
def load_language_specification_from_mar(mar_archive: str) -> dict:
|
|
14
|
-
"""
|
|
15
|
-
Read a ".mar" archive provided by malc (https://github.com/mal-lang/malc)
|
|
16
|
-
and return a dictionary representing a MAL language structure
|
|
17
|
-
|
|
18
|
-
Arguments:
|
|
19
|
-
mar_archive - the path to a ".mar" archive
|
|
20
|
-
|
|
21
|
-
Return:
|
|
22
|
-
A dictionary representing the language specification
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
logger.info(f'Load language specfication from \'{mar_archive}\' mar archive.')
|
|
26
|
-
with zipfile.ZipFile(mar_archive, 'r') as archive:
|
|
27
|
-
langspec = archive.read('langspec.json')
|
|
28
|
-
return json.loads(langspec)
|
|
29
|
-
|
|
30
|
-
def load_language_specification_from_json(json_file: str) -> dict:
|
|
31
|
-
"""
|
|
32
|
-
Read a MAL language JSON specification file
|
|
33
|
-
|
|
34
|
-
Arguments:
|
|
35
|
-
file_spec - a language specification file that can be for example
|
|
36
|
-
provided by malc (https://github.com/mal-lang/malc)
|
|
37
|
-
|
|
38
|
-
Return:
|
|
39
|
-
A dictionary representing the language specification
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
logger.info(f'Load language specfication from \'{json_file}\'.')
|
|
43
|
-
with open(json_file, 'r', encoding='utf-8') as spec:
|
|
44
|
-
data = spec.read()
|
|
45
|
-
return json.loads(data)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def save_language_specification_to_json(lang_spec: dict, filename: str) -> dict:
|
|
49
|
-
"""
|
|
50
|
-
Save a MAL language specification dictionary to a JSON file
|
|
51
|
-
|
|
52
|
-
Arguments:
|
|
53
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
54
|
-
filename - the JSON filename where the language specification will
|
|
55
|
-
be written
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
logger.info(f'Save language specfication to {filename}.')
|
|
59
|
-
|
|
60
|
-
with open(filename, 'w', encoding='utf-8') as file:
|
|
61
|
-
json.dump(lang_spec, file, indent=4)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_attacks_for_class(lang_spec: dict, asset_type: str) -> dict:
|
|
65
|
-
"""
|
|
66
|
-
Get all Attack Steps for a specific Class
|
|
67
|
-
|
|
68
|
-
Arguments:
|
|
69
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
70
|
-
asset_type - a string representing the class for which we want to list
|
|
71
|
-
the possible attack steps
|
|
72
|
-
|
|
73
|
-
Return:
|
|
74
|
-
A dictionary representing the set of possible attacks for the specified
|
|
75
|
-
class. Each key in the dictionary is an attack name and is associated
|
|
76
|
-
with a dictionary containing other characteristics of the attack such as
|
|
77
|
-
type of attack, TTC distribution, child attack steps and other information
|
|
78
|
-
"""
|
|
79
|
-
attacks = {}
|
|
80
|
-
asset = next((asset for asset in lang_spec['assets'] if asset['name'] == \
|
|
81
|
-
asset_type), None)
|
|
82
|
-
if not asset:
|
|
83
|
-
logger.error(f'Failed to find asset type {asset_type} when '\
|
|
84
|
-
'looking for attack steps.')
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
logger.debug(f'Get attack steps for {asset["name"]} asset from '\
|
|
88
|
-
'language specification.')
|
|
89
|
-
if asset['superAsset']:
|
|
90
|
-
logger.debug(f'Asset extends another one, fetch the superclass '\
|
|
91
|
-
'attack steps for it.')
|
|
92
|
-
attacks = get_attacks_for_class(lang_spec, asset['superAsset'])
|
|
93
|
-
|
|
94
|
-
for attack in asset['attackSteps']:
|
|
95
|
-
if attack['name'] not in attacks:
|
|
96
|
-
attacks[attack['name']] = copy.deepcopy(attack)
|
|
97
|
-
else:
|
|
98
|
-
if not attack['reaches']:
|
|
99
|
-
# This attack step does not lead to any attack steps
|
|
100
|
-
continue
|
|
101
|
-
if attack['reaches']['overrides'] == True:
|
|
102
|
-
attacks[attack['name']] = copy.deepcopy(attack)
|
|
103
|
-
else:
|
|
104
|
-
attacks[attack['name']]['reaches']['stepExpressions'].\
|
|
105
|
-
extend(attack['reaches']['stepExpressions'])
|
|
106
|
-
|
|
107
|
-
return attacks
|
|
108
|
-
|
|
109
|
-
def get_associations_for_class(lang_spec: dict, asset_type: str) -> dict:
|
|
110
|
-
"""
|
|
111
|
-
Get all Associations for a specific Class
|
|
112
|
-
|
|
113
|
-
Arguments:
|
|
114
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
115
|
-
asset_type - a string representing the class for which we want to list
|
|
116
|
-
the associations
|
|
117
|
-
|
|
118
|
-
Return:
|
|
119
|
-
A dictionary representing the set of associations for the specified
|
|
120
|
-
class. Each key in the dictionary is an attack name and is associated
|
|
121
|
-
with a dictionary containing other characteristics of the attack such as
|
|
122
|
-
type of attack, TTC distribution, child attack steps and other information
|
|
123
|
-
"""
|
|
124
|
-
logger.debug(f'Get associations for {asset_type} asset from '\
|
|
125
|
-
'language specification.')
|
|
126
|
-
associations = []
|
|
127
|
-
|
|
128
|
-
asset = next((asset for asset in lang_spec['assets'] if asset['name'] == \
|
|
129
|
-
asset_type), None)
|
|
130
|
-
if not asset:
|
|
131
|
-
logger.error(f'Failed to find asset type {asset_type} when '\
|
|
132
|
-
'looking for associations.')
|
|
133
|
-
return None
|
|
134
|
-
|
|
135
|
-
if asset['superAsset']:
|
|
136
|
-
logger.debug(f'Asset extends another one, fetch the superclass '\
|
|
137
|
-
'associations for it.')
|
|
138
|
-
associations.extend(get_associations_for_class(lang_spec,
|
|
139
|
-
asset['superAsset']))
|
|
140
|
-
assoc_iter = (assoc for assoc in lang_spec['associations'] \
|
|
141
|
-
if assoc['leftAsset'] == asset_type or \
|
|
142
|
-
assoc['rightAsset'] == asset_type)
|
|
143
|
-
assoc = next(assoc_iter, None)
|
|
144
|
-
while (assoc):
|
|
145
|
-
associations.append(assoc)
|
|
146
|
-
assoc = next(assoc_iter, None)
|
|
147
|
-
|
|
148
|
-
return associations
|
|
149
|
-
|
|
150
|
-
def get_association_by_fields_and_assets(lang_spec: dict,
|
|
151
|
-
first_field: str,
|
|
152
|
-
second_field: str,
|
|
153
|
-
first_asset: str,
|
|
154
|
-
second_asset: str) -> str:
|
|
155
|
-
"""
|
|
156
|
-
Get an association based on its field names and asset types
|
|
157
|
-
|
|
158
|
-
Arguments:
|
|
159
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
160
|
-
first_field - a string containing the first field
|
|
161
|
-
second_field - a string containing the second field
|
|
162
|
-
first_asset - a string representing the first asset type
|
|
163
|
-
second_asset - a string representing the second asset type
|
|
164
|
-
|
|
165
|
-
Return:
|
|
166
|
-
The name of the association matching the fieldnames and asset types.
|
|
167
|
-
None if there is no match.
|
|
168
|
-
"""
|
|
169
|
-
for assoc in lang_spec['associations']:
|
|
170
|
-
logger.debug(f'Compare (\"{first_asset}\",'
|
|
171
|
-
f'\"{first_field}\",'
|
|
172
|
-
f'\"{second_asset}\",'
|
|
173
|
-
f'\"{second_field}\") to '
|
|
174
|
-
f'(\"{assoc["leftAsset"]}\",'
|
|
175
|
-
f'\"{assoc["leftField"]}\",'
|
|
176
|
-
f'\"{assoc["rightAsset"]}\",'
|
|
177
|
-
f'\"{assoc["rightField"]}\").')
|
|
178
|
-
|
|
179
|
-
# If the asset and fields match either way we accept it as a match.
|
|
180
|
-
if assoc['leftField'] == first_field and \
|
|
181
|
-
assoc['rightField'] == second_field and \
|
|
182
|
-
extends_asset(lang_spec, first_asset, assoc['leftAsset']) and \
|
|
183
|
-
extends_asset(lang_spec, second_asset, assoc['rightAsset']):
|
|
184
|
-
return assoc['name']
|
|
185
|
-
if assoc['leftField'] == second_field and \
|
|
186
|
-
assoc['rightField'] == first_field and \
|
|
187
|
-
extends_asset(lang_spec, second_asset, assoc['leftAsset']) and \
|
|
188
|
-
extends_asset(lang_spec, first_asset, assoc['rightAsset']):
|
|
189
|
-
return assoc['name']
|
|
190
|
-
|
|
191
|
-
return None
|
|
192
|
-
|
|
193
|
-
def get_variable_for_class_by_name(lang_spec: dict, asset_type: str,
|
|
194
|
-
variable_name:str) -> dict:
|
|
195
|
-
"""
|
|
196
|
-
Get a variables for a specific asset type by name.
|
|
197
|
-
NOTE: Variables are the ones specified in MAL through `let` statements
|
|
198
|
-
|
|
199
|
-
Arguments:
|
|
200
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
201
|
-
asset_type - a string representing the type of asset which contains
|
|
202
|
-
the variable
|
|
203
|
-
variable_name - the name of the variable to search for
|
|
204
|
-
|
|
205
|
-
Return:
|
|
206
|
-
A dictionary representing the step expressions for the specified variable.
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
asset = next((asset for asset in lang_spec['assets'] if asset['name'] == \
|
|
210
|
-
asset_type), None)
|
|
211
|
-
if not asset:
|
|
212
|
-
logger.error(f'Failed to find asset type {asset_type} when '\
|
|
213
|
-
'looking for variable.')
|
|
214
|
-
return None
|
|
215
|
-
|
|
216
|
-
variable_dict = next((variable for variable in \
|
|
217
|
-
asset['variables'] if variable['name'] == variable_name), None)
|
|
218
|
-
if not variable_dict:
|
|
219
|
-
if asset['superAsset']:
|
|
220
|
-
variable_dict = get_variable_for_class_by_name(lang_spec,
|
|
221
|
-
asset['superAsset'], variable_name)
|
|
222
|
-
if variable_dict:
|
|
223
|
-
return variable_dict
|
|
224
|
-
else:
|
|
225
|
-
logger.error(f'Failed to find variable {variable_name} in '\
|
|
226
|
-
f'{asset_type}\'s language specification.')
|
|
227
|
-
return None
|
|
228
|
-
|
|
229
|
-
return variable_dict['stepExpression']
|
|
230
|
-
|
|
231
|
-
def extends_asset(lang_spec: dict, asset, target_asset):
|
|
232
|
-
"""
|
|
233
|
-
Check if an asset extends the target asset through inheritance.
|
|
234
|
-
|
|
235
|
-
Arguments:
|
|
236
|
-
lang_spec - a dictionary containing the MAL language specification
|
|
237
|
-
asset - the asset name we wish to evaluate
|
|
238
|
-
target_asset - the target asset name we wish to evaluate if it
|
|
239
|
-
is extended
|
|
240
|
-
|
|
241
|
-
Return:
|
|
242
|
-
True if this asset extends the target_asset via inheritance.
|
|
243
|
-
False otherwise.
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
logger.debug(f'Check if {asset} extends {target_asset} via inheritance.')
|
|
247
|
-
|
|
248
|
-
if asset == target_asset:
|
|
249
|
-
return True
|
|
250
|
-
|
|
251
|
-
asset_dict = next((asset_info for asset_info in lang_spec['assets'] \
|
|
252
|
-
if asset_info['name'] == asset), None)
|
|
253
|
-
if not asset_dict:
|
|
254
|
-
logger.error(f'Failed to find asset type {asset} when '\
|
|
255
|
-
'looking for variable.')
|
|
256
|
-
return False
|
|
257
|
-
if asset_dict['superAsset']:
|
|
258
|
-
if asset_dict['superAsset'] == target_asset:
|
|
259
|
-
return True
|
|
260
|
-
else:
|
|
261
|
-
return extends_asset(lang_spec, asset_dict['superAsset'],
|
|
262
|
-
target_asset)
|
|
263
|
-
|
|
264
|
-
return False
|
|
265
|
-
|
maltoolbox/main.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MAL-Toolbox Main Module
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
import json
|
|
9
|
-
|
|
10
|
-
import maltoolbox
|
|
11
|
-
import maltoolbox.cl_parser
|
|
12
|
-
from maltoolbox.language import classes_factory
|
|
13
|
-
from maltoolbox.language import specification
|
|
14
|
-
from maltoolbox.model import model
|
|
15
|
-
from maltoolbox.attackgraph import attackgraph
|
|
16
|
-
from maltoolbox.attackgraph.analyzers import apriori
|
|
17
|
-
from maltoolbox.ingestors import neo4j
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
def main():
|
|
22
|
-
"""Main routine of maltoolbox command line interface."""
|
|
23
|
-
args = maltoolbox.cl_parser.parse_args(sys.argv[1:])
|
|
24
|
-
cmd_params = vars(args)
|
|
25
|
-
logger.info('Received the following command line parameters:\n' +
|
|
26
|
-
json.dumps(cmd_params, indent = 2))
|
|
27
|
-
|
|
28
|
-
match(cmd_params['command']):
|
|
29
|
-
case 'gen_ag':
|
|
30
|
-
model_filename = cmd_params['model']
|
|
31
|
-
langspec_filename = cmd_params['language']
|
|
32
|
-
|
|
33
|
-
# Load language specification and generate classes based on it.
|
|
34
|
-
lang_spec = specification. \
|
|
35
|
-
load_language_specification_from_mar(langspec_filename)
|
|
36
|
-
if maltoolbox.log_configs['langspec_file']:
|
|
37
|
-
specification.save_language_specification_to_json(lang_spec,
|
|
38
|
-
maltoolbox.log_configs['langspec_file'])
|
|
39
|
-
lang_classes_factory = \
|
|
40
|
-
classes_factory.LanguageClassesFactory(lang_spec)
|
|
41
|
-
lang_classes_factory.create_classes()
|
|
42
|
-
|
|
43
|
-
# Load instance model.
|
|
44
|
-
instance_model = model.Model('Test model', lang_spec,
|
|
45
|
-
lang_classes_factory)
|
|
46
|
-
instance_model.load_from_file(model_filename)
|
|
47
|
-
if maltoolbox.log_configs['model_file']:
|
|
48
|
-
instance_model.save_to_file( \
|
|
49
|
-
maltoolbox.log_configs['model_file'])
|
|
50
|
-
|
|
51
|
-
graph = attackgraph.AttackGraph()
|
|
52
|
-
result = graph.generate_graph(lang_spec, instance_model)
|
|
53
|
-
if result > 0:
|
|
54
|
-
logger.error('Attack graph generation failed!')
|
|
55
|
-
print('Attack graph generation failed!')
|
|
56
|
-
exit(result)
|
|
57
|
-
|
|
58
|
-
apriori.calculate_viability_and_necessity(graph)
|
|
59
|
-
|
|
60
|
-
graph.attach_attackers(instance_model)
|
|
61
|
-
|
|
62
|
-
if maltoolbox.log_configs['attackgraph_file']:
|
|
63
|
-
graph.save_to_file(
|
|
64
|
-
maltoolbox.log_configs['attackgraph_file'])
|
|
65
|
-
|
|
66
|
-
if cmd_params['neo4j']:
|
|
67
|
-
# Injest instance model and attack graph into Neo4J.
|
|
68
|
-
logger.debug('Ingest model graph into Neo4J database.')
|
|
69
|
-
neo4j.ingest_model(instance_model,
|
|
70
|
-
maltoolbox.neo4j_configs['uri'],
|
|
71
|
-
maltoolbox.neo4j_configs['username'],
|
|
72
|
-
maltoolbox.neo4j_configs['password'],
|
|
73
|
-
maltoolbox.neo4j_configs['dbname'],
|
|
74
|
-
delete=True)
|
|
75
|
-
logger.debug('Ingest attack graph into Neo4J database.')
|
|
76
|
-
neo4j.ingest_attack_graph(graph,
|
|
77
|
-
maltoolbox.neo4j_configs['uri'],
|
|
78
|
-
maltoolbox.neo4j_configs['username'],
|
|
79
|
-
maltoolbox.neo4j_configs['password'],
|
|
80
|
-
maltoolbox.neo4j_configs['dbname'],
|
|
81
|
-
delete=False)
|
|
82
|
-
|
|
83
|
-
case _:
|
|
84
|
-
logger.error(f'Received unknown command: {cmd_params["command"]}.')
|
maltoolbox/model/model.py
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MAL-Toolbox Model Module
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from typing import List
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class Attacker:
|
|
15
|
-
id: str = None
|
|
16
|
-
name: str = None
|
|
17
|
-
entry_points: List[tuple] = None
|
|
18
|
-
|
|
19
|
-
class Model:
|
|
20
|
-
latestId = 0
|
|
21
|
-
|
|
22
|
-
def __repr__(self) -> str:
|
|
23
|
-
return f'Model {self.name}'
|
|
24
|
-
|
|
25
|
-
def __init__(self, name, lang_spec, lang_classes_factory):
|
|
26
|
-
self.name = name
|
|
27
|
-
self.assets = []
|
|
28
|
-
self.associations = []
|
|
29
|
-
self.attackers = []
|
|
30
|
-
self.lang_spec = lang_spec
|
|
31
|
-
self.lang_classes_factory = lang_classes_factory
|
|
32
|
-
|
|
33
|
-
def add_asset(self, asset, asset_id: int = None):
|
|
34
|
-
"""
|
|
35
|
-
Add an asset to the model.
|
|
36
|
-
"""
|
|
37
|
-
if asset_id is not None:
|
|
38
|
-
for existing_asset in self.assets:
|
|
39
|
-
if asset_id == existing_asset.id:
|
|
40
|
-
raise ValueError(f'Asset index {asset_id} already in use.')
|
|
41
|
-
asset.id = asset_id
|
|
42
|
-
else:
|
|
43
|
-
asset.id = self.latestId
|
|
44
|
-
self.latestId = max(asset.id + 1, self.latestId)
|
|
45
|
-
|
|
46
|
-
asset.associations = []
|
|
47
|
-
if not hasattr(asset, 'name'):
|
|
48
|
-
asset.name = asset.metaconcept + ':' + str(asset.id)
|
|
49
|
-
self.assets.append(asset)
|
|
50
|
-
|
|
51
|
-
def add_association(self, association):
|
|
52
|
-
"""
|
|
53
|
-
Add an association to the model.
|
|
54
|
-
"""
|
|
55
|
-
for prop in range(0, 2):
|
|
56
|
-
for asset in getattr(association,
|
|
57
|
-
list(vars(association)['_properties'])[prop]):
|
|
58
|
-
assocs = list(asset.associations)
|
|
59
|
-
assocs.append(association)
|
|
60
|
-
asset.associations = assocs
|
|
61
|
-
self.associations.append(association)
|
|
62
|
-
|
|
63
|
-
def add_attacker(self, attacker, attacker_id: int = None):
|
|
64
|
-
"""
|
|
65
|
-
Add an attacker to the model.
|
|
66
|
-
"""
|
|
67
|
-
if attacker_id:
|
|
68
|
-
attacker.id = attacker_id
|
|
69
|
-
else:
|
|
70
|
-
attacker.id = self.latestId
|
|
71
|
-
self.latestId = max(attacker.id + 1, self.latestId)
|
|
72
|
-
|
|
73
|
-
if not hasattr(attacker, 'name') or not attacker.name:
|
|
74
|
-
attacker.name = 'Attacker:' + str(attacker.id)
|
|
75
|
-
self.attackers.append(attacker)
|
|
76
|
-
|
|
77
|
-
def get_asset_by_id(self, asset_id):
|
|
78
|
-
"""
|
|
79
|
-
Find an asset in the model based on its id.
|
|
80
|
-
|
|
81
|
-
Arguments:
|
|
82
|
-
asset_id - the id of the asset we are looking for
|
|
83
|
-
|
|
84
|
-
Return:
|
|
85
|
-
An asset matching the id if it exists in the model.
|
|
86
|
-
"""
|
|
87
|
-
return next((asset for asset in self.assets if asset.id == asset_id),
|
|
88
|
-
None)
|
|
89
|
-
|
|
90
|
-
def get_attacker_by_id(self, attacker_id):
|
|
91
|
-
"""
|
|
92
|
-
Find an attacker in the model based on its id.
|
|
93
|
-
|
|
94
|
-
Arguments:
|
|
95
|
-
attacker_id - the id of the attacker we are looking for
|
|
96
|
-
|
|
97
|
-
Return:
|
|
98
|
-
An attacker matching the id if it exists in the model.
|
|
99
|
-
"""
|
|
100
|
-
return next((attacker for attacker in self.attackers \
|
|
101
|
-
if attacker.id == attacker_id), None)
|
|
102
|
-
|
|
103
|
-
def get_associated_assets_by_field_name(self, asset, field_name):
|
|
104
|
-
"""
|
|
105
|
-
Get a list of associated assets for an asset given a fieldname.
|
|
106
|
-
|
|
107
|
-
Arguments:
|
|
108
|
-
asset - the asset whose fields we are interested in
|
|
109
|
-
fieldname - the field name we are looking for
|
|
110
|
-
|
|
111
|
-
Return:
|
|
112
|
-
A list of assets associated with the asset given that match the
|
|
113
|
-
fieldname.
|
|
114
|
-
"""
|
|
115
|
-
logger.debug(f'Get associated assets for asset '
|
|
116
|
-
f'{asset.name}(id:{asset.id}) by field name {field_name}.')
|
|
117
|
-
associated_assets = []
|
|
118
|
-
for association in asset.associations:
|
|
119
|
-
# Determine which two of the end points leads away from the asset.
|
|
120
|
-
# This is particularly relevant for associations between two
|
|
121
|
-
# assets of the same type.
|
|
122
|
-
if asset in getattr(association,
|
|
123
|
-
list(vars(association)['_properties'])[0]):
|
|
124
|
-
elementName = list(vars(association)['_properties'])[1]
|
|
125
|
-
else:
|
|
126
|
-
elementName = list(vars(association)['_properties'])[0]
|
|
127
|
-
|
|
128
|
-
if elementName == field_name:
|
|
129
|
-
associated_assets.extend(getattr(association, elementName))
|
|
130
|
-
|
|
131
|
-
return associated_assets
|
|
132
|
-
|
|
133
|
-
def asset_to_json(self, asset):
|
|
134
|
-
"""
|
|
135
|
-
Convert an asset to its JSON format.
|
|
136
|
-
"""
|
|
137
|
-
defenses = {}
|
|
138
|
-
logger.debug(f'Translating {asset.name} to json.')
|
|
139
|
-
for defense in list(vars(asset)['_properties'])[1:]:
|
|
140
|
-
defenseValue = getattr(asset, defense)
|
|
141
|
-
logger.debug(f'Translating {defense}: {defenseValue} defense '\
|
|
142
|
-
'to json.')
|
|
143
|
-
if defenseValue != getattr(asset, defense).default():
|
|
144
|
-
# Save the default values that are not the default ones.
|
|
145
|
-
defenses[str(defense)] = str(defenseValue)
|
|
146
|
-
return (str(asset.id), {
|
|
147
|
-
'name': str(asset.name),
|
|
148
|
-
'metaconcept': str(asset.metaconcept),
|
|
149
|
-
'eid': str(asset.id),
|
|
150
|
-
'defenses': defenses
|
|
151
|
-
}
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
def association_to_json(self, association):
|
|
155
|
-
"""
|
|
156
|
-
Convert an association to its JSON format.
|
|
157
|
-
"""
|
|
158
|
-
firstElementName = list(vars(association)['_properties'])[0]
|
|
159
|
-
secondElementName = list(vars(association)['_properties'])[1]
|
|
160
|
-
firstElements = getattr(association, firstElementName)
|
|
161
|
-
secondElements = getattr(association, secondElementName)
|
|
162
|
-
json_association = {
|
|
163
|
-
'metaconcept': type(association).__name__,
|
|
164
|
-
'association': {
|
|
165
|
-
str(firstElementName): [str(asset.id) for asset in firstElements],
|
|
166
|
-
str(secondElementName): [str(asset.id) for asset in secondElements]
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return json_association
|
|
170
|
-
|
|
171
|
-
def attacker_to_json(self, attacker):
|
|
172
|
-
"""
|
|
173
|
-
Convert an attacker to its JSON format.
|
|
174
|
-
"""
|
|
175
|
-
logger.debug(f'Translating {attacker.name} to json.')
|
|
176
|
-
json_attacker = {
|
|
177
|
-
'name': str(attacker.name),
|
|
178
|
-
'entry_points': {},
|
|
179
|
-
}
|
|
180
|
-
for (asset, attack_steps) in attacker.entry_points:
|
|
181
|
-
json_attacker['entry_points'][str(asset.id)] = {
|
|
182
|
-
'attack_steps' : attack_steps
|
|
183
|
-
}
|
|
184
|
-
return (str(attacker.id), json_attacker)
|
|
185
|
-
|
|
186
|
-
def model_to_json(self):
|
|
187
|
-
"""
|
|
188
|
-
Convert the model to its JSON format.
|
|
189
|
-
"""
|
|
190
|
-
logger.debug(f'Converting model to JSON format.')
|
|
191
|
-
contents = {
|
|
192
|
-
'metadata': {},
|
|
193
|
-
'assets': {},
|
|
194
|
-
'associations': [],
|
|
195
|
-
'attackers' : {}
|
|
196
|
-
}
|
|
197
|
-
contents['metadata'] = {
|
|
198
|
-
'name': self.name,
|
|
199
|
-
'langVersion': self.lang_spec['defines']['version'],
|
|
200
|
-
'langID': self.lang_spec['defines']['id'],
|
|
201
|
-
'malVersion': '0.1.0-SNAPSHOT',
|
|
202
|
-
'info': 'Created by the mal-toolbox model python module.'
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
logger.debug('Translating assets to json.')
|
|
206
|
-
for asset in self.assets:
|
|
207
|
-
(asset_id, asset_json) = self.asset_to_json(asset)
|
|
208
|
-
contents['assets'][asset_id] = asset_json
|
|
209
|
-
|
|
210
|
-
logger.debug('Translating associations to json.')
|
|
211
|
-
for association in self.associations:
|
|
212
|
-
assoc_json = self.association_to_json(association)
|
|
213
|
-
contents['associations'].append(assoc_json)
|
|
214
|
-
|
|
215
|
-
logger.debug('Translating attackers to json.')
|
|
216
|
-
for attacker in self.attackers:
|
|
217
|
-
(attacker_id, attacker_json) = self.attacker_to_json(attacker)
|
|
218
|
-
contents['attackers'][attacker_id] = attacker_json
|
|
219
|
-
return contents
|
|
220
|
-
|
|
221
|
-
def save_to_file(self, filename):
|
|
222
|
-
"""
|
|
223
|
-
Save model to a json file.
|
|
224
|
-
|
|
225
|
-
Arguments:
|
|
226
|
-
filename - the name of the output file
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
logger.info(f'Saving model to {filename} file.')
|
|
230
|
-
contents = self.model_to_json()
|
|
231
|
-
fp = open(filename, 'w')
|
|
232
|
-
json.dump(contents, fp, indent = 2)
|
|
233
|
-
|
|
234
|
-
def load_from_file(self, filename):
|
|
235
|
-
"""
|
|
236
|
-
Load model from a json file.
|
|
237
|
-
|
|
238
|
-
Arguments:
|
|
239
|
-
filename - the name of the input file
|
|
240
|
-
"""
|
|
241
|
-
logger.info(f'Loading model from {filename} file.')
|
|
242
|
-
with open(filename, 'r', encoding='utf-8') as model_file:
|
|
243
|
-
json_model = json.loads(model_file.read())
|
|
244
|
-
|
|
245
|
-
self.name = json_model['metadata']['name']
|
|
246
|
-
|
|
247
|
-
# Reconstruct the assets
|
|
248
|
-
for asset_id in json_model['assets']:
|
|
249
|
-
asset_object = json_model['assets'][asset_id]
|
|
250
|
-
logger.debug(f'Loading asset from {filename}:\n' \
|
|
251
|
-
+ json.dumps(asset_object, indent = 2))
|
|
252
|
-
asset = getattr(self.lang_classes_factory.ns,
|
|
253
|
-
asset_object['metaconcept'])(name = asset_object['name'])
|
|
254
|
-
for defense in asset_object['defenses']:
|
|
255
|
-
setattr(asset, defense,
|
|
256
|
-
float(asset_object['defenses'][defense]))
|
|
257
|
-
self.add_asset(asset, asset_id = int(asset_id))
|
|
258
|
-
|
|
259
|
-
# Reconstruct the associations
|
|
260
|
-
if 'associations' in json_model:
|
|
261
|
-
for association_object in json_model['associations']:
|
|
262
|
-
association = getattr(self.lang_classes_factory.ns,
|
|
263
|
-
association_object['metaconcept'])()
|
|
264
|
-
for field in association_object['association']:
|
|
265
|
-
asset_list = []
|
|
266
|
-
for asset_id in association_object['association'][field]:
|
|
267
|
-
asset_list.append(self.get_asset_by_id(int(asset_id)))
|
|
268
|
-
setattr(association, field, asset_list)
|
|
269
|
-
self.add_association(association)
|
|
270
|
-
|
|
271
|
-
# Reconstruct the attackers
|
|
272
|
-
if 'attackers' in json_model:
|
|
273
|
-
attackers_info = json_model['attackers']
|
|
274
|
-
for attacker_id in attackers_info:
|
|
275
|
-
attacker = Attacker(name = attackers_info[attacker_id]['name'])
|
|
276
|
-
attacker.entry_points = []
|
|
277
|
-
for asset_id in attackers_info[attacker_id]['entry_points']:
|
|
278
|
-
attacker.entry_points.append(
|
|
279
|
-
(self.get_asset_by_id(int(asset_id)),
|
|
280
|
-
attackers_info[attacker_id]['entry_points']\
|
|
281
|
-
[asset_id]['attack_steps']))
|
|
282
|
-
self.add_attacker(attacker, attacker_id = int(attacker_id))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|