mal-toolbox 0.1.0__tar.gz → 0.1.1__tar.gz
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.0/mal_toolbox.egg-info → mal_toolbox-0.1.1}/PKG-INFO +1 -1
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1/mal_toolbox.egg-info}/PKG-INFO +1 -1
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/__init__.py +2 -2
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/languagegraph.py +4 -4
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/model.py +117 -61
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/pyproject.toml +1 -1
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/AUTHORS +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/LICENSE +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/README.md +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/SOURCES.txt +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/dependency_links.txt +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/requires.txt +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/top_level.txt +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/__main__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/analyzers/apriori.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/attacker.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/attackgraph.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/node.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/query.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/default.conf +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/exceptions.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/file_utils.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/ingestors/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/ingestors/neo4j.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/classes_factory.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_lexer.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_parser.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_visitor.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/specification.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/__init__.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/securicad.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/updater.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/wrappers.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/setup.cfg +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/tests/test_model.py +0 -0
- {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/tests/test_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
5
|
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Joakim Loxdal <loxdal@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
5
|
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Joakim Loxdal <loxdal@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# MAL Toolbox v0.1.
|
|
2
|
+
# MAL Toolbox v0.1.1
|
|
3
3
|
# Copyright 2024, Andrei Buhaiu.
|
|
4
4
|
#
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -21,7 +21,7 @@ MAL-Toolbox Framework
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
__title__ = 'maltoolbox'
|
|
24
|
-
__version__ = '0.1.
|
|
24
|
+
__version__ = '0.1.1'
|
|
25
25
|
__authors__ = ['Andrei Buhaiu',
|
|
26
26
|
'Giuseppe Nebbione',
|
|
27
27
|
'Nikolaos Kakouros',
|
|
@@ -622,7 +622,7 @@ class LanguageGraph():
|
|
|
622
622
|
new_dep_chain,
|
|
623
623
|
attack_step)
|
|
624
624
|
|
|
625
|
-
case '
|
|
625
|
+
case 'subtype':
|
|
626
626
|
# Create a subType tuple entry that applies to the next
|
|
627
627
|
# component of the step expression and changes the target
|
|
628
628
|
# asset to the subasset.
|
|
@@ -678,7 +678,7 @@ class LanguageGraph():
|
|
|
678
678
|
|
|
679
679
|
case _:
|
|
680
680
|
logger.error(
|
|
681
|
-
'Unknown attack step type: %s', step_expression["type"]
|
|
681
|
+
'Unknown attack step type: "%s"', step_expression["type"]
|
|
682
682
|
)
|
|
683
683
|
return (None, None, None)
|
|
684
684
|
|
|
@@ -748,7 +748,7 @@ class LanguageGraph():
|
|
|
748
748
|
new_dep_chain
|
|
749
749
|
)
|
|
750
750
|
|
|
751
|
-
case '
|
|
751
|
+
case 'subtype':
|
|
752
752
|
#TODO FIX BUG: new_dep_chain is undefined
|
|
753
753
|
# result_reverse_chain = self.reverse_dep_chain(
|
|
754
754
|
# new_dep_chain.next_link,
|
|
@@ -763,7 +763,7 @@ class LanguageGraph():
|
|
|
763
763
|
return reverse_chain
|
|
764
764
|
|
|
765
765
|
case _:
|
|
766
|
-
msg = 'Unknown assoc chain element %s'
|
|
766
|
+
msg = 'Unknown assoc chain element "%s"'
|
|
767
767
|
logger.error(msg, dep_chain.type)
|
|
768
768
|
raise LanguageGraphAssociationError(msg % dep_chain.type)
|
|
769
769
|
|
|
@@ -162,11 +162,12 @@ class Model():
|
|
|
162
162
|
f'Association is not part of model "{self.name}".'
|
|
163
163
|
)
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
left_field_name, right_field_name = \
|
|
166
|
+
self.get_association_field_names(association)
|
|
167
|
+
left_field = getattr(association, left_field_name)
|
|
168
|
+
right_field = getattr(association, right_field_name)
|
|
168
169
|
found = False
|
|
169
|
-
for field in [
|
|
170
|
+
for field in [left_field, right_field]:
|
|
170
171
|
if asset in field:
|
|
171
172
|
found = True
|
|
172
173
|
if len(field) == 1:
|
|
@@ -202,7 +203,8 @@ class Model():
|
|
|
202
203
|
|
|
203
204
|
|
|
204
205
|
# Check for duplicate assets in each field
|
|
205
|
-
left_field_name, right_field_name =
|
|
206
|
+
left_field_name, right_field_name = \
|
|
207
|
+
self.get_association_field_names(association)
|
|
206
208
|
|
|
207
209
|
for field_name in (left_field_name, right_field_name):
|
|
208
210
|
field_assets = getattr(association, field_name)
|
|
@@ -251,8 +253,7 @@ class Model():
|
|
|
251
253
|
# Optional field for extra association data
|
|
252
254
|
association.extras = {}
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
field_names = list(vars(association)['_properties'])[0:2]
|
|
256
|
+
field_names = self.get_association_field_names(association)
|
|
256
257
|
|
|
257
258
|
# Add the association to all of the included assets
|
|
258
259
|
for field_name in field_names:
|
|
@@ -282,22 +283,20 @@ class Model():
|
|
|
282
283
|
f'Association is not part of model "{self.name}".'
|
|
283
284
|
)
|
|
284
285
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
second_field = getattr(association, second_field_name)
|
|
286
|
+
left_field_name, right_field_name = \
|
|
287
|
+
self.get_association_field_names(association)
|
|
288
|
+
left_field = getattr(association, left_field_name)
|
|
289
|
+
right_field = getattr(association, right_field_name)
|
|
290
290
|
|
|
291
|
-
for asset in
|
|
291
|
+
for asset in left_field:
|
|
292
292
|
assocs = list(asset.associations)
|
|
293
293
|
assocs.remove(association)
|
|
294
294
|
asset.associations = assocs
|
|
295
295
|
|
|
296
|
-
for asset in
|
|
296
|
+
for asset in right_field:
|
|
297
297
|
# In fringe cases we may have reflexive associations where the
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
# too.
|
|
298
|
+
# association was already removed when processing the left field
|
|
299
|
+
# assets therefore we have to check if it is still in the list.
|
|
301
300
|
if association in asset.associations:
|
|
302
301
|
assocs = list(asset.associations)
|
|
303
302
|
assocs.remove(association)
|
|
@@ -403,13 +402,89 @@ class Model():
|
|
|
403
402
|
self, association_type, left_asset, right_asset
|
|
404
403
|
):
|
|
405
404
|
"""Return True if the association already exists between the assets"""
|
|
405
|
+
logger.debug(
|
|
406
|
+
'Check to see if an association of type "%s" '
|
|
407
|
+
'already exists between "%s" and "%s".',
|
|
408
|
+
association_type, left_asset.name, right_asset.name
|
|
409
|
+
)
|
|
406
410
|
associations = self._type_to_association.get(association_type, [])
|
|
407
411
|
for association in associations:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
412
|
+
left_field_name, right_field_name = \
|
|
413
|
+
self.get_association_field_names(association)
|
|
414
|
+
if (left_asset.id in [asset.id for asset in \
|
|
415
|
+
getattr(association, left_field_name)] and \
|
|
416
|
+
right_asset.id in [asset.id for asset in \
|
|
417
|
+
getattr(association, right_field_name)]):
|
|
418
|
+
logger.debug(
|
|
419
|
+
'An association of type "%s" '
|
|
420
|
+
'already exists between "%s" and "%s".',
|
|
421
|
+
association_type, left_asset.name, right_asset.name
|
|
422
|
+
)
|
|
411
423
|
return True
|
|
412
|
-
|
|
424
|
+
logger.debug(
|
|
425
|
+
'No association of type "%s" '
|
|
426
|
+
'exists between "%s" and "%s".',
|
|
427
|
+
association_type, left_asset.name, right_asset.name
|
|
428
|
+
)
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
def get_asset_defenses(
|
|
432
|
+
self,
|
|
433
|
+
asset: SchemaGeneratedClass,
|
|
434
|
+
include_defaults: bool = False
|
|
435
|
+
):
|
|
436
|
+
"""
|
|
437
|
+
Get the two field names of the association as a list.
|
|
438
|
+
Arguments:
|
|
439
|
+
asset - the asset to fetch the defenses for
|
|
440
|
+
include_defaults - if not True the defenses that have default
|
|
441
|
+
values will not be included in the list
|
|
442
|
+
|
|
443
|
+
Return:
|
|
444
|
+
A dictionary containing the defenses of the asset
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
defenses = {}
|
|
448
|
+
for key, value in asset._properties.items():
|
|
449
|
+
property_schema = (
|
|
450
|
+
self.lang_classes_factory.json_schema['definitions']['LanguageAsset']
|
|
451
|
+
['definitions'][asset.type]['properties'][key]
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if "maximum" not in property_schema:
|
|
455
|
+
# Check if property is a defense by looking up defense
|
|
456
|
+
# specific key. Skip if it is not a defense.
|
|
457
|
+
continue
|
|
458
|
+
|
|
459
|
+
logger.debug(
|
|
460
|
+
'Translating %s: %s defense to dictionary.',
|
|
461
|
+
key,
|
|
462
|
+
value
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if not include_defaults and value == value.default():
|
|
466
|
+
# Skip the defense values if they are the default ones.
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
defenses[key] = float(value)
|
|
470
|
+
|
|
471
|
+
return defenses
|
|
472
|
+
|
|
473
|
+
def get_association_field_names(
|
|
474
|
+
self,
|
|
475
|
+
association: SchemaGeneratedClass
|
|
476
|
+
):
|
|
477
|
+
"""
|
|
478
|
+
Get the two field names of the association as a list.
|
|
479
|
+
Arguments:
|
|
480
|
+
association - the association to fetch the field names for
|
|
481
|
+
|
|
482
|
+
Return:
|
|
483
|
+
A two item list containing the field names of the association.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
return association._properties.keys()
|
|
487
|
+
|
|
413
488
|
|
|
414
489
|
def get_associated_assets_by_field_name(
|
|
415
490
|
self,
|
|
@@ -421,7 +496,7 @@ class Model():
|
|
|
421
496
|
|
|
422
497
|
Arguments:
|
|
423
498
|
asset - the asset whose fields we are interested in
|
|
424
|
-
field_name
|
|
499
|
+
field_name - the field name we are looking for
|
|
425
500
|
|
|
426
501
|
Return:
|
|
427
502
|
A list of assets associated with the asset given that match the
|
|
@@ -434,22 +509,19 @@ class Model():
|
|
|
434
509
|
)
|
|
435
510
|
associated_assets = []
|
|
436
511
|
for association in asset.associations:
|
|
437
|
-
# Determine which two of the fields
|
|
438
|
-
#
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if asset in getattr(association,
|
|
443
|
-
|
|
444
|
-
# the second one must lead away from it
|
|
445
|
-
field_name_away = field_name2
|
|
512
|
+
# Determine which two of the fields matches the asset given.
|
|
513
|
+
# The other field will provide the associated assets.
|
|
514
|
+
left_field_name, right_field_name = \
|
|
515
|
+
self.get_association_field_names(association)
|
|
516
|
+
|
|
517
|
+
if asset in getattr(association, left_field_name):
|
|
518
|
+
opposite_field_name = right_field_name
|
|
446
519
|
else:
|
|
447
|
-
|
|
448
|
-
field_name_away = field_name1
|
|
520
|
+
opposite_field_name = left_field_name
|
|
449
521
|
|
|
450
|
-
if
|
|
522
|
+
if opposite_field_name == field_name:
|
|
451
523
|
associated_assets.extend(
|
|
452
|
-
getattr(association,
|
|
524
|
+
getattr(association, opposite_field_name)
|
|
453
525
|
)
|
|
454
526
|
|
|
455
527
|
return associated_assets
|
|
@@ -463,37 +535,20 @@ class Model():
|
|
|
463
535
|
Return: tuple with name of asset and the asset as dict
|
|
464
536
|
"""
|
|
465
537
|
|
|
466
|
-
defenses = {}
|
|
467
538
|
logger.debug(
|
|
468
539
|
'Translating "%s"(%d) to dictionary.',
|
|
469
540
|
asset.name,
|
|
470
541
|
asset.id
|
|
471
542
|
)
|
|
472
543
|
|
|
473
|
-
for key, value in asset._properties.items():
|
|
474
|
-
property_schema = (
|
|
475
|
-
self.lang_classes_factory.json_schema['definitions']['LanguageAsset']
|
|
476
|
-
['definitions'][asset.type]['properties'][key]
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
if "maximum" not in property_schema:
|
|
480
|
-
# Check if property is a defense by looking up defense
|
|
481
|
-
# specific key. Skip if it is not a defense.
|
|
482
|
-
continue
|
|
483
|
-
|
|
484
|
-
logger.debug('Translating %s: %s defense to dictionary.', key, value)
|
|
485
|
-
|
|
486
|
-
if value == value.default():
|
|
487
|
-
# Skip the defense values if they are the default ones.
|
|
488
|
-
continue
|
|
489
|
-
|
|
490
|
-
defenses[key] = float(value)
|
|
491
544
|
|
|
492
545
|
asset_dict: dict[str, Any] = {
|
|
493
546
|
'name': str(asset.name),
|
|
494
547
|
'type': str(asset.type)
|
|
495
548
|
}
|
|
496
549
|
|
|
550
|
+
defenses = self.get_asset_defenses(asset)
|
|
551
|
+
|
|
497
552
|
if defenses:
|
|
498
553
|
asset_dict['defenses'] = defenses
|
|
499
554
|
|
|
@@ -513,17 +568,18 @@ class Model():
|
|
|
513
568
|
Returns the association serialized to a dict
|
|
514
569
|
"""
|
|
515
570
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
571
|
+
left_field_name, right_field_name = \
|
|
572
|
+
self.get_association_field_names(association)
|
|
573
|
+
left_field = getattr(association, left_field_name)
|
|
574
|
+
right_field = getattr(association, right_field_name)
|
|
519
575
|
|
|
520
576
|
association_dict = {
|
|
521
577
|
association.__class__.__name__ :
|
|
522
578
|
{
|
|
523
|
-
str(
|
|
524
|
-
[int(asset.id) for asset in
|
|
525
|
-
str(
|
|
526
|
-
[int(asset.id) for asset in
|
|
579
|
+
str(left_field_name):
|
|
580
|
+
[int(asset.id) for asset in left_field],
|
|
581
|
+
str(right_field_name):
|
|
582
|
+
[int(asset.id) for asset in right_field]
|
|
527
583
|
}
|
|
528
584
|
}
|
|
529
585
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|