mal-toolbox 1.0.4__tar.gz → 1.0.6__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-1.0.4/mal_toolbox.egg-info → mal_toolbox-1.0.6}/PKG-INFO +56 -22
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/README.md +52 -20
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6/mal_toolbox.egg-info}/PKG-INFO +56 -22
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/SOURCES.txt +1 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/requires.txt +3 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/__init__.py +5 -2
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/__main__.py +18 -5
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/attackgraph.py +13 -5
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/node.py +20 -10
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/languagegraph.py +24 -5
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/model.py +53 -2
- mal_toolbox-1.0.6/maltoolbox/visualization/__init__.py +9 -0
- mal_toolbox-1.0.6/maltoolbox/visualization/neo4j_utils.py +117 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/pyproject.toml +8 -2
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/tests/test_model.py +7 -1
- mal_toolbox-1.0.4/maltoolbox/visualization/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/AUTHORS +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/LICENSE +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/dependency_links.txt +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/entry_points.txt +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/mal_toolbox.egg-info/top_level.txt +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/exceptions.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/file_utils.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_lexer.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/language/compiler/mal_parser.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/patternfinder/attackgraph_patterns.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/py.typed +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/__init__.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/securicad.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/translators/updater.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/maltoolbox/visualization/graphviz_utils.py +0 -0
- {mal_toolbox-1.0.4 → mal_toolbox-1.0.6}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
|
-
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>
|
|
5
|
+
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
7
7
|
Project-URL: Homepage, https://github.com/mal-lang/mal-toolbox
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/mal-lang/mal-toolbox/issues
|
|
@@ -23,6 +23,8 @@ Requires-Dist: antlr4-tools
|
|
|
23
23
|
Requires-Dist: antlr4-python3-runtime
|
|
24
24
|
Requires-Dist: docopt
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
28
|
Dynamic: license-file
|
|
27
29
|
|
|
28
30
|
# MAL Toolbox overview
|
|
@@ -30,23 +32,15 @@ Dynamic: license-file
|
|
|
30
32
|
MAL Toolbox is a collection of python modules to help developers create and work with
|
|
31
33
|
MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
|
|
32
34
|
|
|
33
|
-
Attack graphs can be used to run simulations
|
|
35
|
+
Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
|
|
34
36
|
|
|
35
|
-
[Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
37
|
+
- [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
38
|
+
- [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
|
|
36
39
|
|
|
37
40
|
## The Language Module
|
|
38
41
|
|
|
39
42
|
The language module provides various tools to process MAL languages.
|
|
40
43
|
|
|
41
|
-
### The Language Specification Submodule
|
|
42
|
-
|
|
43
|
-
The language specification submodule provides functions to load the
|
|
44
|
-
specification from a .mar archive(`load_language_specification_from_mar`) or a
|
|
45
|
-
JSON file(`load_language_specification_from_json`). This specification will
|
|
46
|
-
then be used to generate python classes representing the assets and
|
|
47
|
-
associations of the language and to determine the attack steps for each asset
|
|
48
|
-
when generating the attack graph.
|
|
49
|
-
|
|
50
44
|
## The Model Module
|
|
51
45
|
|
|
52
46
|
With a MAL language a Model (a MAL instance model) can be created either
|
|
@@ -70,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
|
|
|
70
64
|
instance to which this attack step belongs to, if this information is
|
|
71
65
|
available.
|
|
72
66
|
|
|
73
|
-
## Ingestors Module
|
|
74
|
-
|
|
75
|
-
The ingestors module contains various tools that can make use of the instance
|
|
76
|
-
model or attack graph. Currently the Neo4J ingestor is the only one available
|
|
77
|
-
and it can be used to visualise the instance model and the attack graph.
|
|
78
|
-
|
|
79
67
|
|
|
80
68
|
# Usage
|
|
81
69
|
|
|
@@ -98,6 +86,11 @@ logging:
|
|
|
98
86
|
model_file: "logs/model.yml"
|
|
99
87
|
langspec_file: "logs/langspec_file.yml"
|
|
100
88
|
langgraph_file: "logs/langspec_file.yml"
|
|
89
|
+
neo4j:
|
|
90
|
+
uri: None
|
|
91
|
+
username: None
|
|
92
|
+
password: None
|
|
93
|
+
dbname: None
|
|
101
94
|
```
|
|
102
95
|
|
|
103
96
|
Alternatively, you can use the `MALTOOLBOX_CONFIG`
|
|
@@ -124,25 +117,66 @@ You can use the maltoolbox cli to:
|
|
|
124
117
|
Command-line interface for MAL toolbox operations
|
|
125
118
|
|
|
126
119
|
Usage:
|
|
127
|
-
maltoolbox attack-graph generate [options] <model_file> <lang_file>
|
|
128
120
|
maltoolbox compile <lang_file> <output_file>
|
|
121
|
+
maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
|
|
129
122
|
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
123
|
+
maltoolbox visualize-model <model_file> <lang_file>
|
|
130
124
|
|
|
131
125
|
Arguments:
|
|
132
126
|
<model_file> Path to JSON instance model file.
|
|
133
127
|
<lang_file> Path to .mar or .mal file containing MAL spec.
|
|
134
128
|
<output_file> Path to write the result of the compilation (yml/json).
|
|
135
129
|
|
|
130
|
+
Options:
|
|
131
|
+
-h --help Show this screen.
|
|
132
|
+
-g --graphviz Visualize with graphviz
|
|
133
|
+
|
|
136
134
|
Notes:
|
|
137
135
|
- <lang_file> can be either a .mar file (generated by the older MAL
|
|
138
|
-
compiler) or a .mal file containing the DSL written in MAL
|
|
136
|
+
compiler) or a .mal file containing the DSL written in MAL.```
|
|
139
137
|
```
|
|
140
138
|
|
|
141
139
|
## Code examples / Tutorial
|
|
142
140
|
|
|
143
|
-
To find code examples and tutorials, visit the
|
|
141
|
+
To find more code examples and tutorials, visit the
|
|
144
142
|
[MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
|
|
145
143
|
|
|
144
|
+
### Load a language
|
|
145
|
+
```python
|
|
146
|
+
|
|
147
|
+
from maltoolbox.language import LanguageGraph
|
|
148
|
+
|
|
149
|
+
# Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
|
|
150
|
+
lang_graph = LanguageGraph.load_from_file(lang_file_path)
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Generate a model
|
|
155
|
+
```python
|
|
156
|
+
from maltoolbox.model import Model
|
|
157
|
+
|
|
158
|
+
# Create an empty model
|
|
159
|
+
instance_model = Model("Example Model", lang_graph)
|
|
160
|
+
|
|
161
|
+
# Create and add assets of type supported by the MAL language
|
|
162
|
+
asset1 = instance_model.add_asset('Application', 'Application1')
|
|
163
|
+
asset2 = instance_model.add_asset('Application', 'Application2')
|
|
164
|
+
|
|
165
|
+
# Create association between the assets
|
|
166
|
+
asset1.add_associated_assets('appExecutedApps', asset2)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Generate an attack graph
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
|
|
173
|
+
from maltoolbox.attackgraph import AttackGraph
|
|
174
|
+
|
|
175
|
+
attack_graph = AttackGraph(lang_graph, model)
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
|
|
146
180
|
# Tests
|
|
147
181
|
There are unit tests inside of ./tests.
|
|
148
182
|
Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
|
|
@@ -3,23 +3,15 @@
|
|
|
3
3
|
MAL Toolbox is a collection of python modules to help developers create and work with
|
|
4
4
|
MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
|
|
5
5
|
|
|
6
|
-
Attack graphs can be used to run simulations
|
|
6
|
+
Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
|
|
7
7
|
|
|
8
|
-
[Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
8
|
+
- [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
9
|
+
- [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
|
|
9
10
|
|
|
10
11
|
## The Language Module
|
|
11
12
|
|
|
12
13
|
The language module provides various tools to process MAL languages.
|
|
13
14
|
|
|
14
|
-
### The Language Specification Submodule
|
|
15
|
-
|
|
16
|
-
The language specification submodule provides functions to load the
|
|
17
|
-
specification from a .mar archive(`load_language_specification_from_mar`) or a
|
|
18
|
-
JSON file(`load_language_specification_from_json`). This specification will
|
|
19
|
-
then be used to generate python classes representing the assets and
|
|
20
|
-
associations of the language and to determine the attack steps for each asset
|
|
21
|
-
when generating the attack graph.
|
|
22
|
-
|
|
23
15
|
## The Model Module
|
|
24
16
|
|
|
25
17
|
With a MAL language a Model (a MAL instance model) can be created either
|
|
@@ -43,12 +35,6 @@ nodes related and the asset field which will contain the object in the model
|
|
|
43
35
|
instance to which this attack step belongs to, if this information is
|
|
44
36
|
available.
|
|
45
37
|
|
|
46
|
-
## Ingestors Module
|
|
47
|
-
|
|
48
|
-
The ingestors module contains various tools that can make use of the instance
|
|
49
|
-
model or attack graph. Currently the Neo4J ingestor is the only one available
|
|
50
|
-
and it can be used to visualise the instance model and the attack graph.
|
|
51
|
-
|
|
52
38
|
|
|
53
39
|
# Usage
|
|
54
40
|
|
|
@@ -71,6 +57,11 @@ logging:
|
|
|
71
57
|
model_file: "logs/model.yml"
|
|
72
58
|
langspec_file: "logs/langspec_file.yml"
|
|
73
59
|
langgraph_file: "logs/langspec_file.yml"
|
|
60
|
+
neo4j:
|
|
61
|
+
uri: None
|
|
62
|
+
username: None
|
|
63
|
+
password: None
|
|
64
|
+
dbname: None
|
|
74
65
|
```
|
|
75
66
|
|
|
76
67
|
Alternatively, you can use the `MALTOOLBOX_CONFIG`
|
|
@@ -97,25 +88,66 @@ You can use the maltoolbox cli to:
|
|
|
97
88
|
Command-line interface for MAL toolbox operations
|
|
98
89
|
|
|
99
90
|
Usage:
|
|
100
|
-
maltoolbox attack-graph generate [options] <model_file> <lang_file>
|
|
101
91
|
maltoolbox compile <lang_file> <output_file>
|
|
92
|
+
maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
|
|
102
93
|
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
94
|
+
maltoolbox visualize-model <model_file> <lang_file>
|
|
103
95
|
|
|
104
96
|
Arguments:
|
|
105
97
|
<model_file> Path to JSON instance model file.
|
|
106
98
|
<lang_file> Path to .mar or .mal file containing MAL spec.
|
|
107
99
|
<output_file> Path to write the result of the compilation (yml/json).
|
|
108
100
|
|
|
101
|
+
Options:
|
|
102
|
+
-h --help Show this screen.
|
|
103
|
+
-g --graphviz Visualize with graphviz
|
|
104
|
+
|
|
109
105
|
Notes:
|
|
110
106
|
- <lang_file> can be either a .mar file (generated by the older MAL
|
|
111
|
-
compiler) or a .mal file containing the DSL written in MAL
|
|
107
|
+
compiler) or a .mal file containing the DSL written in MAL.```
|
|
112
108
|
```
|
|
113
109
|
|
|
114
110
|
## Code examples / Tutorial
|
|
115
111
|
|
|
116
|
-
To find code examples and tutorials, visit the
|
|
112
|
+
To find more code examples and tutorials, visit the
|
|
117
113
|
[MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
|
|
118
114
|
|
|
115
|
+
### Load a language
|
|
116
|
+
```python
|
|
117
|
+
|
|
118
|
+
from maltoolbox.language import LanguageGraph
|
|
119
|
+
|
|
120
|
+
# Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
|
|
121
|
+
lang_graph = LanguageGraph.load_from_file(lang_file_path)
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Generate a model
|
|
126
|
+
```python
|
|
127
|
+
from maltoolbox.model import Model
|
|
128
|
+
|
|
129
|
+
# Create an empty model
|
|
130
|
+
instance_model = Model("Example Model", lang_graph)
|
|
131
|
+
|
|
132
|
+
# Create and add assets of type supported by the MAL language
|
|
133
|
+
asset1 = instance_model.add_asset('Application', 'Application1')
|
|
134
|
+
asset2 = instance_model.add_asset('Application', 'Application2')
|
|
135
|
+
|
|
136
|
+
# Create association between the assets
|
|
137
|
+
asset1.add_associated_assets('appExecutedApps', asset2)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Generate an attack graph
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
|
|
144
|
+
from maltoolbox.attackgraph import AttackGraph
|
|
145
|
+
|
|
146
|
+
attack_graph = AttackGraph(lang_graph, model)
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
119
151
|
# Tests
|
|
120
152
|
There are unit tests inside of ./tests.
|
|
121
153
|
Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
|
-
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>
|
|
5
|
+
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Sandor Berglund <sandor@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
7
7
|
Project-URL: Homepage, https://github.com/mal-lang/mal-toolbox
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/mal-lang/mal-toolbox/issues
|
|
@@ -23,6 +23,8 @@ Requires-Dist: antlr4-tools
|
|
|
23
23
|
Requires-Dist: antlr4-python3-runtime
|
|
24
24
|
Requires-Dist: docopt
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
28
|
Dynamic: license-file
|
|
27
29
|
|
|
28
30
|
# MAL Toolbox overview
|
|
@@ -30,23 +32,15 @@ Dynamic: license-file
|
|
|
30
32
|
MAL Toolbox is a collection of python modules to help developers create and work with
|
|
31
33
|
MAL ([Meta Attack Language](https://mal-lang.org/)) models and attack graphs.
|
|
32
34
|
|
|
33
|
-
Attack graphs can be used to run simulations
|
|
35
|
+
Attack graphs can be used to run simulations in [MAL Simulator](https://github.com/mal-lang/mal-simulator) or run your own custom analysis on.
|
|
34
36
|
|
|
35
|
-
[Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
37
|
+
- [MAL Toolbox Documentation](https://mal-lang.org/mal-toolbox/index.html)
|
|
38
|
+
- [MAL Toolbox tutorial](https://github.com/mal-lang/mal-toolbox-tutorial)
|
|
36
39
|
|
|
37
40
|
## The Language Module
|
|
38
41
|
|
|
39
42
|
The language module provides various tools to process MAL languages.
|
|
40
43
|
|
|
41
|
-
### The Language Specification Submodule
|
|
42
|
-
|
|
43
|
-
The language specification submodule provides functions to load the
|
|
44
|
-
specification from a .mar archive(`load_language_specification_from_mar`) or a
|
|
45
|
-
JSON file(`load_language_specification_from_json`). This specification will
|
|
46
|
-
then be used to generate python classes representing the assets and
|
|
47
|
-
associations of the language and to determine the attack steps for each asset
|
|
48
|
-
when generating the attack graph.
|
|
49
|
-
|
|
50
44
|
## The Model Module
|
|
51
45
|
|
|
52
46
|
With a MAL language a Model (a MAL instance model) can be created either
|
|
@@ -70,12 +64,6 @@ nodes related and the asset field which will contain the object in the model
|
|
|
70
64
|
instance to which this attack step belongs to, if this information is
|
|
71
65
|
available.
|
|
72
66
|
|
|
73
|
-
## Ingestors Module
|
|
74
|
-
|
|
75
|
-
The ingestors module contains various tools that can make use of the instance
|
|
76
|
-
model or attack graph. Currently the Neo4J ingestor is the only one available
|
|
77
|
-
and it can be used to visualise the instance model and the attack graph.
|
|
78
|
-
|
|
79
67
|
|
|
80
68
|
# Usage
|
|
81
69
|
|
|
@@ -98,6 +86,11 @@ logging:
|
|
|
98
86
|
model_file: "logs/model.yml"
|
|
99
87
|
langspec_file: "logs/langspec_file.yml"
|
|
100
88
|
langgraph_file: "logs/langspec_file.yml"
|
|
89
|
+
neo4j:
|
|
90
|
+
uri: None
|
|
91
|
+
username: None
|
|
92
|
+
password: None
|
|
93
|
+
dbname: None
|
|
101
94
|
```
|
|
102
95
|
|
|
103
96
|
Alternatively, you can use the `MALTOOLBOX_CONFIG`
|
|
@@ -124,25 +117,66 @@ You can use the maltoolbox cli to:
|
|
|
124
117
|
Command-line interface for MAL toolbox operations
|
|
125
118
|
|
|
126
119
|
Usage:
|
|
127
|
-
maltoolbox attack-graph generate [options] <model_file> <lang_file>
|
|
128
120
|
maltoolbox compile <lang_file> <output_file>
|
|
121
|
+
maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
|
|
129
122
|
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
123
|
+
maltoolbox visualize-model <model_file> <lang_file>
|
|
130
124
|
|
|
131
125
|
Arguments:
|
|
132
126
|
<model_file> Path to JSON instance model file.
|
|
133
127
|
<lang_file> Path to .mar or .mal file containing MAL spec.
|
|
134
128
|
<output_file> Path to write the result of the compilation (yml/json).
|
|
135
129
|
|
|
130
|
+
Options:
|
|
131
|
+
-h --help Show this screen.
|
|
132
|
+
-g --graphviz Visualize with graphviz
|
|
133
|
+
|
|
136
134
|
Notes:
|
|
137
135
|
- <lang_file> can be either a .mar file (generated by the older MAL
|
|
138
|
-
compiler) or a .mal file containing the DSL written in MAL
|
|
136
|
+
compiler) or a .mal file containing the DSL written in MAL.```
|
|
139
137
|
```
|
|
140
138
|
|
|
141
139
|
## Code examples / Tutorial
|
|
142
140
|
|
|
143
|
-
To find code examples and tutorials, visit the
|
|
141
|
+
To find more code examples and tutorials, visit the
|
|
144
142
|
[MAL Toolbox Tutorial](https://github.com/mal-lang/mal-toolbox-tutorial/tree/main) repository.
|
|
145
143
|
|
|
144
|
+
### Load a language
|
|
145
|
+
```python
|
|
146
|
+
|
|
147
|
+
from maltoolbox.language import LanguageGraph
|
|
148
|
+
|
|
149
|
+
# Will load the MAL language (.mal/.mar) or a saved language graph (yml/json)
|
|
150
|
+
lang_graph = LanguageGraph.load_from_file(lang_file_path)
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Generate a model
|
|
155
|
+
```python
|
|
156
|
+
from maltoolbox.model import Model
|
|
157
|
+
|
|
158
|
+
# Create an empty model
|
|
159
|
+
instance_model = Model("Example Model", lang_graph)
|
|
160
|
+
|
|
161
|
+
# Create and add assets of type supported by the MAL language
|
|
162
|
+
asset1 = instance_model.add_asset('Application', 'Application1')
|
|
163
|
+
asset2 = instance_model.add_asset('Application', 'Application2')
|
|
164
|
+
|
|
165
|
+
# Create association between the assets
|
|
166
|
+
asset1.add_associated_assets('appExecutedApps', asset2)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Generate an attack graph
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
|
|
173
|
+
from maltoolbox.attackgraph import AttackGraph
|
|
174
|
+
|
|
175
|
+
attack_graph = AttackGraph(lang_graph, model)
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
|
|
146
180
|
# Tests
|
|
147
181
|
There are unit tests inside of ./tests.
|
|
148
182
|
Before running the tests, make sure to install the requirements in ./tests/requirements.txt with `python -m pip install -r ./tests/requirements.txt`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# MAL Toolbox v1.0.
|
|
2
|
+
# MAL Toolbox v1.0.6
|
|
3
3
|
# Copyright 2025, 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__ = "1.0.
|
|
24
|
+
__version__ = "1.0.6"
|
|
25
25
|
__authors__ = [
|
|
26
26
|
"Andrei Buhaiu",
|
|
27
27
|
"Giuseppe Nebbione",
|
|
@@ -48,6 +48,7 @@ config: dict[str, Any] = {
|
|
|
48
48
|
"langspec_file": "logs/langspec_file.json",
|
|
49
49
|
"langgraph_file": "logs/langgraph.yml",
|
|
50
50
|
},
|
|
51
|
+
"neo4j": {"uri": None, "username": None, "password": None, "dbname": None},
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
config_file = os.getenv("MALTOOLBOX_CONFIG", "maltoolbox.yml")
|
|
@@ -59,6 +60,8 @@ if os.path.exists(config_file):
|
|
|
59
60
|
log_configs = config['logging']
|
|
60
61
|
os.makedirs(os.path.dirname(log_configs["log_file"]), exist_ok=True)
|
|
61
62
|
|
|
63
|
+
neo4j_configs = config['logging']
|
|
64
|
+
|
|
62
65
|
formatter = logging.Formatter(
|
|
63
66
|
"%(asctime)s %(name)-12s %(levelname)-8s %(message)s", datefmt="%m-%d %H:%M"
|
|
64
67
|
)
|
|
@@ -3,9 +3,9 @@ Command-line interface for MAL toolbox operations
|
|
|
3
3
|
|
|
4
4
|
Usage:
|
|
5
5
|
maltoolbox compile <lang_file> <output_file>
|
|
6
|
-
maltoolbox generate-attack-graph [--graphviz] <model_file> <lang_file>
|
|
6
|
+
maltoolbox generate-attack-graph [--graphviz] [--neo4j] <model_file> <lang_file>
|
|
7
7
|
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
8
|
-
maltoolbox visualize-model <model_file> <lang_file>
|
|
8
|
+
maltoolbox visualize-model [--neo4j] [--graphviz] <model_file> <lang_file>
|
|
9
9
|
|
|
10
10
|
Arguments:
|
|
11
11
|
<model_file> Path to JSON instance model file.
|
|
@@ -15,6 +15,7 @@ Arguments:
|
|
|
15
15
|
Options:
|
|
16
16
|
-h --help Show this screen.
|
|
17
17
|
-g --graphviz Visualize with graphviz
|
|
18
|
+
-n --neo4j Send to neo4j
|
|
18
19
|
|
|
19
20
|
Notes:
|
|
20
21
|
- <lang_file> can be either a .mar file (generated by the older MAL
|
|
@@ -25,12 +26,17 @@ import logging
|
|
|
25
26
|
import json
|
|
26
27
|
import docopt
|
|
27
28
|
|
|
28
|
-
from . import log_configs
|
|
29
|
+
from . import log_configs, neo4j_configs
|
|
29
30
|
from .attackgraph import create_attack_graph, AttackGraph
|
|
30
31
|
from .language.compiler import MalCompiler
|
|
31
32
|
from .language.languagegraph import LanguageGraph
|
|
32
33
|
from .translators.updater import load_model_from_older_version
|
|
33
|
-
from .visualization
|
|
34
|
+
from .visualization import (
|
|
35
|
+
render_model,
|
|
36
|
+
render_attack_graph,
|
|
37
|
+
ingest_model_neo4j,
|
|
38
|
+
ingest_attack_graph_neo4j
|
|
39
|
+
)
|
|
34
40
|
from .model import Model
|
|
35
41
|
|
|
36
42
|
logger = logging.getLogger(__name__)
|
|
@@ -78,6 +84,8 @@ def main():
|
|
|
78
84
|
)
|
|
79
85
|
if args['--graphviz']:
|
|
80
86
|
render_attack_graph(attack_graph)
|
|
87
|
+
if args['--neo4j']:
|
|
88
|
+
ingest_attack_graph_neo4j(attack_graph, neo4j_configs)
|
|
81
89
|
|
|
82
90
|
elif args['compile']:
|
|
83
91
|
compile(
|
|
@@ -90,7 +98,12 @@ def main():
|
|
|
90
98
|
elif args['visualize-model']:
|
|
91
99
|
lang_graph = LanguageGraph.load_from_file(args['<lang_file>'])
|
|
92
100
|
model = Model.load_from_file(args['<model_file>'], lang_graph)
|
|
93
|
-
|
|
101
|
+
if args['--graphviz']:
|
|
102
|
+
render_model(model)
|
|
103
|
+
else:
|
|
104
|
+
print("Use flag --graphviz to generate a pdf")
|
|
105
|
+
if args['--neo4j']:
|
|
106
|
+
ingest_model_neo4j(model, neo4j_configs)
|
|
94
107
|
|
|
95
108
|
if __name__ == "__main__":
|
|
96
109
|
main()
|
|
@@ -167,10 +167,10 @@ class AttackGraph():
|
|
|
167
167
|
|
|
168
168
|
attack_graph = AttackGraph(lang_graph)
|
|
169
169
|
attack_graph.model = model
|
|
170
|
-
serialized_attack_steps = serialized_object['attack_steps']
|
|
170
|
+
serialized_attack_steps: dict[str, dict] = serialized_object['attack_steps']
|
|
171
171
|
|
|
172
172
|
# Create all of the nodes in the imported attack graph.
|
|
173
|
-
for node_dict in serialized_attack_steps.
|
|
173
|
+
for node_full_name, node_dict in serialized_attack_steps.items():
|
|
174
174
|
|
|
175
175
|
# Recreate asset links if model is available.
|
|
176
176
|
node_asset = None
|
|
@@ -192,7 +192,13 @@ class AttackGraph():
|
|
|
192
192
|
node_id = node_dict['id'],
|
|
193
193
|
model_asset = node_asset,
|
|
194
194
|
ttc_dist = node_dict['ttc'],
|
|
195
|
-
existence_status =
|
|
195
|
+
existence_status = (
|
|
196
|
+
bool(node_dict['existence_status'])
|
|
197
|
+
if 'existence_status' in node_dict else None
|
|
198
|
+
),
|
|
199
|
+
# Give explicit full name if model is missing, otherwise
|
|
200
|
+
# it will generate automatically in node.full_name
|
|
201
|
+
full_name=node_full_name if not model else None
|
|
196
202
|
)
|
|
197
203
|
ag_node.tags = list(node_dict.get('tags', []))
|
|
198
204
|
ag_node.extras = node_dict.get('extras', {})
|
|
@@ -608,7 +614,8 @@ class AttackGraph():
|
|
|
608
614
|
node_id: Optional[int] = None,
|
|
609
615
|
model_asset: Optional[ModelAsset] = None,
|
|
610
616
|
ttc_dist: Optional[dict] = None,
|
|
611
|
-
existence_status: Optional[bool] = None
|
|
617
|
+
existence_status: Optional[bool] = None,
|
|
618
|
+
full_name: Optional[str] = None
|
|
612
619
|
) -> AttackGraphNode:
|
|
613
620
|
"""Create and add a node to the graph
|
|
614
621
|
Arguments:
|
|
@@ -653,7 +660,8 @@ class AttackGraph():
|
|
|
653
660
|
lg_attack_step = lg_attack_step,
|
|
654
661
|
model_asset = model_asset,
|
|
655
662
|
ttc_dist = ttc_dist,
|
|
656
|
-
existence_status = existence_status
|
|
663
|
+
existence_status = existence_status,
|
|
664
|
+
full_name = full_name
|
|
657
665
|
)
|
|
658
666
|
|
|
659
667
|
self.nodes[node_id] = node
|
|
@@ -21,7 +21,8 @@ class AttackGraphNode:
|
|
|
21
21
|
lg_attack_step: LanguageGraphAttackStep,
|
|
22
22
|
model_asset: Optional[ModelAsset] = None,
|
|
23
23
|
ttc_dist: Optional[dict] = None,
|
|
24
|
-
existence_status: Optional[bool] = None
|
|
24
|
+
existence_status: Optional[bool] = None,
|
|
25
|
+
full_name: Optional[str] = None
|
|
25
26
|
):
|
|
26
27
|
self.lg_attack_step = lg_attack_step
|
|
27
28
|
self.name = lg_attack_step.name
|
|
@@ -30,6 +31,7 @@ class AttackGraphNode:
|
|
|
30
31
|
self.tags = lg_attack_step.tags
|
|
31
32
|
self.detectors = lg_attack_step.detectors
|
|
32
33
|
|
|
34
|
+
self._full_name = full_name
|
|
33
35
|
self.id = node_id
|
|
34
36
|
self.model_asset = model_asset
|
|
35
37
|
self.existence_status = existence_status
|
|
@@ -45,10 +47,12 @@ class AttackGraphNode:
|
|
|
45
47
|
'lang_graph_attack_step': self.lg_attack_step.full_name,
|
|
46
48
|
'name': self.name,
|
|
47
49
|
'ttc': self.ttc,
|
|
48
|
-
'children': {
|
|
49
|
-
self.children
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
'children': {
|
|
51
|
+
child.id: child.full_name for child in self.children
|
|
52
|
+
},
|
|
53
|
+
'parents': {
|
|
54
|
+
parent.id: parent.full_name for parent in self.parents
|
|
55
|
+
},
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
for detector in self.detectors.values():
|
|
@@ -56,7 +60,7 @@ class AttackGraphNode:
|
|
|
56
60
|
if self.model_asset is not None:
|
|
57
61
|
node_dict['asset'] = str(self.model_asset.name)
|
|
58
62
|
if self.existence_status is not None:
|
|
59
|
-
node_dict['existence_status'] =
|
|
63
|
+
node_dict['existence_status'] = self.existence_status
|
|
60
64
|
if self.tags:
|
|
61
65
|
node_dict['tags'] = list(self.tags)
|
|
62
66
|
if self.extras:
|
|
@@ -105,13 +109,19 @@ class AttackGraphNode:
|
|
|
105
109
|
@property
|
|
106
110
|
def full_name(self) -> str:
|
|
107
111
|
"""
|
|
108
|
-
Return the full name of the attack step. This is a
|
|
109
|
-
asset name to which the attack step
|
|
110
|
-
itself
|
|
112
|
+
Return the full name of the attack step. This is normally a
|
|
113
|
+
combination of the asset name to which the attack step
|
|
114
|
+
belongs and attack step name itself, but can also be
|
|
115
|
+
explicitly set or a combination of the step id and step name.
|
|
111
116
|
"""
|
|
112
|
-
if self.
|
|
117
|
+
if self._full_name:
|
|
118
|
+
# Explicitly set
|
|
119
|
+
return self._full_name
|
|
120
|
+
elif self.model_asset:
|
|
121
|
+
# Inherited from model asset
|
|
113
122
|
full_name = self.model_asset.name + ':' + self.name
|
|
114
123
|
else:
|
|
124
|
+
# Fallback: use ID
|
|
115
125
|
full_name = str(self.id) + ':' + self.name
|
|
116
126
|
return full_name
|
|
117
127
|
|
|
@@ -10,7 +10,7 @@ import zipfile
|
|
|
10
10
|
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
12
|
from functools import cached_property
|
|
13
|
-
from typing import Any, Optional
|
|
13
|
+
from typing import Any, Literal, Optional
|
|
14
14
|
|
|
15
15
|
from maltoolbox.file_utils import (
|
|
16
16
|
load_dict_from_yaml_file, load_dict_from_json_file,
|
|
@@ -171,6 +171,18 @@ class LanguageGraphAsset:
|
|
|
171
171
|
current_asset = current_asset.own_super_asset
|
|
172
172
|
return superassets
|
|
173
173
|
|
|
174
|
+
def associations_to(
|
|
175
|
+
self, asset_type: LanguageGraphAsset
|
|
176
|
+
) -> dict[str, LanguageGraphAssociation]:
|
|
177
|
+
"""
|
|
178
|
+
Return dict of association types that go from self
|
|
179
|
+
to given `asset_type`
|
|
180
|
+
"""
|
|
181
|
+
associations_to_asset_type = {}
|
|
182
|
+
for fieldname, association in self.associations.items():
|
|
183
|
+
if association in asset_type.associations.values():
|
|
184
|
+
associations_to_asset_type[fieldname] = association
|
|
185
|
+
return associations_to_asset_type
|
|
174
186
|
|
|
175
187
|
@cached_property
|
|
176
188
|
def associations(self) -> dict[str, LanguageGraphAssociation]:
|
|
@@ -222,7 +234,7 @@ class LanguageGraphAsset:
|
|
|
222
234
|
return self_superassets.intersection(other_superassets)
|
|
223
235
|
|
|
224
236
|
|
|
225
|
-
@dataclass
|
|
237
|
+
@dataclass(frozen=True)
|
|
226
238
|
class LanguageGraphAssociationField:
|
|
227
239
|
"""A field in an association"""
|
|
228
240
|
asset: LanguageGraphAsset
|
|
@@ -231,7 +243,7 @@ class LanguageGraphAssociationField:
|
|
|
231
243
|
maximum: int
|
|
232
244
|
|
|
233
245
|
|
|
234
|
-
@dataclass
|
|
246
|
+
@dataclass(frozen=True, eq=True)
|
|
235
247
|
class LanguageGraphAssociation:
|
|
236
248
|
"""
|
|
237
249
|
An association type between asset types as defined in the MAL language
|
|
@@ -239,7 +251,7 @@ class LanguageGraphAssociation:
|
|
|
239
251
|
name: str
|
|
240
252
|
left_field: LanguageGraphAssociationField
|
|
241
253
|
right_field: LanguageGraphAssociationField
|
|
242
|
-
info: dict = field(default_factory = dict)
|
|
254
|
+
info: dict = field(default_factory = dict, compare=False)
|
|
243
255
|
|
|
244
256
|
def to_dict(self) -> dict:
|
|
245
257
|
"""Convert LanguageGraphAssociation to dictionary"""
|
|
@@ -356,7 +368,7 @@ class LanguageGraphAttackStep:
|
|
|
356
368
|
An attack step belonging to an asset type in the MAL language
|
|
357
369
|
"""
|
|
358
370
|
name: str
|
|
359
|
-
type:
|
|
371
|
+
type: Literal["or", "and", "defense", "exist", "notExist"]
|
|
360
372
|
asset: LanguageGraphAsset
|
|
361
373
|
ttc: Optional[dict] = field(default_factory = dict)
|
|
362
374
|
overrides: bool = False
|
|
@@ -739,6 +751,13 @@ class LanguageGraph():
|
|
|
739
751
|
|
|
740
752
|
return serialized_graph
|
|
741
753
|
|
|
754
|
+
@property
|
|
755
|
+
def associations(self) -> set[LanguageGraphAssociation]:
|
|
756
|
+
"""
|
|
757
|
+
Return all associations in the language graph.
|
|
758
|
+
"""
|
|
759
|
+
return {assoc for asset in self.assets.values() for assoc in asset.associations.values()}
|
|
760
|
+
|
|
742
761
|
@staticmethod
|
|
743
762
|
def _link_association_to_assets(
|
|
744
763
|
assoc: LanguageGraphAssociation,
|
|
@@ -3,7 +3,6 @@ MAL-Toolbox Model Module
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
|
-
from dataclasses import dataclass, field
|
|
7
6
|
import json
|
|
8
7
|
import logging
|
|
9
8
|
from typing import TYPE_CHECKING
|
|
@@ -23,6 +22,7 @@ if TYPE_CHECKING:
|
|
|
23
22
|
from .language import (
|
|
24
23
|
LanguageGraph,
|
|
25
24
|
LanguageGraphAsset,
|
|
25
|
+
LanguageGraphAssociation
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
@@ -101,6 +101,12 @@ class Model():
|
|
|
101
101
|
' and we do not allow duplicates.'
|
|
102
102
|
)
|
|
103
103
|
|
|
104
|
+
if asset_type not in self.lang_graph.assets:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f'Asset type "{asset_type}" does not exist in language, '
|
|
107
|
+
'must be one of:\n -' +
|
|
108
|
+
'\n -'.join(self.lang_graph.assets.keys())
|
|
109
|
+
)
|
|
104
110
|
lg_asset = self.lang_graph.assets[asset_type]
|
|
105
111
|
|
|
106
112
|
asset = ModelAsset(
|
|
@@ -184,8 +190,11 @@ class Model():
|
|
|
184
190
|
)
|
|
185
191
|
return self._name_to_asset.get(asset_name, None)
|
|
186
192
|
|
|
187
|
-
|
|
188
193
|
def _to_dict(self) -> dict:
|
|
194
|
+
"""Backwards compatible"""
|
|
195
|
+
return self.to_dict()
|
|
196
|
+
|
|
197
|
+
def to_dict(self) -> dict:
|
|
189
198
|
"""Get dictionary representation of the model."""
|
|
190
199
|
logger.debug('Translating model to dict.')
|
|
191
200
|
contents: dict[str, Any] = {
|
|
@@ -361,6 +370,34 @@ class ModelAsset:
|
|
|
361
370
|
return (f'ModelAsset(name: "{self.name}", id: {self.id}, '
|
|
362
371
|
f'type: {self.type})')
|
|
363
372
|
|
|
373
|
+
def associations_with(
|
|
374
|
+
self, b: ModelAsset
|
|
375
|
+
) -> set[LanguageGraphAssociation]:
|
|
376
|
+
"""Returns all associations from self to `b`"""
|
|
377
|
+
assocs_in_common = set()
|
|
378
|
+
for assoc in self.lg_asset.associations.values():
|
|
379
|
+
assets_to_left = self.associated_assets.get(
|
|
380
|
+
assoc.left_field.fieldname, set()
|
|
381
|
+
)
|
|
382
|
+
assets_to_right = self.associated_assets.get(
|
|
383
|
+
assoc.right_field.fieldname, set()
|
|
384
|
+
)
|
|
385
|
+
if b in assets_to_left or b in assets_to_right:
|
|
386
|
+
assocs_in_common.add(assoc)
|
|
387
|
+
|
|
388
|
+
return assocs_in_common
|
|
389
|
+
|
|
390
|
+
def has_association_with(self, b: ModelAsset, assoc_name: str) -> bool:
|
|
391
|
+
"""
|
|
392
|
+
Returns True if association `assoc_name` exists between self and `b`
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
for fieldname, associated_assets in self.associated_assets.items():
|
|
396
|
+
assoc = self.lg_asset.associations[fieldname]
|
|
397
|
+
if assoc.name == assoc_name and b in associated_assets:
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
return False
|
|
364
401
|
|
|
365
402
|
def validate_associated_assets(
|
|
366
403
|
self, fieldname: str, assets_to_add: set[ModelAsset]
|
|
@@ -418,6 +455,20 @@ class ModelAsset:
|
|
|
418
455
|
assets dictionary entry corresponding to the given fieldname.
|
|
419
456
|
"""
|
|
420
457
|
|
|
458
|
+
if fieldname not in self.lg_asset.associations:
|
|
459
|
+
if assets:
|
|
460
|
+
to_asset_type = next(iter(assets)).lg_asset
|
|
461
|
+
possible_associations = self.lg_asset.associations_to(to_asset_type)
|
|
462
|
+
else:
|
|
463
|
+
to_asset_type = None
|
|
464
|
+
possible_associations = self.lg_asset.associations
|
|
465
|
+
raise ValueError(
|
|
466
|
+
f'Association fieldname "{fieldname}" does not exist from '
|
|
467
|
+
f'<{self.lg_asset.name}> to <{to_asset_type.name if to_asset_type else "Any"}>'
|
|
468
|
+
', must be one of:\n -' +
|
|
469
|
+
'\n -'.join([a for a in possible_associations])
|
|
470
|
+
)
|
|
471
|
+
|
|
421
472
|
lg_assoc = self.lg_asset.associations[fieldname]
|
|
422
473
|
other_fieldname = lg_assoc.get_opposite_fieldname(fieldname)
|
|
423
474
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MAL-Toolbox Neo4j Ingestor Module
|
|
3
|
+
"""
|
|
4
|
+
# mypy: ignore-errors
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
from py2neo import Graph, Node, Relationship, Subgraph
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def ingest_attack_graph_neo4j(
|
|
14
|
+
graph,
|
|
15
|
+
neo4j_config: dict[str, Any],
|
|
16
|
+
delete: bool = True
|
|
17
|
+
) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Ingest an attack graph into a neo4j database
|
|
20
|
+
|
|
21
|
+
Arguments:
|
|
22
|
+
graph - the attackgraph provided by the atkgraph.py module.
|
|
23
|
+
uri - the URI to a running neo4j instance
|
|
24
|
+
username - the username to login on Neo4J
|
|
25
|
+
password - the password to login on Neo4J
|
|
26
|
+
dbname - the selected database
|
|
27
|
+
delete - if True, the previous content of the database is deleted
|
|
28
|
+
before ingesting the new attack graph
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
uri = neo4j_config.get('uri')
|
|
32
|
+
username = neo4j_config.get('username')
|
|
33
|
+
password = neo4j_config.get('password')
|
|
34
|
+
dbname = neo4j_config.get('dbname')
|
|
35
|
+
|
|
36
|
+
g = Graph(uri=uri, user=username, password=password, name=dbname)
|
|
37
|
+
if delete:
|
|
38
|
+
g.delete_all()
|
|
39
|
+
|
|
40
|
+
nodes = {}
|
|
41
|
+
rels = []
|
|
42
|
+
for node in graph.nodes.values():
|
|
43
|
+
node_dict = node.to_dict()
|
|
44
|
+
nodes[node.id] = Node(
|
|
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']),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
for node in graph.nodes.values():
|
|
54
|
+
for child in node.children:
|
|
55
|
+
rels.append(Relationship(nodes[node.id], nodes[child.id]))
|
|
56
|
+
|
|
57
|
+
subgraph = Subgraph(list(nodes.values()), rels)
|
|
58
|
+
|
|
59
|
+
tx = g.begin()
|
|
60
|
+
tx.create(subgraph)
|
|
61
|
+
g.commit(tx)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def ingest_model_neo4j(
|
|
65
|
+
model,
|
|
66
|
+
neo4j_config: dict[str, Any],
|
|
67
|
+
delete: bool = True
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Ingest an instance model graph into a Neo4J database
|
|
71
|
+
|
|
72
|
+
Arguments:
|
|
73
|
+
model - the instance model dictionary as provided by the model.py module
|
|
74
|
+
uri - the URI to a running neo4j instance
|
|
75
|
+
username - the username to login on Neo4J
|
|
76
|
+
password - the password to login on Neo4J
|
|
77
|
+
dbname - the selected database
|
|
78
|
+
delete - if True, the previous content of the database is deleted
|
|
79
|
+
before ingesting the new attack graph
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
uri = neo4j_config.get('uri')
|
|
83
|
+
username = neo4j_config.get('username')
|
|
84
|
+
password = neo4j_config.get('password')
|
|
85
|
+
dbname = neo4j_config.get('dbname')
|
|
86
|
+
|
|
87
|
+
g = Graph(uri=uri, user=username, password=password, name=dbname)
|
|
88
|
+
if delete:
|
|
89
|
+
g.delete_all()
|
|
90
|
+
|
|
91
|
+
nodes = {}
|
|
92
|
+
rels = []
|
|
93
|
+
|
|
94
|
+
for asset in model.assets.values():
|
|
95
|
+
nodes[str(asset.id)] = Node(
|
|
96
|
+
str(asset.type),
|
|
97
|
+
name=str(asset.name),
|
|
98
|
+
asset_id=str(asset.id),
|
|
99
|
+
type=str(asset.type)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
for asset in model.assets.values():
|
|
103
|
+
for fieldname, other_assets in asset.associated_assets.items():
|
|
104
|
+
for other_asset in other_assets:
|
|
105
|
+
rels.append(
|
|
106
|
+
Relationship(
|
|
107
|
+
nodes[str(asset.id)],
|
|
108
|
+
str(fieldname),
|
|
109
|
+
nodes[str(other_asset.id)]
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
subgraph = Subgraph(list(nodes.values()), rels)
|
|
114
|
+
|
|
115
|
+
tx = g.begin()
|
|
116
|
+
tx.create(subgraph)
|
|
117
|
+
g.commit(tx)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mal-toolbox"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.6"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Andrei Buhaiu", email="buhaiu@kth.se" },
|
|
6
6
|
{ name="Joakim Loxdal", email="loxdal@kth.se" },
|
|
7
7
|
{ name="Nikolaos Kakouros", email="nkak@kth.se" },
|
|
8
8
|
{ name="Jakob Nyberg", email="jaknyb@kth.se" },
|
|
9
|
-
{ name="Giuseppe Nebbione", email="nebbione@kth.se" }
|
|
9
|
+
{ name="Giuseppe Nebbione", email="nebbione@kth.se" },
|
|
10
|
+
{ name="Sandor Berglund", email="sandor@kth.se" }
|
|
10
11
|
]
|
|
11
12
|
description = "A collection of tools used to create MAL models and attack graphs."
|
|
12
13
|
readme = "README.md"
|
|
@@ -34,6 +35,11 @@ classifiers = [
|
|
|
34
35
|
"Bug Tracker" = "https://github.com/mal-lang/mal-toolbox/issues"
|
|
35
36
|
"Repository" = "https://github.com/mal-lang/mal-toolbox"
|
|
36
37
|
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest",
|
|
41
|
+
]
|
|
42
|
+
|
|
37
43
|
[project.scripts]
|
|
38
44
|
"maltoolbox" = "maltoolbox.__main__:main"
|
|
39
45
|
|
|
@@ -125,6 +125,12 @@ def test_model_add_associated_asset(model: Model):
|
|
|
125
125
|
assert 'hostApp' in asset2.associated_assets
|
|
126
126
|
assert asset1 in asset2.associated_assets['hostApp']
|
|
127
127
|
|
|
128
|
+
associations_in_common = asset1.associations_with(asset2)
|
|
129
|
+
assert associations_in_common
|
|
130
|
+
|
|
131
|
+
for assoc in associations_in_common:
|
|
132
|
+
assert asset1.has_association_with(asset2, assoc.name)
|
|
133
|
+
|
|
128
134
|
|
|
129
135
|
def test_model_add_appexecution_association_two_assets(model: Model):
|
|
130
136
|
"""coreLang specifies that AppExecution only can have one 'left' asset"""
|
|
@@ -161,7 +167,7 @@ def test_model_add_association_nonexisting_fieldname(model: Model):
|
|
|
161
167
|
data = model.add_asset(asset_type = 'Data')
|
|
162
168
|
|
|
163
169
|
# Try create an association between asset1 and data
|
|
164
|
-
with pytest.raises(
|
|
170
|
+
with pytest.raises(ValueError):
|
|
165
171
|
# will raise error because fieldname does not exist
|
|
166
172
|
asset1.add_associated_assets(
|
|
167
173
|
fieldname = 'unknownFieldName', assets = {data}
|
|
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
|