mal-toolbox 0.1.8__py3-none-any.whl → 0.1.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mal-toolbox
3
- Version: 0.1.8
3
+ Version: 0.1.9
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
@@ -102,6 +102,12 @@ and it can be used to visualise the instance model and the attack graph.
102
102
 
103
103
  # Usage
104
104
 
105
+ ## Installation
106
+
107
+ ```
108
+ pip install mal-toolbox
109
+ ```
110
+
105
111
  ## Configuration
106
112
  A default configuration file `default.conf` can be found in the package
107
113
  directory. This contains the default values to use for logging and can also be
@@ -1,21 +1,21 @@
1
- maltoolbox/__init__.py,sha256=c31l952N_1NVtHOSM-r2F5gL-RwHqye846B524iVwx4,2776
1
+ maltoolbox/__init__.py,sha256=tfDCz1FxF71yzZIZyY0uKDOqCV2nThbOBOUCyW72Qvg,2776
2
2
  maltoolbox/__main__.py,sha256=1lOOOme_y56VWrEE1jkarTt-WoUo9yilCo8sUrivyns,2680
3
3
  maltoolbox/default.conf,sha256=YLGBSJh2q8hn3RzRRBbib9F6E6pcvquoHeALMRtA0wU,295
4
4
  maltoolbox/exceptions.py,sha256=0YjPx2v1yYumZ2o7pVZ1s_jS-GAb3Ng979KEFhROSNY,1399
5
5
  maltoolbox/file_utils.py,sha256=6KFEEZvf9x8yfNAq7hadF7lUGlLimNFMJ0W_DK2rh6Q,2024
6
6
  maltoolbox/model.py,sha256=1HuH58skm1StcKtvMFO-iXkpLOaauB9YSEv1XNdW_-Y,30406
7
7
  maltoolbox/wrappers.py,sha256=BYYNcIdTlyumADQCPcy1xmPEabfmi0P1l9RcbdVWm9w,2002
8
- maltoolbox/attackgraph/__init__.py,sha256=F6maWV2PYSH5xtbqozeM3IolalWQqs-tdaCG2ywbqKI,102
8
+ maltoolbox/attackgraph/__init__.py,sha256=Oqqj5iCwnrzjDoJEFZnVI_kebjJPVbPXK-mWHy0lf-8,209
9
9
  maltoolbox/attackgraph/attacker.py,sha256=hmJtgcwtQgz0r9J_0Pgm7ktfKpFE3dp-HJ2TOz1NiWw,2880
10
- maltoolbox/attackgraph/attackgraph.py,sha256=lIn7_k7T6DL4AovNtBTdamsJ5j5080HCk0OWNqUExj8,28254
11
- maltoolbox/attackgraph/node.py,sha256=XSjMPqCGtnMeTXJDS2QluLcK5sLlsxpFIFpy2twJ2PQ,4603
12
- maltoolbox/attackgraph/query.py,sha256=ukMfkSnixBenfeg5H1SokoSftGo-V4xPL5qpgSaeMN4,5232
10
+ maltoolbox/attackgraph/attackgraph.py,sha256=Gx88-3M1HacMZRE9YgIGg-6cxCFVZg23bj_pkxDeEH4,29182
11
+ maltoolbox/attackgraph/node.py,sha256=ZYnXMIucmTIspQvBD-zyB_wnYO6j27HxQahuQGpYbkE,5640
12
+ maltoolbox/attackgraph/query.py,sha256=JnoNTUEIlLv2VIk3u5Rq3GpleOn9TZVGBVijniRY_44,6802
13
13
  maltoolbox/attackgraph/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  maltoolbox/attackgraph/analyzers/apriori.py,sha256=t_QdfetgNoDXFwlOasrmtBE7XaTaKZvnQCo6X9rs5Xg,6481
15
15
  maltoolbox/ingestors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  maltoolbox/ingestors/neo4j.py,sha256=jdulYsQ2eZT2r0Af_yYjyGkmVx4l5h8viu1Z70NjVAM,8811
17
- maltoolbox/language/__init__.py,sha256=OtqUWq8BRvqR5h8Hr4Smlnyz2az4w1pqyWYsbZDsolI,93
18
- maltoolbox/language/classes_factory.py,sha256=MNqiDemaflloVMRhjg1ZkUcQObKNEaGxAoXhNQQqem0,9983
17
+ maltoolbox/language/__init__.py,sha256=0tvCJDayrLwpqKRny7LBkMOrvcDE6JfJj7U7Jd64Okg,140
18
+ maltoolbox/language/classes_factory.py,sha256=0QFF5Z9e4UeWwavvH1jM4BkeDZqKKZ4Ij7leGmSfXn4,10063
19
19
  maltoolbox/language/languagegraph.py,sha256=f79ovmrGQb6tvY9ze-zqP031N6rApB9WsbZd5LRyhk8,47031
20
20
  maltoolbox/language/compiler/__init__.py,sha256=fJ22-FlXfr907WCPkqlr_eBTzPqsrg6m3i7J_ZWpuAo,840
