mal-toolbox 0.3.10__py3-none-any.whl → 1.0.0__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.
maltoolbox/model.py CHANGED
@@ -27,108 +27,6 @@ if TYPE_CHECKING:
27
27
 
28
28
  logger = logging.getLogger(__name__)
29
29
 
30
- @dataclass
31
- class AttackerAttachment:
32
- """Used to attach attackers to attack step entry points of assets"""
33
- id: Optional[int] = None
34
- name: Optional[str] = None
35
- entry_points: list[tuple[ModelAsset, list[str]]] = \
36
- field(default_factory=lambda: [])
37
-
38
-
39
- def get_entry_point_tuple(
40
- self,
41
- asset: ModelAsset
42
- ) -> Optional[tuple[ModelAsset, list[str]]]:
43
- """Return an entry point tuple of an AttackerAttachment matching the
44
- asset provided.
45
-
46
-
47
- Arguments:
48
- asset - the asset to add entry point to
49
-
50
- Return:
51
- The entry point tuple containing the asset and the list of attack
52
- steps if the asset has any entry points defined for this attacker
53
- attachemnt.
54
- None, otherwise.
55
- """
56
- return next((ep_tuple for ep_tuple in self.entry_points
57
- if ep_tuple[0] == asset), None)
58
-
59
-
60
- def add_entry_point(
61
- self, asset: ModelAsset, attackstep_name: str):
62
- """Add an entry point to an AttackerAttachment
63
-
64
- self.entry_points contain tuples, first element of each tuple
65
- is an asset, second element is a list of attack step names that
66
- are entry points for the attacker.
67
-
68
- Arguments:
69
- asset - the asset to add the entry point to
70
- attackstep_name - the name of the attack step to add as an entry point
71
- """
72
-
73
- logger.debug(
74
- f'Add entry point "{attackstep_name}" on asset "{asset.name}" '
75
- f'to AttackerAttachment "{self.name}".'
76
- )
77
-
78
- # Get the entry point tuple for the asset if it already exists
79
- entry_point_tuple = self.get_entry_point_tuple(asset)
80
-
81
- if entry_point_tuple:
82
- if attackstep_name not in entry_point_tuple[1]:
83
- # If it exists and does not already have the attack step,
84
- # add it
85
- entry_point_tuple[1].append(attackstep_name)
86
- else:
87
- logger.info(
88
- f'Entry point "{attackstep_name}" on asset "{asset.name}"'
89
- f' already existed for AttackerAttachment "{self.name}".'
90
- )
91
- else:
92
- # Otherwise, create the entry point tuple and the initial entry
93
- # point
94
- self.entry_points.append((asset, [attackstep_name]))
95
-
96
-
97
- def remove_entry_point(
98
- self, asset: ModelAsset, attackstep_name: str):
99
- """Remove an entry point from an AttackerAttachment if it exists
100
-
101
- Arguments:
102
- asset - the asset to remove the entry point from
103
- """
104
-
105
- logger.debug(
106
- f'Remove entry point "{attackstep_name}" on asset "{asset.name}" '
107
- f'from AttackerAttachment "{self.name}".'
108
- )
109
-
110
- # Get the entry point tuple for the asset if it exists
111
- entry_point_tuple = self.get_entry_point_tuple(asset)
112
-
113
- if entry_point_tuple:
114
- if attackstep_name in entry_point_tuple[1]:
115
- # If it exists and not already has the attack step, add it
116
- entry_point_tuple[1].remove(attackstep_name)
117
- else:
118
- logger.warning(
119
- f'Failed to find entry point "{attackstep_name}" on '
120
- f'asset "{asset.name}" for AttackerAttachment '
121
- f'"{self.name}". Nothing to remove.'
122
- )
123
-
124
- if not entry_point_tuple[1]:
125
- self.entry_points.remove(entry_point_tuple)
126
- else:
127
- logger.warning(
128
- f'Failed to find entry points on asset "{asset.name}" '
129
- f'for AttackerAttachment "{self.name}". Nothing to remove.'
130
- )
131
-
132
30
 
