mal-toolbox 0.1.8__tar.gz → 0.1.10__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.
Files changed (40) hide show
  1. {mal_toolbox-0.1.8/mal_toolbox.egg-info → mal_toolbox-0.1.10}/PKG-INFO +7 -1
  2. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/README.md +6 -0
  3. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10/mal_toolbox.egg-info}/PKG-INFO +7 -1
  4. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/__init__.py +2 -2
  5. mal_toolbox-0.1.10/maltoolbox/attackgraph/__init__.py +8 -0
  6. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/analyzers/apriori.py +9 -0
  7. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/attacker.py +23 -0
  8. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/attackgraph.py +57 -6
  9. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/node.py +43 -0
  10. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/query.py +43 -2
  11. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/__init__.py +2 -0
  12. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/classes_factory.py +3 -2
  13. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/pyproject.toml +2 -3
  14. mal_toolbox-0.1.8/maltoolbox/attackgraph/__init__.py +0 -3
  15. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/AUTHORS +0 -0
  16. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/LICENSE +0 -0
  17. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/mal_toolbox.egg-info/SOURCES.txt +0 -0
  18. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/mal_toolbox.egg-info/dependency_links.txt +0 -0
  19. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/mal_toolbox.egg-info/requires.txt +0 -0
  20. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/mal_toolbox.egg-info/top_level.txt +0 -0
  21. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/__main__.py +0 -0
  22. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  23. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/default.conf +0 -0
  24. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/exceptions.py +0 -0
  25. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/file_utils.py +0 -0
  26. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/ingestors/__init__.py +0 -0
  27. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/ingestors/neo4j.py +0 -0
  28. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/compiler/__init__.py +0 -0
  29. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/compiler/mal_lexer.py +0 -0
  30. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/compiler/mal_parser.py +0 -0
  31. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/compiler/mal_visitor.py +0 -0
  32. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/language/languagegraph.py +0 -0
  33. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/model.py +0 -0
  34. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/translators/__init__.py +0 -0
  35. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/translators/securicad.py +0 -0
  36. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/translators/updater.py +0 -0
  37. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/maltoolbox/wrappers.py +0 -0
  38. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/setup.cfg +0 -0
  39. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/tests/test_model.py +0 -0
  40. {mal_toolbox-0.1.8 → mal_toolbox-0.1.10}/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.8
3
+ Version: 0.1.10
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
@@ -75,6 +75,12 @@ and it can be used to visualise the instance model and the attack graph.
75
75
 
76
76
  # Usage
77
77
 
78
+ ## Installation
79
+
80
+ ```
81
+ pip install mal-toolbox
82
+ ```
83
+
78
84
  ## Configuration
79
85
  A default configuration file `default.conf` can be found in the package
80
86
  directory. This contains the default values to use for logging and can also be
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mal-toolbox
3
- Version: 0.1.8
3
+ Version: 0.1.10
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,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v0.1.8
2
+ # MAL Toolbox v0.1.10
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.10'
25
25
  __authors__ = ['Andrei Buhaiu',
26
26
  'Giuseppe Nebbione',
27
27
  'Nikolaos Kakouros',
@@ -0,0 +1,8 @@
1
+ """
2
+ Contains tools used to generate attack graphs from MAL instance
3
+ models and analyze attack graphs.
4
+ """
5
+
6
+ from .attacker import Attacker
7
+ from .attackgraph import AttackGraph
8
+ from .node import AttackGraphNode
@@ -51,6 +51,15 @@ def propagate_necessity_from_node(node: AttackGraphNode) -> None:
51
51
  'Propagate necessity from "%s"(%d) with necessity status %s.',
52
52
  node.full_name, node.id, node.is_necessary
53
53
  )
54
+
55
+ if node.ttc and 'name' in node.ttc:
56
+ if node.ttc['name'] not in ['Enabled', 'Disabled']:
57
+ # Do not propagate unnecessary state from nodes that have a TTC
58
+ # probability distribution associated with them.
59
+ # TODO: Evaluate this more carefully, how do we want to have TTCs
60
+ # impact necessity and viability.
61
+ return
62
+
54
63
  for child in node.children:
55
64
  original_value = child.is_necessary
56
65
  if child.type == 'or':
@@ -4,6 +4,7 @@ MAL-Toolbox Attack Graph Attacker Class
4
4
 
5
5
  from __future__ import annotations
6
6
  from dataclasses import dataclass, field
7
+ import copy
7
8
  import logging
8
9
 
9
10
  from typing import Optional
@@ -41,6 +42,28 @@ class Attacker:
41
42
  def __repr__(self) -> str:
42
43
  return str(self.to_dict())
43
44
 
45
+ def __deepcopy__(self, memo) -> Attacker:
46
+ """Deep copy an Attacker"""
47
+
48
+ # Check if the object is already in the memo dictionary
49
+ if id(self) in memo:
50
+ return memo[id(self)]
51
+
52
+ copied_attacker = Attacker(
53
+ id = self.id,
54
+ name = self.name,
55
+ )
56
+
57
+ # Remember that self was already copied
58
+ memo[id(self)] = copied_attacker
59
+
60
+ copied_attacker.entry_points = copy.deepcopy(
61
+ self.entry_points, memo = memo)
62
+ copied_attacker.reached_attack_steps = copy.deepcopy(
63
+ self.reached_attack_steps, memo = memo)
64
+
65
+ return copied_attacker
66
+
44
67
  def compromise(self, node: AttackGraphNode) -> None:
45
68
  """
46
69
  Have the attacke compromise the node given as a parameter.
@@ -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,52 @@ class AttackGraph():
224
225
  'attackers': serialized_attackers,
225
226
  }
226
227
 
228
+ def __deepcopy__(self, memo):
229
+
230
+ # Check if the object is already in the memo dictionary
231
+ if id(self) in memo:
232
+ return memo[id(self)]
233
+
234
+ copied_attackgraph = AttackGraph(self.lang_graph)
235
+ copied_attackgraph.model = self.model
236
+
237
+ copied_attackgraph.nodes = []
238
+
239
+ # Deep copy nodes
240
+ for node in self.nodes:
241
+ copied_node = copy.deepcopy(node, memo)
242
+ copied_attackgraph.nodes.append(copied_node)
243
+
244
+ # Re-link node references
245
+ for node in self.nodes:
246
+ if node.parents:
247
+ memo[id(node)].parents = copy.deepcopy(node.parents, memo)
248
+ if node.children:
249
+ memo[id(node)].children = copy.deepcopy(node.children, memo)
250
+
251
+ # Deep copy attackers and references to them
252
+ copied_attackgraph.attackers = copy.deepcopy(self.attackers, memo)
253
+
254
+ # Re-link attacker references
255
+ for node in self.nodes:
256
+ if node.compromised_by:
257
+ memo[id(node)].compromised_by = copy.deepcopy(
258
+ node.compromised_by, memo)
259
+
260
+ # Copy lookup dicts
261
+ copied_attackgraph._id_to_attacker = \
262
+ copy.deepcopy(self._id_to_attacker, memo)
263
+ copied_attackgraph._id_to_node = \
264
+ copy.deepcopy(self._id_to_node, memo)
265
+ copied_attackgraph._full_name_to_node = \
266
+ copy.deepcopy(self._full_name_to_node, memo)
267
+
268
+ # Copy counters
269
+ copied_attackgraph.next_node_id = self.next_node_id
270
+ copied_attackgraph.next_attacker_id = self.next_attacker_id
271
+
272
+ return copied_attackgraph
273
+
227
274
  def save_to_file(self, filename: str) -> None:
228
275
  """Save to json/yml depending on extension"""
229
276
  logger.debug('Save attack graph to file "%s".', filename)
@@ -329,7 +376,10 @@ class AttackGraph():
329
376
  attacker = ag_attacker,
330
377
  attacker_id = int(attacker['id']),
331
378
  entry_points = attacker['entry_points'].keys(),
332
- reached_attack_steps = attacker['reached_attack_steps'].keys()
379
+ reached_attack_steps = [
380
+ int(node_id) # Convert to int since they can be strings
381
+ for node_id in attacker['reached_attack_steps'].keys()
382
+ ]
333
383
  )
334
384
 
335
385
  return attack_graph
@@ -382,7 +432,7 @@ class AttackGraph():
382
432
  The attack step node that matches the given full name.
383
433
  """
