mal-toolbox 0.1.9__tar.gz → 0.1.11__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.9/mal_toolbox.egg-info → mal_toolbox-0.1.11}/PKG-INFO +1 -1
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11/mal_toolbox.egg-info}/PKG-INFO +1 -1
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/__init__.py +2 -2
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/analyzers/apriori.py +9 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/attacker.py +23 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/attackgraph.py +33 -7
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/node.py +20 -11
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/model.py +5 -1
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/pyproject.toml +1 -1
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/tests/test_model.py +22 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/AUTHORS +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/LICENSE +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/README.md +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/mal_toolbox.egg-info/SOURCES.txt +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/mal_toolbox.egg-info/dependency_links.txt +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/mal_toolbox.egg-info/requires.txt +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/mal_toolbox.egg-info/top_level.txt +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/__main__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/attackgraph/query.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/default.conf +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/exceptions.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/file_utils.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/ingestors/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/ingestors/neo4j.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/classes_factory.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/compiler/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/compiler/mal_lexer.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/compiler/mal_parser.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/compiler/mal_visitor.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/language/languagegraph.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/translators/__init__.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/translators/securicad.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/translators/updater.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/maltoolbox/wrappers.py +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/setup.cfg +0 -0
- {mal_toolbox-0.1.9 → mal_toolbox-0.1.11}/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.11
|
|
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.11
|
|
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.11
|
|
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.11'
|
|
25
25
|
__authors__ = ['Andrei Buhaiu',
|
|
26
26
|
'Giuseppe Nebbione',
|
|
27
27
|
'Nikolaos Kakouros',
|
|
@@ -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.
|
|
@@ -226,15 +226,37 @@ class AttackGraph():
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
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
|
+
|
|
229
234
|
copied_attackgraph = AttackGraph(self.lang_graph)
|
|
230
235
|
copied_attackgraph.model = self.model
|
|
231
236
|
|
|
232
|
-
|
|
233
|
-
|
|
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)
|
|
234
250
|
|
|
235
251
|
# Deep copy attackers and references to them
|
|
236
252
|
copied_attackgraph.attackers = copy.deepcopy(self.attackers, memo)
|
|
237
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
|
+
|
|
238
260
|
# Copy lookup dicts
|
|
239
261
|
copied_attackgraph._id_to_attacker = \
|
|
240
262
|
copy.deepcopy(self._id_to_attacker, memo)
|
|
@@ -354,7 +376,10 @@ class AttackGraph():
|
|
|
354
376
|
attacker = ag_attacker,
|
|
355
377
|
attacker_id = int(attacker['id']),
|
|
356
378
|
entry_points = attacker['entry_points'].keys(),
|
|
357
|
-
reached_attack_steps =
|
|
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
|
+
]
|
|
358
383
|
)
|
|
359
384
|
|
|
360
385
|
return attack_graph
|
|
@@ -466,7 +491,7 @@ class AttackGraph():
|
|
|
466
491
|
continue
|
|
467
492
|
attacker.compromise(ag_node)
|
|
468
493
|
|
|
469
|
-
attacker.entry_points = attacker.reached_attack_steps
|
|
494
|
+
attacker.entry_points = list(attacker.reached_attack_steps)
|
|
470
495
|
|
|
471
496
|
def _generate_graph(self) -> None:
|
|
472
497
|
"""
|
|
@@ -612,7 +637,7 @@ class AttackGraph():
|
|
|
612
637
|
if logger.isEnabledFor(logging.DEBUG):
|
|
613
638
|
# Avoid running json.dumps when not in debug
|
|
614
639
|
logger.debug(f'Add node \"{node.full_name}\" '
|
|
615
|
-
'with id:{node_id}:\n' \
|
|
640
|
+
f'with id:{node_id}:\n' \
|
|
616
641
|
+ json.dumps(node.to_dict(), indent = 2))
|
|
617
642
|
|
|
618
643
|
if node.id in self._id_to_node:
|
|
@@ -669,7 +694,8 @@ class AttackGraph():
|
|
|
669
694
|
attacker.name,
|
|
670
695
|
attacker_id)
|
|
671
696
|
else:
|
|
672
|
-
logger.debug('Add attacker "%s" without id.'
|
|
697
|
+
logger.debug('Add attacker "%s" without id.',
|
|
698
|
+
attacker.name)
|
|
673
699
|
|
|
674
700
|
|
|
675
701
|
attacker.id = attacker_id or self.next_attacker_id
|
|
@@ -678,7 +704,7 @@ class AttackGraph():
|
|
|
678
704
|
|
|
679
705
|
self.next_attacker_id = max(attacker.id + 1, self.next_attacker_id)
|
|
680
706
|
for node_id in reached_attack_steps:
|
|
681
|
-
node = self.get_node_by_id(
|
|
707
|
+
node = self.get_node_by_id(node_id)
|
|
682
708
|
if node:
|
|
683
709
|
attacker.compromise(node)
|
|
684
710
|
else:
|
|
@@ -71,7 +71,18 @@ class AttackGraphNode:
|
|
|
71
71
|
return str(self.to_dict())
|
|
72
72
|
|
|
73
73
|
def __deepcopy__(self, memo) -> AttackGraphNode:
|
|
74
|
-
"""Deep copy an attackgraph node
|
|
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)]
|
|
75
86
|
|
|
76
87
|
copied_node = AttackGraphNode(
|
|
77
88
|
self.type,
|
|
@@ -85,22 +96,20 @@ class AttackGraphNode:
|
|
|
85
96
|
self.existence_status,
|
|
86
97
|
self.is_viable,
|
|
87
98
|
self.is_necessary,
|
|
88
|
-
|
|
99
|
+
[],
|
|
89
100
|
self.mitre_info,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
[],
|
|
102
|
+
{},
|
|
103
|
+
{}
|
|
93
104
|
)
|
|
94
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
|
+
|
|
95
110
|
# Remember that self was already copied
|
|
96
111
|
memo[id(self)] = copied_node
|
|
97
112
|
|
|
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
113
|
return copied_node
|
|
105
114
|
|
|
106
115
|
def is_compromised(self) -> bool:
|
|
@@ -207,6 +207,10 @@ class Model():
|
|
|
207
207
|
)
|
|
208
208
|
self.assets.append(asset)
|
|
209
209
|
|
|
210
|
+
def remove_attacker(self, attacker: AttackerAttachment) -> None:
|
|
211
|
+
"""Remove attacker"""
|
|
212
|
+
self.attackers.remove(attacker)
|
|
213
|
+
|
|
210
214
|
def remove_asset(self, asset: SchemaGeneratedClass) -> None:
|
|
211
215
|
"""Remove an asset from the model.
|
|
212
216
|
|
|
@@ -659,7 +663,7 @@ class Model():
|
|
|
659
663
|
|
|
660
664
|
if asset.extras:
|
|
661
665
|
# Add optional metadata to dict
|
|
662
|
-
asset_dict['extras'] = asset.extras
|
|
666
|
+
asset_dict['extras'] = asset.extras.as_dict()
|
|
663
667
|
|
|
664
668
|
return (asset.id, asset_dict)
|
|
665
669
|
|
|
@@ -195,6 +195,27 @@ def test_attacker_attachment_remove_asset(model: Model):
|
|
|
195
195
|
assert attacker2.entry_points[0][1] == ['read']
|
|
196
196
|
|
|
197
197
|
|
|
198
|
+
def test_add_remove_attacker(model: Model):
|
|
199
|
+
""""""
|
|
200
|
+
|
|
201
|
+
asset1 = create_application_asset(model, "Asset1")
|
|
202
|
+
model.add_asset(asset1)
|
|
203
|
+
|
|
204
|
+
attacker1 = AttackerAttachment()
|
|
205
|
+
model.add_attacker(attacker1)
|
|
206
|
+
attacker2 = AttackerAttachment()
|
|
207
|
+
model.add_attacker(attacker2)
|
|
208
|
+
|
|
209
|
+
attacker1.add_entry_point(asset1, 'read')
|
|
210
|
+
attacker1.add_entry_point(asset1, 'access')
|
|
211
|
+
attacker2.add_entry_point(asset1, 'read')
|
|
212
|
+
|
|
213
|
+
assert len(model.attackers) == 2
|
|
214
|
+
model.remove_attacker(attacker1)
|
|
215
|
+
assert len(model.attackers) == 1
|
|
216
|
+
model.remove_attacker(attacker2)
|
|
217
|
+
assert len(model.attackers) == 0
|
|
218
|
+
|
|
198
219
|
def test_model_add_asset(model: Model):
|
|
199
220
|
"""Make sure assets are added correctly"""
|
|
200
221
|
|
|
@@ -815,6 +836,7 @@ def test_model_save_and_load_model_from_scratch(model: Model):
|
|
|
815
836
|
|
|
816
837
|
# Create and add 3 applications
|
|
817
838
|
p1 = create_application_asset(model, "Program 1")
|
|
839
|
+
p1.extras = {"testing": "testing"}
|
|
818
840
|
p2 = create_application_asset(model, "Program 2")
|
|
819
841
|
p3 = create_application_asset(model, "Program 3")
|
|
820
842
|
model.add_asset(p1)
|
|
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
|