133
31
  class Model():
134
32
  """An implementation of a MAL language model containing assets"""
@@ -148,7 +46,6 @@ class Model():
148
46
  self.name = name
149
47
  self.assets: dict[int, ModelAsset] = {}
150
48
  self._name_to_asset:dict[str, ModelAsset] = {} # optimization
151
- self.attackers: list[AttackerAttachment] = []
152
49
  self.lang_graph = lang_graph
153
50
  self.maltoolbox_version: str = mt_version
154
51
 
@@ -222,11 +119,6 @@ class Model():
222
119
  return asset
223
120
 
224
121
 
225
- def remove_attacker(self, attacker: AttackerAttachment) -> None:
226
- """Remove attacker"""
227
- self.attackers.remove(attacker)
228
-
229
-
230
122
  def remove_asset(self, asset: ModelAsset) -> None:
231
123
  """Remove an asset from the model.
232
124
 
@@ -251,39 +143,10 @@ class Model():
251
143
  for fieldname, assoc_assets in associated_fieldnames.items():
252
144
  asset.remove_associated_assets(fieldname, assoc_assets)
253
145
 
254
- # Also remove all of the entry points
255
- for attacker in self.attackers:
256
- entry_point_tuple = attacker.get_entry_point_tuple(asset)
257
- if entry_point_tuple:
258
- attacker.entry_points.remove(entry_point_tuple)
259
-
260
146
  del self.assets[asset.id]
261
147
  del self._name_to_asset[asset.name]
262
148
 
263
149
 
264
- def add_attacker(
265
- self,
266
- attacker: AttackerAttachment,
267
- attacker_id: Optional[int] = None
268
- ) -> None:
269
- """Add an attacker to the model.
270
-
271
- Arguments:
272
- attacker - the attacker to add
273
- attacker_id - optional id for the attacker
274
- """
275
-
276
- if attacker_id is not None:
277
- attacker.id = attacker_id
278
- else:
279
- attacker.id = self.next_id
280
- self.next_id = max(attacker.id + 1, self.next_id)
281
-
282
- if not hasattr(attacker, 'name') or not attacker.name:
283
- attacker.name = 'Attacker:' + str(attacker.id)
284
- self.attackers.append(attacker)
285
-
286
-
287
150
  def get_asset_by_id(
288
151
  self, asset_id: int
289
152
  ) -> Optional[ModelAsset]:
@@ -322,57 +185,12 @@ class Model():
322
185
  return self._name_to_asset.get(asset_name, None)
323
186
 
324
187
 
325
- def get_attacker_by_id(
326
- self, attacker_id: int
327
- ) -> Optional[AttackerAttachment]:
328
- """
329
- Find an attacker in the model based on its id.
330
-
331
- Arguments:
332
- attacker_id - the id of the attacker we are looking for
333
-
334
- Return:
335
- An attacker matching the id if it exists in the model.
336
- """
337
- logger.debug(
338
- 'Get attacker with id %d from model "%s".',
339
- attacker_id, self.name
340
- )
341
- return next(
342
- (attacker for attacker in self.attackers
343
- if attacker.id == attacker_id), None
344
- )
345
-
346
-
347
- def attacker_to_dict(
348
- self, attacker: AttackerAttachment
349
- ) -> tuple[Optional[int], dict]:
350
- """Get dictionary representation of the attacker.
351
-
352
- Arguments:
353
- attacker - attacker to get dictionary representation of
354
- """
355
-
356
- logger.debug('Translating %s to dictionary.', attacker.name)
357
- attacker_dict: dict[str, Any] = {
358
- 'name': attacker.name,
359
- 'entry_points': {},
360
- }
361
- for (asset, attack_steps) in attacker.entry_points:
362
- attacker_dict['entry_points'][asset.name] = {
363
- 'asset_id': asset.id,
364
- 'attack_steps' : attack_steps
365
- }
366
- return (attacker.id, attacker_dict)
367
-
368
-
369
188
  def _to_dict(self) -> dict:
370
189
  """Get dictionary representation of the model."""
371
190
  logger.debug('Translating model to dict.')
372
191
  contents: dict[str, Any] = {
373
192
  'metadata': {},
374
193
  'assets': {},
375
- 'attackers' : {}
376
194
  }
377
195
  contents['metadata'] = {
378
196
  'name': self.name,
@@ -387,10 +205,6 @@ class Model():
387
205
  for asset in self.assets.values():
388
206
  contents['assets'].update(asset._to_dict())
389
207
 
390
- logger.debug('Translating attackers to dictionary.')
391
- for attacker in self.attackers:
392
- (attacker_id, attacker_dict) = self.attacker_to_dict(attacker)
393
- contents['attackers'][attacker_id] = attacker_dict
394
208
  return contents
395
209
 
396
210
 
@@ -459,29 +273,13 @@ class Model():
459
273
  for assoc_asset_id in assoc_assets}
460
274
  )
461
275
 
462
- # Reconstruct the attackers
276
+ # Attackers no longer part of mal-toolbox
463
277
  if 'attackers' in serialized_object:
464
- attackers_info = serialized_object['attackers']
465
- for attacker_id in attackers_info:
466
- attacker = AttackerAttachment(name = attackers_info[attacker_id]['name'])
467
- for asset_name, entry_points_dict in \
468
- attackers_info[attacker_id]['entry_points'].items():
469
- target_asset = model.get_asset_by_id(
470
- entry_points_dict['asset_id'])
471
- if target_asset is None:
472
- raise LookupError(
473
- 'Asset "%s"(%d) is not part of model "%s".' % (
474
- asset_name,
475
- entry_points_dict['asset_id'],
476
- model.name)
477
- )
478
- attacker.entry_points.append(
479
- (
480
- target_asset,
481
- entry_points_dict['attack_steps']
482
- )
483
- )
484
- model.add_attacker(attacker, attacker_id = int(attacker_id))
278
+ msg = ("Defining attackers in a model file is deprecated,"
279
+ " use mal-simulator for attacker simulations.")
280
+ print(msg)
281
+ logger.warning(msg)
282
+
485
283
  return model
486
284
 
487
285
 
maltoolbox/py.typed ADDED
File without changes
@@ -9,7 +9,7 @@ import xml.etree.ElementTree as ET
9
9
 
10
10
  from typing import Optional
11
11
 
12
- from ..model import AttackerAttachment, Model
12
+ from ..model import Model
13
13
  from ..language import LanguageGraph
14
14
 
15
15
  logger = logging.getLogger(__name__)
@@ -82,7 +82,7 @@ def convert_model_dict_from_version_0_0(model_dict: dict) -> dict:
82
82
 
83
83
  # Meta data and attackers did not change
84
84
  new_model_dict['metadata'] = model_dict['metadata']
85
- new_model_dict['attackers'] = model_dict['attackers']
85
+ new_model_dict['attackers'] = model_dict.get('attackers', {})
86
86
 
87
87
  new_model_dict['assets'] = {}
88
88
 
@@ -1,29 +0,0 @@
1
- maltoolbox/__init__.py,sha256=cf1wAw8jm8XYUjWxnAdMa6w0EbnWks374FnyBJ1ZcHE,2090
2
- maltoolbox/__main__.py,sha256=PSg8vFS8X-klJBJdSzrg0aLh9ykZgbcoSSEy3DTQoQQ,3499
3
- maltoolbox/exceptions.py,sha256=0YjPx2v1yYumZ2o7pVZ1s_jS-GAb3Ng979KEFhROSNY,1399
4
- maltoolbox/file_utils.py,sha256=tBR8Kjl8IoFzAtYaLNHNALuQrdMT3pD1ZpczHm1pu2g,1875
5
- maltoolbox/model.py,sha256=Y3FKyWyRGPzJlpP92XRrWS52xVeKaz9rgOLoQJvJ808,24008
6
- maltoolbox/attackgraph/__init__.py,sha256=AHDyX6dAkx3mDic2K56v1xche9N6ofDfbaHkKbdJ2qQ,230
7
- maltoolbox/attackgraph/attacker.py,sha256=Lq7g_uFDvThU0wah-CiYA6oTshxt1TlgPJfkojlSyRQ,3132
8
- maltoolbox/attackgraph/attackgraph.py,sha256=QEfWblZDBpvX7o17LL3LHf1gyB080fTGt7uVC8082mM,32537
9
- maltoolbox/attackgraph/node.py,sha256=Ec67_u_8qf_MgCHaUg4wIbZFC013GWxbIsC8EjoguzE,6465
10
- maltoolbox/attackgraph/query.py,sha256=iuaLAc3bMnQefgGa1g62re8-3yQrgBW_cS5W_DgWEjY,6835
11
- maltoolbox/attackgraph/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- maltoolbox/attackgraph/analyzers/apriori.py,sha256=lMGuFj9v-X5v3jHLzLI5G4onZ443T5eukzWDuEnfA8k,8984
13
- maltoolbox/ingestors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- maltoolbox/ingestors/neo4j.py,sha256=W3AH3nymRQHI9N65HsSyyeQKcETPXmY_SLKc-iB4sBI,8328
15
- maltoolbox/language/__init__.py,sha256=9p5nvVqDCKEhXbDMIz1MtwZ9GN7x1jmUUXbpjEwuqnw,269
16
- maltoolbox/language/languagegraph.py,sha256=eBPTyoDpfc01ONEj321-RmIJV3DfVenYfHdVu0TiITo,67856
17
- maltoolbox/language/compiler/__init__.py,sha256=JQyAgDwJh1pU7AmuOhd1-d2b2PYXpgMVPtxnav8QHVc,15872
18
- maltoolbox/language/compiler/mal_lexer.py,sha256=BeifykDAt4PloRASOaLzBgWF35ev_zgD8lXMIsSHykc,12063
19
- maltoolbox/language/compiler/mal_parser.py,sha256=sUoaE43l2VKg-Dou30mk2wlVS1FvdOREwHNIyFe4IkY,114699
20
- maltoolbox/translators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- maltoolbox/translators/securicad.py,sha256=PJYjieioWN5tE_oKm83dtgV5UkC8EUH9Vsy3-FxBtUo,7017
22
- maltoolbox/translators/updater.py,sha256=8bisZnzMWjGaG5tu8jdF-Oq6bPwIjXkVO-_yZDGc6cA,8652
23
- mal_toolbox-0.3.10.dist-info/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
24
- mal_toolbox-0.3.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
25
- mal_toolbox-0.3.10.dist-info/METADATA,sha256=pUsc7KK-BgKpqSRt8MwDRugVnV1M3JhPUMCRqJWcP5U,6159
26
- mal_toolbox-0.3.10.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
27
- mal_toolbox-0.3.10.dist-info/entry_points.txt,sha256=oqby5O6cUP_OHCm70k_iYPA6UlbTBf7se1i3XwdK3uU,56
28
- mal_toolbox-0.3.10.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
29
- mal_toolbox-0.3.10.dist-info/RECORD,,
@@ -1,244 +0,0 @@
1
- """
2
- MAL-Toolbox Attack Graph Apriori Analyzer Submodule
3
-
4
- This submodule contains analyzers that are relevant before attackers are even
5
- connected to the attack graph.
6
- Currently these are:
7
- - Viability = Determine if a node can be traversed under any circumstances or
8
- if the model structure makes it unviable.
9
- - Necessity = Determine if a node is necessary for the attacker or if the
10
- model structure means it is not needed(it behaves as if it were already
11
- compromised) to compromise children attack steps.
12
- """
13
-
14
- from __future__ import annotations
15
- from typing import Optional, TYPE_CHECKING
16
- import logging
17
-
18
- if TYPE_CHECKING:
19
- from ..attackgraph import AttackGraph
20
- from ..node import AttackGraphNode
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
- def propagate_viability_from_node(node: AttackGraphNode) -> None:
25
- """
26
- Arguments:
27
- node - the attack graph node from which to propagate the viable
28
- status
29
- """
30
- logger.debug(
31
- 'Propagate viability from "%s"(%d) with viability status %s.',
32
- node.full_name, node.id, node.is_viable
33
- )
34
- for child in node.children:
35
- original_value = child.is_viable
36
- if child.type == 'or':
37
- child.is_viable = False
38
- for parent in child.parents:
39
- child.is_viable = child.is_viable or parent.is_viable
40
- if child.type == 'and':
41
- child.is_viable = False
42
-
43
- if child.is_viable != original_value:
44
- propagate_viability_from_node(child)
45
-
46
-
47
- def propagate_necessity_from_node(node: AttackGraphNode) -> None:
48
- """
49
- Arguments:
50
- node - the attack graph node from which to propagate the necessary
51
- status
52
- """
53
- logger.debug(
54
- 'Propagate necessity from "%s"(%d) with necessity status %s.',
55
- node.full_name, node.id, node.is_necessary
56
- )
57
-
58
- if node.ttc and 'name' in node.ttc:
59
- if node.ttc['name'] not in ['Enabled', 'Disabled', 'Instant']:
60
- # Do not propagate unnecessary state from nodes that have a TTC
61
- # probability distribution associated with them.
62
- # TODO: Evaluate this more carefully, how do we want to have TTCs
63
- # impact necessity and viability.
64
- # TODO: Have this condition be any probability that has a
65
- # Bernoulli component
66
- return
67
-
68
- for child in node.children:
69
- original_value = child.is_necessary
70
- if child.type == 'or':
71
- child.is_necessary = False
72
- if child.type == 'and':
73
- child.is_necessary = False
74
- for parent in child.parents:
75
- child.is_necessary = child.is_necessary or parent.is_necessary
76
-
77
- # TODO: Update TTC for child attack step before if it is not necessary
78
- # before propagating it further.
79
- if child.is_necessary != original_value:
80
- propagate_necessity_from_node(child)
81
-
82
-
83
- def evaluate_viability(node: AttackGraphNode) -> None:
84
- """
85
- Arguments:
86
- graph - the node to evaluate viability for.
87
- """
88
- match (node.type):
89
- case 'exist':
90
- assert isinstance(node.existence_status, bool), \
91
- f'Existence status not defined for {node.full_name}.'
92
- node.is_viable = node.existence_status
93
- case 'notExist':
94
- assert isinstance(node.existence_status, bool), \
95
- f'Existence status not defined for {node.full_name}.'
96
- node.is_viable = not node.existence_status
97
- case 'defense':
98
- assert node.defense_status is not None and \
99
- 0.0 <= node.defense_status <= 1.0, \
100
- f'{node.full_name} defense status invalid: {node.defense_status}.'
101
- node.is_viable = node.defense_status != 1.0
102
- case 'or':
103
- node.is_viable = False
104
- for parent in node.parents:
105
- node.is_viable = node.is_viable or parent.is_viable
106
- case 'and':
107
- node.is_viable = True
108
- for parent in node.parents:
109
- node.is_viable = node.is_viable and parent.is_viable
110
- case _:
111
- msg = ('Evaluate viability was provided node "%s"(%d) which '
112
- 'is of unknown type "%s"')
113
- logger.error(msg, node.full_name, node.id, node.type)
114
- raise ValueError(msg % (node.full_name, node.id, node.type))
115
-
116
-
117
- def evaluate_necessity(node: AttackGraphNode) -> None:
118
- """
119
- Arguments:
120
- graph - the node to evaluate necessity for.
121
- """
122
- match (node.type):
123
- case 'exist':
124
- assert isinstance(node.existence_status, bool), \
125
- f'Existence status not defined for {node.full_name}.'
126
- node.is_necessary = not node.existence_status
127
- case 'notExist':
128
- assert isinstance(node.existence_status, bool), \
129
- f'Existence status not defined for {node.full_name}.'
130
- node.is_necessary = bool(node.existence_status)
131
- case 'defense':
132
- assert node.defense_status is not None and \
133
- 0.0 <= node.defense_status <= 1.0, \
134
- f'{node.full_name} defense status invalid: {node.defense_status}.'
135
- node.is_necessary = node.defense_status != 0.0
136
- case 'or':
137
- node.is_necessary = True
138
- for parent in node.parents:
139
- node.is_necessary = node.is_necessary and parent.is_necessary
140
- case 'and':
141
- node.is_necessary = False
142
- for parent in node.parents:
143
- node.is_necessary = node.is_necessary or parent.is_necessary
144
- case _:
145
- msg = ('Evaluate necessity was provided node "%s"(%d) which '
146
- 'is of unknown type "%s"')
147
- logger.error(msg, node.full_name, node.id, node.type)
148
- raise ValueError(msg % (node.full_name, node.id, node.type))
149
-
150
-
151
- def evaluate_viability_and_necessity(node: AttackGraphNode) -> None:
152
- """
153
- Arguments:
154
- graph - the node to evaluate viability and necessity for.
155
- """
156
- evaluate_viability(node)
157
- evaluate_necessity(node)
158
-
159
-
160
- def calculate_viability_and_necessity(graph: AttackGraph) -> None:
161
- """
162
- Arguments:
163
- graph - the attack graph for which we wish to determine the
164
- viability and necessity statuses for the nodes.
165
- """
166
- for node in graph.nodes.values():
167
- if node.type in ['exist', 'notExist', 'defense']:
168
- evaluate_viability_and_necessity(node)
169
- if not node.is_viable:
170
- propagate_viability_from_node(node)
171
- if not node.is_necessary:
172
- propagate_necessity_from_node(node)
173
-
174
-
175
- def prune_unviable_and_unnecessary_nodes(graph: AttackGraph) -> None:
176
- """
177
- Arguments:
178
- graph - the attack graph for which we wish to remove the
179
- the nodes which are not viable or necessary.
180
- """
181
- logger.debug(
182
- 'Prune unviable and unnecessary nodes from the attack graph.')
183
-
184
- nodes_to_remove = set()
185
- for node in graph.nodes.values():
186
- if node.type in ('or', 'and') and \
187
- (not node.is_viable or not node.is_necessary):
188
- nodes_to_remove.add(node)
189
-
190
- # Do the removal separatly so we don't remove
191
- # nodes from a set we are looping over
192
- for node in nodes_to_remove:
193
- graph.remove_node(node)
194
-
195
-
196
- def propagate_viability_from_unviable_node(
197
- unviable_node: AttackGraphNode,
198
- ) -> set[AttackGraphNode]:
199
- """
200
- Update viability of nodes affected by newly enabled defense
201
- `unviable_node` in the graph and return any attack steps
202
- that are no longer viable because of it.
203
-
204
- Propagate recursively via children as long as changes occur.
205
-
206
- Arguments:
207
- unviable_node - the node to propagate viability from
208
-
209
- Returns:
210
- attack_steps_made_unviable - set of the attack steps that have been
211
- made unviable by a defense enabled in the
212
- current step. Builds up recursively.
213
- """
214
-
215
- attack_steps_made_unviable = set()
216
-
217
- logger.debug(
218
- 'Update viability for node "%s"(%d)',
219
- unviable_node.full_name,
220
- unviable_node.id
221
- )
222
-
223
- assert not unviable_node.is_viable, (
224
- "propagate_viability_from_unviable_node should not be called"
225
- f" on viable node {unviable_node.full_name}"
226
- )
227
-
228
- if unviable_node.type in ('and', 'or'):
229
- attack_steps_made_unviable.add(unviable_node)
230
-
231
- for child in unviable_node.children:
232
- original_value = child.is_viable
233
- if child.type == 'or':
234
- child.is_viable = False
235
- for parent in child.parents:
236
- child.is_viable = child.is_viable or parent.is_viable
237
- if child.type == 'and':
238
- child.is_viable = False
239
-
240
- if child.is_viable != original_value:
241
- attack_steps_made_unviable |= \
242
- propagate_viability_from_unviable_node(child)
243
-
244
- return attack_steps_made_unviable
@@ -1,109 +0,0 @@
1
- """
2
- MAL-Toolbox Attack Graph Attacker Class
3
- """
4
-
5
- from __future__ import annotations
6
- import logging
7
-
8
- from typing import Optional
9
- from typing import TYPE_CHECKING
10
- if TYPE_CHECKING:
11
- from .attackgraph import AttackGraphNode
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
- class Attacker:
16
-
17
- def __init__(
18
- self,
19
- name: str,
20
- entry_points: Optional[set[AttackGraphNode]] = None,
21
- reached_attack_steps: Optional[set[AttackGraphNode]] = None,
22
- attacker_id: Optional[int] = None
23
- ):
24
- self.name = name
25
- self.id = attacker_id
26
- self.entry_points = entry_points or set()
27
- self.reached_attack_steps: set[AttackGraphNode] = set()
28
- for node in reached_attack_steps or {}:
29
- self.compromise(node)
30
-
31
- def to_dict(self) -> dict:
32
- attacker_dict: dict = {
33
- 'id': self.id,
34
- 'name': self.name,
35
- 'entry_points': {},
36
- 'reached_attack_steps': {}
37
- }
38
-
39
- for entry_point in self.entry_points:
40
- attacker_dict['entry_points'][entry_point.id] = \
41
- entry_point.full_name
42
- for attack_step in self.reached_attack_steps:
43
- attacker_dict['reached_attack_steps'][attack_step.id] = \
44
- attack_step.full_name
45
-
46
- return attacker_dict
47
-
48
- def __repr__(self) -> str:
49
- return f'Attacker(name: "{self.name}", id: {self.id})'
50
-
51
- def compromise(self, node: AttackGraphNode) -> None:
52
- """
53
- Have the attacker compromise the node given as a parameter.
54
-
55
- Arguments:
56
- node - the node that the attacker will compromise
57
- """
58
-
59
- logger.debug(
60
- 'Attacker "%s"(%d) is compromising node "%s"(%d).',
61
- self.name,
62
- self.id,
63
- node.full_name,
64
- node.id
65
- )
66
- if node.is_compromised_by(self):
67
- logger.info(
68
- 'Attacker "%s"(%d) already compromised node "%s"(%d). '
69
- 'Do nothing.',
70
- self.name,
71
- self.id,
72
- node.full_name,
73
- node.id
74
- )
75
- return
76
-
77
- node.compromised_by.add(self)
78
- self.reached_attack_steps.add(node)
79
-
80
- def undo_compromise(self, node: AttackGraphNode) -> None:
81
- """
82
- Remove the attacker from the list of attackers that have compromised
83
- the node given as a parameter.
84
-
85
- Arguments:
86
- node - the node that we wish to remove this attacker from.
87
- """
88
-
89
- logger.debug(
90
- 'Removing attacker "%s"(%d) from compromised_by '
91
- 'list of node "%s"(%d).',
92
- self.name,
93
- self.id,
94
- node.full_name,
95
- node.id
96
- )
97
- if not node.is_compromised_by(self):
98
- logger.info(
99
- 'Attacker "%s"(%d) had not compromised node "%s"(%d).'
100
- ' Do nothing.',
101
- self.name,
102
- self.id,
103
- node.full_name,
104
- node.id
105
- )
106
- return
107
-
108
- node.compromised_by.remove(self)
109
- self.reached_attack_steps.remove(node)