21
21
  maltoolbox/language/compiler/mal_lexer.py,sha256=wocRzBkLbqYefpGvq2W77x7439-AdZKVgPWhRiRubXg,10776
@@ -24,9 +24,9 @@ maltoolbox/language/compiler/mal_visitor.py,sha256=9gG06D7GZKlBY-62SmbIkRYkGBUBI
24
24
  maltoolbox/translators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  maltoolbox/translators/securicad.py,sha256=FAIHnoqFTmNYbCGxLsK6pX5g1oiNFfPTqkT_3qq3GG8,6692
26
26
  maltoolbox/translators/updater.py,sha256=Ap08-AsU_7or5ESQvZL2i4nWz3B5pvgfftZyc_-Gd8M,4766
27
- mal_toolbox-0.1.8.dist-info/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
28
- mal_toolbox-0.1.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
29
- mal_toolbox-0.1.8.dist-info/METADATA,sha256=qzQgXn7tofBgGq_HKWwqk7NFLcQnQobIVI8oYNqNRcY,5953
30
- mal_toolbox-0.1.8.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
31
- mal_toolbox-0.1.8.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
32
- mal_toolbox-0.1.8.dist-info/RECORD,,
27
+ mal_toolbox-0.1.9.dist-info/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
28
+ mal_toolbox-0.1.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
29
+ mal_toolbox-0.1.9.dist-info/METADATA,sha256=ceylz74EpI9pdyIeJQCzeU_HcqpEB4-riD12UjpLyv8,6003
30
+ mal_toolbox-0.1.9.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
31
+ mal_toolbox-0.1.9.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
32
+ mal_toolbox-0.1.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
maltoolbox/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v0.1.8
2
+ # MAL Toolbox v0.1.9
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.8'
24
+ __version__ = '0.1.9'
25
25
  __authors__ = ['Andrei Buhaiu',
26
26
  'Giuseppe Nebbione',
27
27
  'Nikolaos Kakouros',
@@ -1,3 +1,8 @@
1
+ """
2
+ Contains tools used to generate attack graphs from MAL instance
3
+ models and analyze attack graphs.
4
+ """
5
+
1
6
  from .attacker import Attacker
2
7
  from .attackgraph import AttackGraph
3
8
  from .node import AttackGraphNode
@@ -2,6 +2,7 @@
2
2
  MAL-Toolbox Attack Graph Module
3
3
  """
4
4
  from __future__ import annotations
5
+ import copy
5
6
  import logging
6
7
  import json
7
8
 
@@ -224,6 +225,30 @@ class AttackGraph():
224
225
  'attackers': serialized_attackers,
225
226
  }
226
227
 
228
+ def __deepcopy__(self, memo):
229
+ copied_attackgraph = AttackGraph(self.lang_graph)
230
+ copied_attackgraph.model = self.model
231
+
232
+ # Deep copy nodes and add references to them
233
+ copied_attackgraph.nodes = copy.deepcopy(self.nodes, memo)
234
+
235
+ # Deep copy attackers and references to them
236
+ copied_attackgraph.attackers = copy.deepcopy(self.attackers, memo)
237
+
238
+ # Copy lookup dicts
239
+ copied_attackgraph._id_to_attacker = \
240
+ copy.deepcopy(self._id_to_attacker, memo)
241
+ copied_attackgraph._id_to_node = \
242
+ copy.deepcopy(self._id_to_node, memo)
243
+ copied_attackgraph._full_name_to_node = \
244
+ copy.deepcopy(self._full_name_to_node, memo)
245
+
246
+ # Copy counters
247
+ copied_attackgraph.next_node_id = self.next_node_id
248
+ copied_attackgraph.next_attacker_id = self.next_attacker_id
249
+
250
+ return copied_attackgraph
251
+
227
252
  def save_to_file(self, filename: str) -> None:
228
253
  """Save to json/yml depending on extension"""
229
254
  logger.debug('Save attack graph to file "%s".', filename)
@@ -382,7 +407,7 @@ class AttackGraph():
382
407
  The attack step node that matches the given full name.
383
408
  """
384
409
 
385
- logger.debug(f'Looking up node with id {full_name}')
410
+ logger.debug(f'Looking up node with full name "{full_name}"')
386
411
  return self._full_name_to_node.get(full_name)
387
412
 
388
413
  def get_attacker_by_id(self, attacker_id: int) -> Optional[Attacker]:
@@ -3,6 +3,7 @@ MAL-Toolbox Attack Graph Node Dataclass
3
3
  """
4
4
 
5
5
  from __future__ import annotations
6
+ import copy
6
7
  from dataclasses import field, dataclass
7
8
  from typing import Any, Optional
8
9
 
@@ -69,6 +70,39 @@ class AttackGraphNode:
69
70
  def __repr__(self) -> str:
70
71
  return str(self.to_dict())
71
72
 
73
+ def __deepcopy__(self, memo) -> AttackGraphNode:
74
+ """Deep copy an attackgraph node"""
75
+
76
+ copied_node = AttackGraphNode(
77
+ self.type,
78
+ self.name,
79
+ self.ttc,
80
+ self.id,
81
+ self.asset,
82
+ [],
83
+ [],
84
+ self.defense_status,
85
+ self.existence_status,
86
+ self.is_viable,
87
+ self.is_necessary,
88
+ copy.deepcopy(self.compromised_by, memo),
89
+ self.mitre_info,
90
+ copy.deepcopy(self.tags, memo),
91
+ copy.deepcopy(self.attributes, memo),
92
+ copy.deepcopy(self.extras, memo)
93
+ )
94
+
95
+ # Remember that self was already copied
96
+ memo[id(self)] = copied_node
97
+
98
+ # Deep copy children and parents, send memo (avoid infinite recursion)
99
+ if self.parents:
100
+ copied_node.parents = copy.deepcopy(self.parents, memo)
101
+ if self.children:
102
+ copied_node.children = copy.deepcopy(self.children, memo)
103
+
104
+ return copied_node
105
+
72
106
  def is_compromised(self) -> bool:
73
107
  """
74
108
  Return True if any attackers have compromised this node.
@@ -37,10 +37,21 @@ def is_node_traversable_by_attacker(
37
37
  attacker.id
38
38
  )
39
39
  if not node.is_viable:
40
+ logger.debug(
41
+ '"%s"(%d) is not traversable because it is non-viable',
42
+ node.full_name,
43
+ node.id,
44
+ )
40
45
  return False
41
46
 
42
47
  match(node.type):
43
48
  case 'or':
49
+ logger.debug(
50
+ '"%s"(%d) is traversable because it is viable and '
51
+ 'of type "or".',
52
+ node.full_name,
53
+ node.id
54
+ )
44
55
  return True
45
56
 
46
57
  case 'and':
@@ -49,14 +60,44 @@ def is_node_traversable_by_attacker(
49
60
  not parent.is_compromised_by(attacker):
50
61
  # If the parent is not present in the attacks steps
51
62
  # already reached and is necessary.
63
+ logger.debug(
64
+ '"%s"(%d) is not traversable because while it is '
65
+ 'viable, and of type "and", its necessary parent '
66
+ '"%s(%d)" has not already been compromised.',
67
+ node.full_name,
68
+ node.id,
69
+ parent.full_name,
70
+ parent.id
71
+ )
52
72
  return False
73
+ logger.debug(
74
+ '"%s"(%d) is traversable because it is viable, '
75
+ 'of type "and", and all of its necessary parents have '
76
+ 'already been compromised.',
77
+ node.full_name,
78
+ node.id
79
+ )
53
80
  return True
54
81
 
55
- case 'exist' | 'notExist' | 'defense':
82
+ case 'exist' | 'notExist' | 'defense':
83
+ logger.warning(
84
+ 'Nodes of type "exist", "notExist", and "defense" are never '
85
+ 'marked as traversable. However, we do not normally check '
86
+ 'if they are traversable. Node "%s"(%d) of type "%s" was '
87
+ 'checked for traversability.',
88
+ node.full_name,
89
+ node.id,
90
+ node.type
91
+ )
56
92
  return False
57
93
 
58
94
  case _:
59
- logger.error('Unknown node type %s.', node.type)
95
+ logger.error(
96
+ 'Node "%s"(%d) has an unknown type "%s".',
97
+ node.full_name,
98
+ node.id,
99
+ node.type
100
+ )
60
101
  return False
61
102
 
62
103
  def get_attack_surface(
@@ -1,2 +1,4 @@
1
+ """Contains tools to process MAL languages"""
2
+
1
3
  from .languagegraph import LanguageGraph
2
4
  from .classes_factory import LanguageClassesFactory
@@ -1,5 +1,6 @@
1
1
  """
2
2
  MAL-Toolbox Language Classes Factory Module
3
+ Uses python_jsonschema_objects to generate python classes from a MAL language
3
4
  """
4
5
  from __future__ import annotations
5
6
  import json
@@ -25,7 +26,7 @@ class LanguageClassesFactory:
25
26
 
26
27
  def _generate_assets(self) -> None:
27
28
  """
28
- Generate JSON Schema for the assets in the language specification.
29
+ Generate JSON Schema for asset types in the language specification.
29
30
  """
30
31
  for asset in self.lang_graph.assets:
31
32
  logger.debug('Creating %s asset JSON schema entry.', asset.name)
@@ -67,7 +68,7 @@ class LanguageClassesFactory:
67
68
 
68
69
  def _generate_associations(self) -> None:
69
70
  """
70
- Generate JSON Schema for the associations in the language specification.
71
+ Generate JSON Schema for association types in the language specification.
71
72
  """
72
73
  def create_association_entry(assoc: SchemaGeneratedClass):
73
74
  logger.debug('Creating %s association JSON schema entry.', assoc.name)