384
434
 
385
- logger.debug(f'Looking up node with id {full_name}')
435
+ logger.debug(f'Looking up node with full name "{full_name}"')
386
436
  return self._full_name_to_node.get(full_name)
387
437
 
388
438
  def get_attacker_by_id(self, attacker_id: int) -> Optional[Attacker]:
@@ -441,7 +491,7 @@ class AttackGraph():
441
491
  continue
442
492
  attacker.compromise(ag_node)
443
493
 
444
- attacker.entry_points = attacker.reached_attack_steps
494
+ attacker.entry_points = list(attacker.reached_attack_steps)
445
495
 
446
496
  def _generate_graph(self) -> None:
447
497
  """
@@ -587,7 +637,7 @@ class AttackGraph():
587
637
  if logger.isEnabledFor(logging.DEBUG):
588
638
  # Avoid running json.dumps when not in debug
589
639
  logger.debug(f'Add node \"{node.full_name}\" '
590
- 'with id:{node_id}:\n' \
640
+ f'with id:{node_id}:\n' \
591
641
  + json.dumps(node.to_dict(), indent = 2))
592
642
 
593
643
  if node.id in self._id_to_node:
@@ -644,7 +694,8 @@ class AttackGraph():
644
694
  attacker.name,
645
695
  attacker_id)
646
696
  else:
647
- logger.debug('Add attacker "%s" without id.')
697
+ logger.debug('Add attacker "%s" without id.',
698
+ attacker.name)
648
699
 
649
700
 
650
701
  attacker.id = attacker_id or self.next_attacker_id
@@ -653,7 +704,7 @@ class AttackGraph():
653
704
 
654
705
  self.next_attacker_id = max(attacker.id + 1, self.next_attacker_id)
655
706
  for node_id in reached_attack_steps:
656
- node = self.get_node_by_id(int(node_id))
707
+ node = self.get_node_by_id(node_id)
657
708
  if node:
658
709
  attacker.compromise(node)
659
710
  else:
@@ -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,48 @@ 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
+ The deepcopy will copy over node specific information, such as type,
77
+ name, etc., but it will not copy attack graph relations such as
78
+ parents, children, or attackers it is compromised by. These references
79
+ should be recreated when deepcopying the attack graph itself.
80
+
81
+ """
82
+
83
+ # Check if the object is already in the memo dictionary
84
+ if id(self) in memo:
85
+ return memo[id(self)]
86
+
87
+ copied_node = AttackGraphNode(
88
+ self.type,
89
+ self.name,
90
+ self.ttc,
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
+ {}
104
+ )
105
+
106
+ copied_node.tags = copy.deepcopy(self.tags, memo)
107
+ copied_node.attributes = copy.deepcopy(self.attributes, memo)
108
+ copied_node.extras = copy.deepcopy(self.extras, memo)
109
+
110
+ # Remember that self was already copied
111
+ memo[id(self)] = copied_node
112
+
113
+ return copied_node
114
+
72
115
  def is_compromised(self) -> bool:
73
116
  """
74
117
  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)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mal-toolbox"
3
- version = "0.1.8"
3
+ version = "0.1.10"
4
4
  authors = [
5
5
  { name="Andrei Buhaiu", email="buhaiu@kth.se" },
6
6
  { name="Giuseppe Nebbione", email="nebbione@kth.se" },
@@ -53,7 +53,6 @@ filterwarnings = "ignore:invalid escape sequence"
53
53
  exclude = [
54
54
  'maltoolbox/ingestors',
55
55
  'maltoolbox/translators',
56
- 'maltoolbox/language/compiler',
57
- 'venv'
56
+ 'maltoolbox/language/compiler'
58
57
  ]
59
58
  allow_redefinition = true
@@ -1,3 +0,0 @@
1
- from .attacker import Attacker
2
- from .attackgraph import AttackGraph
3
- from .node import AttackGraphNode
File without changes
File without changes
File without changes