mal-toolbox 0.2.0__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.2.0.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 +6 -5
- maltoolbox/attackgraph/attacker.py +26 -13
- maltoolbox/attackgraph/attackgraph.py +175 -148
- maltoolbox/attackgraph/node.py +56 -54
- maltoolbox/attackgraph/query.py +4 -2
- maltoolbox/file_utils.py +0 -8
- maltoolbox/ingestors/neo4j.py +146 -157
- maltoolbox/language/__init__.py +7 -3
- 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 +103 -99
- maltoolbox/model.py +306 -488
- maltoolbox/translators/securicad.py +164 -163
- maltoolbox/translators/updater.py +231 -108
- mal_toolbox-0.2.0.dist-info/RECORD +0 -32
- maltoolbox/default.conf +0 -17
- maltoolbox/language/classes_factory.py +0 -259
- maltoolbox/language/compiler/mal_visitor.py +0 -416
- maltoolbox/wrappers.py +0 -62
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/AUTHORS +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/LICENSE +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/WHEEL +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -32,6 +32,36 @@ def disaggregate_attack_step_full_name(
|
|
|
32
32
|
return attack_step_full_name.split(':')
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
@dataclass
|
|
36
|
+
class Detector:
|
|
37
|
+
context: Context
|
|
38
|
+
name: Optional[str]
|
|
39
|
+
type: Optional[str]
|
|
40
|
+
tprate: Optional[dict]
|
|
41
|
+
|
|
42
|
+
def to_dict(self) -> dict:
|
|
43
|
+
return {
|
|
44
|
+
"context": self.context.to_dict(),
|
|
45
|
+
"name": self.name,
|
|
46
|
+
"type": self.type,
|
|
47
|
+
"tprate": self.tprate,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Context(dict):
|
|
52
|
+
def __init__(self, context) -> None:
|
|
53
|
+
super().__init__(context)
|
|
54
|
+
self._context_dict = context
|
|
55
|
+
for label, asset in context.items():
|
|
56
|
+
setattr(self, label, asset)
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict:
|
|
59
|
+
return {label: asset.name for label, asset in self.items()}
|
|
60
|
+
|
|
61
|
+
def __str__(self) -> str:
|
|
62
|
+
return str({label: asset.name for label, asset in self._context_dict.items()})
|
|
63
|
+
|
|
64
|
+
|
|
35
65
|
@dataclass
|
|
36
66
|
class LanguageGraphAsset:
|
|
37
67
|
name: str
|
|
@@ -60,8 +90,8 @@ class LanguageGraphAsset:
|
|
|
60
90
|
'is_abstract': self.is_abstract
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
for assoc in self.own_associations.
|
|
64
|
-
node_dict['associations'][
|
|
93
|
+
for fieldname, assoc in self.own_associations.items():
|
|
94
|
+
node_dict['associations'][fieldname] = assoc.to_dict()
|
|
65
95
|
for attack_step in self.attack_steps.values():
|
|
66
96
|
node_dict['attack_steps'][attack_step.name] = \
|
|
67
97
|
attack_step.to_dict()
|
|
@@ -75,7 +105,7 @@ class LanguageGraphAsset:
|
|
|
75
105
|
|
|
76
106
|
|
|
77
107
|
def __repr__(self) -> str:
|
|
78
|
-
return
|
|
108
|
+
return f'LanguageGraphAsset(name: "{self.name}")'
|
|
79
109
|
|
|
80
110
|
|
|
81
111
|
def __hash__(self):
|
|
@@ -101,6 +131,7 @@ class LanguageGraphAsset:
|
|
|
101
131
|
current_asset = current_asset.own_super_asset
|
|
102
132
|
return False
|
|
103
133
|
|
|
134
|
+
|
|
104
135
|
@cached_property
|
|
105
136
|
def sub_assets(self) -> set[LanguageGraphAsset]:
|
|
106
137
|
"""
|
|
@@ -244,7 +275,9 @@ class LanguageGraphAssociation:
|
|
|
244
275
|
|
|
245
276
|
|
|
246
277
|
def __repr__(self) -> str:
|
|
247
|
-
return
|
|
278
|
+
return (f'LanguageGraphAssociation(name: "{self.name}", '
|
|
279
|
+
f'left_field: {self.left_field}, '
|
|
280
|
+
f'right_field: {self.right_field})')
|
|
248
281
|
|
|
249
282
|
|
|
250
283
|
@property
|
|
@@ -262,6 +295,15 @@ class LanguageGraphAssociation:
|
|
|
262
295
|
return full_name
|
|
263
296
|
|
|
264
297
|
|
|
298
|
+
def get_field(self, fieldname: str) -> LanguageGraphAssociationField:
|
|
299
|
+
"""
|
|
300
|
+
Return the field that matches the `fieldname` given as parameter.
|
|
301
|
+
"""
|
|
302
|
+
if self.right_field.fieldname == fieldname:
|
|
303
|
+
return self.right_field
|
|
304
|
+
return self.left_field
|
|
305
|
+
|
|
306
|
+
|
|
265
307
|
def contains_fieldname(self, fieldname: str) -> bool:
|
|
266
308
|
"""
|
|
267
309
|
Check if the association contains the field name given as a parameter.
|
|
@@ -317,35 +359,6 @@ class LanguageGraphAssociation:
|
|
|
317
359
|
raise LanguageGraphAssociationError(msg % (fieldname, self.name))
|
|
318
360
|
|
|
319
361
|
|
|
320
|
-
def get_opposite_asset(
|
|
321
|
-
self, asset: LanguageGraphAsset
|
|
322
|
-
) -> Optional[LanguageGraphAsset]:
|
|
323
|
-
"""
|
|
324
|
-
Return the opposite asset if the association matches the asset given
|
|
325
|
-
as a parameter. A match can either be an explicit one or if the asset
|
|
326
|
-
given subassets either of the two assets that are part of the
|
|
327
|
-
association.
|
|
328
|
-
|
|
329
|
-
Arguments:
|
|
330
|
-
asset - the asset to look for
|
|
331
|
-
Return the other asset if the parameter matched either of the
|
|
332
|
-
two. None, otherwise.
|
|
333
|
-
"""
|
|
334
|
-
#TODO Should check to see which one is the tightest fit for
|
|
335
|
-
# associations between assets on different levels of the same
|
|
336
|
-
# inheritance chain.
|
|
337
|
-
if asset.is_subasset_of(self.left_field.asset):
|
|
338
|
-
return self.right_field.asset
|
|
339
|
-
if asset.is_subasset_of(self.right_field.asset):
|
|
340
|
-
return self.left_field.asset
|
|
341
|
-
|
|
342
|
-
logger.warning(
|
|
343
|
-
'Requested asset "%s" from association %s'
|
|
344
|
-
'which did not contain it!', asset.name, self.name
|
|
345
|
-
)
|
|
346
|
-
return None
|
|
347
|
-
|
|
348
|
-
|
|
349
362
|
@dataclass
|
|
350
363
|
class LanguageGraphAttackStep:
|
|
351
364
|
name: str
|
|
@@ -359,6 +372,7 @@ class LanguageGraphAttackStep:
|
|
|
359
372
|
inherits: Optional[LanguageGraphAttackStep] = None
|
|
360
373
|
tags: set = field(default_factory = set)
|
|
361
374
|
_attributes: Optional[dict] = None
|
|
375
|
+
detectors: dict = field(default_factory = lambda: {})
|
|
362
376
|
|
|
363
377
|
|
|
364
378
|
def __hash__(self):
|
|
@@ -386,7 +400,9 @@ class LanguageGraphAttackStep:
|
|
|
386
400
|
'info': self.info,
|
|
387
401
|
'overrides': self.overrides,
|
|
388
402
|
'inherits': self.inherits.full_name if self.inherits else None,
|
|
389
|
-
'tags': list(self.tags)
|
|
403
|
+
'tags': list(self.tags),
|
|
404
|
+
'detectors': {label: detector.to_dict() for label, detector in
|
|
405
|
+
self.detectors.items()},
|
|
390
406
|
}
|
|
391
407
|
|
|
392
408
|
for child in self.children:
|
|
@@ -484,7 +500,7 @@ class ExpressionsChain:
|
|
|
484
500
|
)
|
|
485
501
|
|
|
486
502
|
return {
|
|
487
|
-
self.association.
|
|
503
|
+
self.association.name:
|
|
488
504
|
{
|
|
489
505
|
'fieldname': self.fieldname,
|
|
490
506
|
'asset type': asset_type
|
|
@@ -527,7 +543,7 @@ class ExpressionsChain:
|
|
|
527
543
|
serialized_expr_chain: dict,
|
|
528
544
|
lang_graph: LanguageGraph,
|
|
529
545
|
) -> Optional[ExpressionsChain]:
|
|
530
|
-
"""Create
|
|
546
|
+
"""Create ExpressionsChain from dict
|
|
531
547
|
Args:
|
|
532
548
|
serialized_expr_chain - expressions chain in dict format
|
|
533
549
|
lang_graph - the LanguageGraph that contains the assets,
|
|
@@ -567,10 +583,26 @@ class ExpressionsChain:
|
|
|
567
583
|
assoc_name = list(serialized_expr_chain.keys())[0]
|
|
568
584
|
target_asset = lang_graph.assets[\
|
|
569
585
|
serialized_expr_chain[assoc_name]['asset type']]
|
|
586
|
+
fieldname = serialized_expr_chain[assoc_name]['fieldname']
|
|
587
|
+
|
|
588
|
+
association = None
|
|
589
|
+
for assoc in target_asset.associations.values():
|
|
590
|
+
if assoc.contains_fieldname(fieldname) and \
|
|
591
|
+
assoc.name == assoc_name:
|
|
592
|
+
association = assoc
|
|
593
|
+
break
|
|
594
|
+
|
|
595
|
+
if association is None:
|
|
596
|
+
msg = 'Failed to find association "%s" with '\
|
|
597
|
+
'fieldname "%s"'
|
|
598
|
+
logger.error(msg % (assoc_name, fieldname))
|
|
599
|
+
raise LanguageGraphException(msg % (assoc_name,
|
|
600
|
+
fieldname))
|
|
601
|
+
|
|
570
602
|
new_expr_chain = ExpressionsChain(
|
|
571
603
|
type = 'field',
|
|
572
|
-
association =
|
|
573
|
-
fieldname =
|
|
604
|
+
association = association,
|
|
605
|
+
fieldname = fieldname
|
|
574
606
|
)
|
|
575
607
|
return new_expr_chain
|
|
576
608
|
|
|
@@ -629,6 +661,11 @@ class LanguageGraph():
|
|
|
629
661
|
self._generate_graph()
|
|
630
662
|
|
|
631
663
|
|
|
664
|
+
def __repr__(self) -> str:
|
|
665
|
+
return (f'LanguageGraph(id: "{self.metadata.get("id", "N/A")}", '
|
|
666
|
+
f'version: "{self.metadata.get("version", "N/A")}")')
|
|
667
|
+
|
|
668
|
+
|
|
632
669
|
@classmethod
|
|
633
670
|
def from_mal_spec(cls, mal_spec_file: str) -> LanguageGraph:
|
|
634
671
|
"""
|
|
@@ -663,12 +700,18 @@ class LanguageGraph():
|
|
|
663
700
|
'Serializing %s assets.', len(self.assets.items())
|
|
664
701
|
)
|
|
665
702
|
|
|
666
|
-
serialized_graph = {}
|
|
703
|
+
serialized_graph = {'metadata': self.metadata}
|
|
667
704
|
for asset in self.assets.values():
|
|
668
705
|
serialized_graph[asset.name] = asset.to_dict()
|
|
669
706
|
|
|
670
707
|
return serialized_graph
|
|
671
708
|
|
|
709
|
+
def _link_association_to_assets(cls,
|
|
710
|
+
assoc: LanguageGraphAssociation,
|
|
711
|
+
left_asset: LanguageGraphAsset,
|
|
712
|
+
right_asset: LanguageGraphAsset):
|
|
713
|
+
left_asset.own_associations[assoc.right_field.fieldname] = assoc
|
|
714
|
+
right_asset.own_associations[assoc.left_field.fieldname] = assoc
|
|
672
715
|
|
|
673
716
|
def save_to_file(self, filename: str) -> None:
|
|
674
717
|
"""Save to json/yml depending on extension"""
|
|
@@ -684,6 +727,8 @@ class LanguageGraph():
|
|
|
684
727
|
|
|
685
728
|
logger.debug('Create language graph from dictionary.')
|
|
686
729
|
lang_graph = LanguageGraph()
|
|
730
|
+
lang_graph.metadata = serialized_graph.pop('metadata')
|
|
731
|
+
|
|
687
732
|
# Recreate all of the assets
|
|
688
733
|
for asset_dict in serialized_graph.values():
|
|
689
734
|
logger.debug(
|
|
@@ -766,11 +811,8 @@ class LanguageGraph():
|
|
|
766
811
|
)
|
|
767
812
|
|
|
768
813
|
# Add the association to the left and right asset
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
asset = associated_assets.pop()
|
|
772
|
-
if assoc_node.full_name not in asset.own_associations:
|
|
773
|
-
asset.own_associations[assoc_node.full_name] = assoc_node
|
|
814
|
+
lang_graph._link_association_to_assets(assoc_node,
|
|
815
|
+
left_asset, right_asset)
|
|
774
816
|
|
|
775
817
|
# Recreate the variables
|
|
776
818
|
for asset_dict in serialized_graph.values():
|
|
@@ -1360,11 +1402,8 @@ class LanguageGraph():
|
|
|
1360
1402
|
)
|
|
1361
1403
|
|
|
1362
1404
|
# Add the association to the left and right asset
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
asset = associated_assets.pop()
|
|
1366
|
-
if assoc_node.full_name not in asset.own_associations:
|
|
1367
|
-
asset.own_associations[assoc_node.full_name] = assoc_node
|
|
1405
|
+
self._link_association_to_assets(assoc_node,
|
|
1406
|
+
left_asset, right_asset)
|
|
1368
1407
|
|
|
1369
1408
|
# Set the variables
|
|
1370
1409
|
for asset in self.assets.values():
|
|
@@ -1407,6 +1446,20 @@ class LanguageGraph():
|
|
|
1407
1446
|
asset.attack_steps[attack_step_attribs['name']] = \
|
|
1408
1447
|
attack_step_node
|
|
1409
1448
|
|
|
1449
|
+
for detector in attack_step_attribs.get("detectors",
|
|
1450
|
+
{}).values():
|
|
1451
|
+
attack_step_node.detectors[detector["name"]] = Detector(
|
|
1452
|
+
context=Context(
|
|
1453
|
+
{
|
|
1454
|
+
label: self.assets[asset]
|
|
1455
|
+
for label, asset in detector["context"].items()
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
name=detector.get("name"),
|
|
1459
|
+
type=detector.get("type"),
|
|
1460
|
+
tprate=detector.get("tprate"),
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1410
1463
|
# Create the inherited attack steps
|
|
1411
1464
|
assets = list(self.assets.values())
|
|
1412
1465
|
while len(assets) > 0:
|
|
@@ -1540,8 +1593,7 @@ class LanguageGraph():
|
|
|
1540
1593
|
None,
|
|
1541
1594
|
step_expression
|
|
1542
1595
|
)
|
|
1543
|
-
attack_step.own_requires.append(
|
|
1544
|
-
self.reverse_expr_chain(result_expr_chain, None))
|
|
1596
|
+
attack_step.own_requires.append(result_expr_chain)
|
|
1545
1597
|
|
|
1546
1598
|
def _get_attacks_for_asset_type(self, asset_type: str) -> dict:
|
|
1547
1599
|
"""
|
|
@@ -1681,51 +1733,3 @@ class LanguageGraph():
|
|
|
1681
1733
|
self._generate_graph()
|
|
1682
1734
|
|
|
1683
1735
|
|
|
1684
|
-
def get_association_by_fields_and_assets(
|
|
1685
|
-
self,
|
|
1686
|
-
first_field: str,
|
|
1687
|
-
second_field: str,
|
|
1688
|
-
first_asset_name: str,
|
|
1689
|
-
second_asset_name: str
|
|
1690
|
-
) -> Optional[LanguageGraphAssociation]:
|
|
1691
|
-
"""
|
|
1692
|
-
Get an association based on its field names and asset types
|
|
1693
|
-
|
|
1694
|
-
Arguments:
|
|
1695
|
-
first_field - a string containing the first field
|
|
1696
|
-
second_field - a string containing the second field
|
|
1697
|
-
first_asset_name - a string representing the first asset type
|
|
1698
|
-
second_asset_name - a string representing the second asset type
|
|
1699
|
-
|
|
1700
|
-
Return:
|
|
1701
|
-
The association matching the fieldnames and asset types.
|
|
1702
|
-
None if there is no match.
|
|
1703
|
-
"""
|
|
1704
|
-
first_asset = self.assets[first_asset_name]
|
|
1705
|
-
second_asset = self.assets[second_asset_name]
|
|
1706
|
-
|
|
1707
|
-
for assoc in first_asset.associations.values():
|
|
1708
|
-
logger.debug(
|
|
1709
|
-
'Compare ("%s", "%s", "%s", "%s") to '
|
|
1710
|
-
'("%s", "%s", "%s", "%s").',
|
|
1711
|
-
first_asset_name, first_field,
|
|
1712
|
-
second_asset_name, second_field,
|
|
1713
|
-
assoc.left_field.asset.name, assoc.left_field.fieldname,
|
|
1714
|
-
assoc.right_field.asset.name, assoc.right_field.fieldname
|
|
1715
|
-
)
|
|
1716
|
-
|
|
1717
|
-
# If the asset and fields match either way we accept it as a
|
|
1718
|
-
# match.
|
|
1719
|
-
if assoc.left_field.fieldname == first_field and \
|
|
1720
|
-
assoc.right_field.fieldname == second_field and \
|
|
1721
|
-
first_asset.is_subasset_of(assoc.left_field.asset) and \
|
|
1722
|
-
second_asset.is_subasset_of(assoc.right_field.asset):
|
|
1723
|
-
return assoc
|
|
1724
|
-
|
|
1725
|
-
if assoc.left_field.fieldname == second_field and \
|
|
1726
|
-
assoc.right_field.fieldname == first_field and \
|
|
1727
|
-
second_asset.is_subasset_of(assoc.left_field.asset) and \
|
|
1728
|
-
first_asset.is_subasset_of(assoc.right_field.asset):
|
|
1729
|
-
return assoc
|
|
1730
|
-
|
|
1731
|
-
return None
|