mal-toolbox 1.1.1__py3-none-any.whl → 1.1.3__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.
Files changed (31) hide show
  1. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/METADATA +25 -2
  2. mal_toolbox-1.1.3.dist-info/RECORD +32 -0
  3. maltoolbox/__init__.py +6 -7
  4. maltoolbox/__main__.py +17 -9
  5. maltoolbox/attackgraph/__init__.py +2 -3
  6. maltoolbox/attackgraph/attackgraph.py +379 -362
  7. maltoolbox/attackgraph/node.py +14 -19
  8. maltoolbox/exceptions.py +7 -10
  9. maltoolbox/file_utils.py +10 -4
  10. maltoolbox/language/__init__.py +1 -1
  11. maltoolbox/language/compiler/__init__.py +4 -4
  12. maltoolbox/language/compiler/mal_lexer.py +154 -154
  13. maltoolbox/language/compiler/mal_parser.py +784 -1136
  14. maltoolbox/language/languagegraph.py +487 -639
  15. maltoolbox/model.py +64 -77
  16. maltoolbox/patternfinder/attackgraph_patterns.py +17 -8
  17. maltoolbox/translators/__init__.py +8 -0
  18. maltoolbox/translators/networkx.py +42 -0
  19. maltoolbox/translators/updater.py +18 -25
  20. maltoolbox/visualization/__init__.py +4 -4
  21. maltoolbox/visualization/draw_io_utils.py +6 -5
  22. maltoolbox/visualization/graphviz_utils.py +4 -2
  23. maltoolbox/visualization/neo4j_utils.py +13 -14
  24. maltoolbox/visualization/utils.py +2 -3
  25. mal_toolbox-1.1.1.dist-info/RECORD +0 -32
  26. maltoolbox/translators/securicad.py +0 -179
  27. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/WHEEL +0 -0
  28. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/entry_points.txt +0 -0
  29. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/licenses/AUTHORS +0 -0
  30. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/licenses/LICENSE +0 -0
  31. {mal_toolbox-1.1.1.dist-info → mal_toolbox-1.1.3.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  import random
2
+
2
3
  import graphviz
3
4
 
4
- from ..model import Model
5
5
  from ..attackgraph import AttackGraph
6
+ from ..model import Model
6
7
 
7
8
  graphviz_bright_colors = [
8
9
  "aliceblue", "antiquewhite", "antiquewhite1", "antiquewhite2", "azure", "azure1", "azure2",
@@ -60,11 +61,12 @@ def render_model(model: Model):
60
61
  )
61
62
  dot.render(directory='.', view=True)
62
63
 
64
+
63
65
  def render_attack_graph(attack_graph: AttackGraph):
64
66
  """Render attack graph graphviz, create pdf and open it"""
65
67
  assert attack_graph.model, "Attack graph needs a model"
66
68
  dot = graphviz.Graph(attack_graph.model.name)
67
- dot.graph_attr['nodesep'] = '3.0' # Node separation
69
+ dot.graph_attr['nodesep'] = '3.0' # Node separation
68
70
  dot.graph_attr['ratio'] = 'compress'
69
71
 
70
72
  # Create nodes
@@ -1,24 +1,24 @@
1
- """
2
- MAL-Toolbox Neo4j Ingestor Module
1
+ """MAL-Toolbox Neo4j Ingestor Module
3
2
  """
4
3
  # mypy: ignore-errors
5
4
 
6
5
  import logging
7
-
8
6
  from typing import Any
7
+
9
8
  from py2neo import Graph, Node, Relationship, Subgraph
10
9
 
11
10
  logger = logging.getLogger(__name__)
12
11
 
12
+
13
13
  def ingest_attack_graph_neo4j(
14
14
  graph,
15
15
  neo4j_config: dict[str, Any],
16
16
  delete: bool = True
17
17
  ) -> None:
18
- """
19
- Ingest an attack graph into a neo4j database
18
+ """Ingest an attack graph into a neo4j database
20
19
 
21
20
  Arguments:
21
+ ---------
22
22
  graph - the attackgraph provided by the atkgraph.py module.
23
23
  uri - the URI to a running neo4j instance
24
24
  username - the username to login on Neo4J
@@ -26,8 +26,8 @@ def ingest_attack_graph_neo4j(
26
26
  dbname - the selected database
27
27
  delete - if True, the previous content of the database is deleted
28
28
  before ingesting the new attack graph
29
- """
30
29
 
30
+ """
31
31
  uri = neo4j_config.get('uri')
32
32
  username = neo4j_config.get('username')
33
33
  password = neo4j_config.get('password')
@@ -43,13 +43,12 @@ def ingest_attack_graph_neo4j(
43
43
  node_dict = node.to_dict()
44
44
  nodes[node.id] = Node(
45
45
  node_dict['asset'] if 'asset' in node_dict else node_dict['id'],
46
- name = node_dict['name'],
47
- full_name = node.full_name,
48
- type = node_dict['type'],
49
- ttc = str(node_dict['ttc']),
46
+ name=node_dict['name'],
47
+ full_name=node.full_name,
48
+ type=node_dict['type'],
49
+ ttc=str(node_dict['ttc']),
50
50
  )
51
51
 
52
-
53
52
  for node in graph.nodes.values():
54
53
  for child in node.children:
55
54
  rels.append(Relationship(nodes[node.id], nodes[child.id]))
@@ -66,10 +65,10 @@ def ingest_model_neo4j(
66
65
  neo4j_config: dict[str, Any],
67
66
  delete: bool = True
68
67
  ) -> None:
69
- """
70
- Ingest an instance model graph into a Neo4J database
68
+ """Ingest an instance model graph into a Neo4J database
71
69
 
72
70
  Arguments:
71
+ ---------
73
72
  model - the instance model dictionary as provided by the model.py module
74
73
  uri - the URI to a running neo4j instance
75
74
  username - the username to login on Neo4J
@@ -77,8 +76,8 @@ def ingest_model_neo4j(
77
76
  dbname - the selected database
78
77
  delete - if True, the previous content of the database is deleted
79
78
  before ingesting the new attack graph
80
- """
81
79
 
80
+ """
82
81
  uri = neo4j_config.get('uri')
83
82
  username = neo4j_config.get('username')
84
83
  password = neo4j_config.get('password')
@@ -1,14 +1,13 @@
1
1
  from maltoolbox.model import Model
2
2
 
3
+
3
4
  def position_assets(model: Model):
4
- """
5
- Assigns (x, y) positions to assets in a graph where relations are stored
5
+ """Assigns (x, y) positions to assets in a graph where relations are stored
6
6
  in asset.associated_assets[relation_name] = [related_assets...].
7
7
  Positions are stored in asset.extras['position'] = {'x': ..., 'y': ...}.
8
8
  Layout is computed by traversing connected components.
9
9
  Adds uniform padding between assets.
10
10
  """
11
-
12
11
  visited = set()
13
12
  x_spacing = 200
14
13
  y_spacing = 200
@@ -1,32 +0,0 @@
1
- mal_toolbox-1.1.1.dist-info/licenses/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
2
- mal_toolbox-1.1.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
3
- maltoolbox/__init__.py,sha256=XZ8sSK4zPhqPNVWRIy-JKgipTyX3dx6CpnloZs37xlY,2158
4
- maltoolbox/__main__.py,sha256=XREFzTuS_hIajSt1aNdzW8a5XFS3lAGrenEBnB_zzjI,3484
5
- maltoolbox/exceptions.py,sha256=0YjPx2v1yYumZ2o7pVZ1s_jS-GAb3Ng979KEFhROSNY,1399
6
- maltoolbox/file_utils.py,sha256=fYG3UsvPQcU0ES_WI3nLfuzSZgc0jtE4IAxdMGgs9aA,1876
7
- maltoolbox/model.py,sha256=0z0S0SH4lkslsAxPNxb7exfmNwBPXM_BK0Oujo899a4,18139
8
- maltoolbox/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- maltoolbox/attackgraph/__init__.py,sha256=m_81AjzwXONdclcW_R7mF2f8p-4DvoSRVfQ3Nyh7fak,298
10
- maltoolbox/attackgraph/attackgraph.py,sha256=ux6TjBLfAgKEfwzGqNU6D_xDGCzc4UTCrdTh1ADoKeI,27722
11
- maltoolbox/attackgraph/node.py,sha256=F48FgCeYQFEiAq1HswjOnHImYSSRINZpHDvUZP0FqRk,4265
12
- maltoolbox/attackgraph/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- maltoolbox/language/__init__.py,sha256=TsTTryEyjChwHN1o5F2BSUlFsAss2N6J0H0-nzvXiD8,489
14
- maltoolbox/language/languagegraph.py,sha256=fNQRSDE_XKQMGgxZe75kpBbLpqjhDD5guAmmx9nSZR0,72143
15
- maltoolbox/language/compiler/__init__.py,sha256=JQyAgDwJh1pU7AmuOhd1-d2b2PYXpgMVPtxnav8QHVc,15872
16
- maltoolbox/language/compiler/mal_lexer.py,sha256=BeifykDAt4PloRASOaLzBgWF35ev_zgD8lXMIsSHykc,12063
17
- maltoolbox/language/compiler/mal_parser.py,sha256=sUoaE43l2VKg-Dou30mk2wlVS1FvdOREwHNIyFe4IkY,114699
18
- maltoolbox/patternfinder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- maltoolbox/patternfinder/attackgraph_patterns.py,sha256=jgW7UG1yBJ08jvuokdpSiHm7jDMg2PTcjfGXZ8UJAnw,5082
20
- maltoolbox/translators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- maltoolbox/translators/securicad.py,sha256=F_rndv2JyKxfHAXPwf2RrdiFPnemJVArYUpVsFP6QQk,6997
22
- maltoolbox/translators/updater.py,sha256=UZPnx22udROiocCcSmtrgUJUupkjktkxl-M7rhBxUPc,8660
23
- maltoolbox/visualization/__init__.py,sha256=JM-cfT_9wtA5GJOAiS_2jtY5TYsuHOFqgn1cZpmcAeE,349
24
- maltoolbox/visualization/draw_io_utils.py,sha256=7ZmhG5yTEYLZnMhOY9gfyKOb4Dmu2K9v1IdL6Qy-8i0,14372
25
- maltoolbox/visualization/graphviz_utils.py,sha256=dfQhPL6Z2hvlMFpThsDr-5tm4Pa22SGHEiXw5ym9JJc,3906
26
- maltoolbox/visualization/neo4j_utils.py,sha256=FHTkkaTvlrf36RXdVdD65TYtlKLNcna9pCl39d6o8wg,3487
27
- maltoolbox/visualization/utils.py,sha256=8SrGMUXJPFBjDd2Ujw7jgoR2zqCNFdoNHGmG4_tEhwQ,1485
28
- mal_toolbox-1.1.1.dist-info/METADATA,sha256=Fcv-ZEhpIHUPmGkmR7_QgfkxCt3fncco7BFVl_J9KAs,5960
29
- mal_toolbox-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- mal_toolbox-1.1.1.dist-info/entry_points.txt,sha256=oqby5O6cUP_OHCm70k_iYPA6UlbTBf7se1i3XwdK3uU,56
31
- mal_toolbox-1.1.1.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
32
- mal_toolbox-1.1.1.dist-info/RECORD,,
@@ -1,179 +0,0 @@
1
- """
2
- MAL-Toolbox securiCAD Translator Module
3
- """
4
-
5
- import zipfile
6
- import json
7
- import logging
8
- import xml.etree.ElementTree as ET
9
-
10
- from typing import Optional
11
-
12
- from ..model import Model
13
- from ..language import LanguageGraph
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
- # TODO: Update this at some point
18
-
19
- # def load_model_from_scad_archive(
20
- # scad_archive: str,
21
- # lang_graph: LanguageGraph
22
- # ) -> Optional[Model]:
23
- # """
24
- # Reads a '.sCAD' archive generated by securiCAD representing an instance
25
- # model and loads the information into a maltoobox.model.Model object.
26
- #
27
- # Arguments:
28
- # scad_archive - the path to a '.sCAD' archive
29
- # lang_graph - a language graph representing the MAL
30
- # language specification
31
- #
32
- # Return:
33
- # A maltoobox.model.Model object containing the instance model.
34
- # """
35
- # with zipfile.ZipFile(scad_archive, 'r') as archive:
36
- # filelist = archive.namelist()
37
- # model_file = next(filter(lambda x: ( x[-4:] == '.eom'), filelist))
38
- # scad_model = archive.read(model_file)
39
- # root = ET.fromstring(scad_model)
40
- #
41
- # instance_model = Model(scad_archive,
42
- # lang_classes_factory)
43
- #
44
- # for child in root.iter('objects'):
45
- #
46
- # if logger.isEnabledFor(logging.DEBUG):
47
- # # Avoid running json.dumps when not in debug
48
- # logger.debug(
49
- # 'Loading asset from "%s": \n%s',
50
- # scad_archive, json.dumps(child.attrib, indent=2)
51
- # )
52
- #
53
- # if child.attrib['metaConcept'] == 'Attacker':
54
- # attacker_obj_id = int(child.attrib['id'])
55
- # attacker_at = AttackerAttachment()
56
- # attacker_at.entry_points = []
57
- # instance_model.add_attacker(
58
- # attacker_at,
59
- # attacker_id = attacker_obj_id
60
- # )
61
- # continue
62
- #
63
- # if not hasattr(lang_classes_factory.ns,
64
- # child.attrib['metaConcept']):
65
- # logger.error(
66
- # 'Failed to find %s asset in language specification!',
67
- # child.attrib["metaConcept"]
68
- # )
69
- # return None
70
- # asset = getattr(lang_classes_factory.ns,
71
- # child.attrib['metaConcept'])(name = child.attrib['name'])
72
- # asset_id = int(child.attrib['id'])
73
- # for subchild in child.iter('evidenceAttributes'):
74
- # defense_name = subchild.attrib['metaConcept']
75
- # defense_name = defense_name[0].lower() + defense_name[1:]
76
- # for distrib in subchild.iter('evidenceDistribution'):
77
- # for d in distrib.iter('parameters'):
78
- # if 'value' in d.attrib:
79
- # dist_value = d.attrib['value']
80
- # setattr(asset, defense_name, float(dist_value))
81
- # instance_model.add_asset(asset, asset_id)
82
- #
83
- # for child in root.iter('associations'):
84
- # logger.debug(
85
- # 'Load association ("%s", "%s", "%s", "%s") from %s',
86
- # child.attrib["sourceObject"], child.attrib["targetObject"],
87
- # child.attrib["targetProperty"], child.attrib["sourceProperty"],
88
- # scad_archive
89
- # )
90
- # # Note: This is not a bug in the code. The fields and assets are
91
- # # listed incorrectly in the securiCAD format where the source asset
92
- # # matches the target field and vice versa.
93
- # left_id = int(child.attrib['targetObject'])
94
- # right_id = int(child.attrib['sourceObject'])
95
- # attacker_id = None
96
- # if child.attrib['sourceProperty'] == 'firstSteps':
97
- # attacker_id = right_id
98
- # target_id = left_id
99
- # target_prop = child.attrib['targetProperty']
100
- # elif child.attrib['targetProperty'] == 'firstSteps':
101
- # attacker_id = left_id
102
- # target_id = right_id
103
- # target_prop = child.attrib['sourceProperty']
104
- #
105
- # if attacker_id is not None:
106
- # attacker = instance_model.get_attacker_by_id(attacker_id)
107
- # if not attacker:
108
- # logger.error(
109
- # 'Failed to find attacker with id %s in model!',
110
- # attacker_id
111
- # )
112
- # return None
113
- # target_asset = instance_model.get_asset_by_id(target_id)
114
- # if not target_asset:
115
- # logger.error(
116
- # 'Failed to find asset with id %s in model!',
117
- # target_id
118
- # )
119
- # return None
120
- # attacker.entry_points.append((target_asset,
121
- # [target_prop.split('.')[0]]))
122
- # continue
123
- #
124
- # left_asset = instance_model.get_asset_by_id(left_id)
125
- # if not left_asset:
126
- # logger.error(
127
- # 'Failed to find asset with id %s in model!', left_id
128
- # )
129
- # return None
130
- # right_asset = instance_model.get_asset_by_id(right_id)
131
- # if not right_asset:
132
- # logger.error(
133
- # 'Failed to find asset with id %s in model!', right_id
134
- # )
135
- # return None
136
- #
137
- # # Note: This is not a bug in the code. The fields and assets are
138
- # # listed incorrectly in the securiCAD format where the source asset
139
- # # matches the target field and vice versa.
140
- # left_field = child.attrib['sourceProperty']
141
- # right_field = child.attrib['targetProperty']
142
- # lang_graph_assoc = None
143
- # for assoc in left_asset.lg_asset.associations:
144
- # if (assoc.left_field.fieldname == left_field and
145
- # assoc.right_field.fieldname == right_field) or \
146
- # (assoc.left_field.fieldname == right_field and
147
- # assoc.right_field.fieldname == left_field):
148
- # lang_graph_assoc = assoc
149
- # break
150
- #
151
- # if not lang_graph_assoc:
152
- # raise LookupError(
153
- # 'Failed to find ("%s", "%s", "%s", "%s")'
154
- # 'association in lang specification.' %
155
- # (left_asset.type, right_asset.type,
156
- # left_field, right_field)
157
- # )
158
- # return None
159
- #
160
- # logger.debug('Found "%s" association.', lang_graph_assoc.name)
161
- # assoc_name = lang_classes_factory.get_association_by_signature(
162
- # lang_graph_assoc.name,
163
- # left_asset.type,
164
- # right_asset.type
165
- # )
166
- #
167
- # if assoc_name is None:
168
- # logger.error(
169
- # 'Failed to find association with name \"%s\" in model!',
170
- # lang_graph_assoc.name
171
- # )
172
- # return None
173
- #
174
- # assoc = getattr(lang_classes_factory.ns, assoc_name)()
175
- # setattr(assoc, left_field, [left_asset])
176
- # setattr(assoc, right_field, [right_asset])
177
- # instance_model.add_association(assoc)
178
- #
179
- # return instance_model