csle-attack-profiler 0.5.2__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.

Potentially problematic release.


This version of csle-attack-profiler might be problematic. Click here for more details.

@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.1
2
+ Name: csle_attack_profiler
3
+ Version: 0.5.2
4
+ Summary: Library with MITRE attack profiler for CSLE
5
+ Author: Bength Pappila
6
+ Author-email: brpa@kth.se
7
+ License: Creative Commons Attribution-ShareAlike 4.0 International
8
+ Keywords: Reinforcement-Learning Cyber-Security Markov-Games Markov-Decision-Processes
9
+ Platform: unix
10
+ Platform: linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Intended Audience :: Science/Research
16
+ Requires-Python: >=3.8
17
+ Provides-Extra: testing
@@ -0,0 +1,177 @@
1
+ # `csle-attack-profiler`
2
+
3
+ Scripts and programs to profile attacks, attack sequences, and a probabilistic HMM profiler
4
+ using data from the csle platform, profiling attacks to MITRE ATT&CK techniques, and tactics.
5
+
6
+ [![PyPI version]] 0.5.1
7
+ ![PyPI - Downloads] (https://pypi.org/project/csle-attack-profiler/)
8
+
9
+ ## Requirements
10
+
11
+ - Python 3.8+
12
+ - `csle-common`
13
+ - `csle-base`
14
+ - `mitreattack-python`
15
+
16
+
17
+ ## Development Requirement`
18
+
19
+ - Python 3.8+
20
+ - `flake8` (for linting)
21
+ - `flake8-rst-docstrings` (for linting docstrings)
22
+ - `tox` (for automated testing)
23
+ - `pytest` (for unit tests)
24
+ - `pytest-cov` (for unit test coverage)
25
+ - `mypy` (for static typing)
26
+ - `mypy-extensions` (for static typing)
27
+ - `mypy-protobuf` (for static typing)
28
+ - `types-PyYaml` (for static typing)
29
+ - `types-paramiko` (for static typing)
30
+ - `types-protobuf` (for static typing)
31
+ - `types-requests` (for static typing)
32
+ - `types-urllib3` (for static typing)
33
+ - `sphinx` (for API documentation)
34
+ - `sphinxcontrib-napoleon` (for API documentation)
35
+ - `sphinx-rtd-theme` (for API documentation)
36
+ - `pytest-mock` (for mocking tests)
37
+ - `pytest-grpc` (for grpc tests)
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ # install from pip
43
+ pip install csle-attack-profiler==<version>
44
+ # local install from source
45
+ $ pip install -e csle-attack-profiler
46
+ # or (equivalently):
47
+ make install
48
+ # force upgrade deps
49
+ $ pip install -e csle-attack-profiler --upgrade
50
+ # git clone and install from source
51
+ git clone https://github.com/Limmen/csle
52
+ cd csle/simulation-system/libs/csle-attack-profiler
53
+ pip3 install -e .
54
+ # Install development dependencies
55
+ $ pip install -r requirements_dev.txt
56
+ ```
57
+
58
+ ### Development tools
59
+
60
+ Install all development tools at once:
61
+ ```bash
62
+ make install_dev
63
+ ```
64
+ or
65
+ ```bash
66
+ pip install -r requirements_dev.txt
67
+ ```
68
+ ## API documentation
69
+
70
+ This section contains instructions for generating API documentation using `sphinx`.
71
+
72
+ ### Latest Documentation
73
+
74
+ The latest documentation is available at [https://limmen.dev/csle/docs/csle-base](https://limmen.dev/csle/docs/csle-base)
75
+
76
+ ### Generate API Documentation
77
+
78
+ First make sure that the `CSLE_HOME` environment variable is set:
79
+ ```bash
80
+ echo $CSLE_HOME
81
+ ```
82
+ Then generate the documentation with the commands:
83
+ ```bash
84
+ cd docs
85
+ sphinx-apidoc -f -o source/ ../src/csle_attack_profiler/
86
+ make html
87
+ ```
88
+ To update the official documentation at [https://limmen.dev/csle](https://limmen.dev/csle),
89
+ copy the generated HTML files to the documentation folder:
90
+ ```bash
91
+ cp -r build/html ../../../../docs/_docs/csle-attack-profiler
92
+ ```
93
+
94
+ To run all documentation commands at once, use the command:
95
+ ```bash
96
+ make docs
97
+ ```
98
+
99
+ ## Static code analysis
100
+
101
+ To run the Python linter, execute the following command:
102
+ ```
103
+ flake8 .
104
+ # or (equivalently):
105
+ make lint
106
+ ```
107
+
108
+ To run the mypy type checker, execute the following command:
109
+ ```
110
+ mypy .
111
+ # or (equivalently):
112
+ make types
113
+ ```
114
+
115
+ ## Unit tests
116
+
117
+ To run the unit tests, execute the following command:
118
+ ```
119
+ pytest
120
+ # or (equivalently):
121
+ make unit_tests
122
+ ```
123
+
124
+ To run tests of a specific test suite, execute the following command:
125
+ ```
126
+ pytest -k "ClassName"
127
+ ```
128
+
129
+ To generate a coverage report, execute the following command:
130
+ ```
131
+ pytest --cov=csle_attack_profiler
132
+ ```
133
+
134
+ ## Run tests and code analysis in different python environments
135
+
136
+ To run tests and code analysis in different python environments, execute the following command:
137
+
138
+ ```bash
139
+ tox
140
+ # or (equivalently):
141
+ make tests
142
+ ```
143
+
144
+ ## Create a new release and publish to PyPi
145
+
146
+ First build the package by executing:
147
+ ```bash
148
+ python3 -m build
149
+ # or (equivalently)
150
+ make build
151
+ ```
152
+ After running the command above, the built package is available at `./dist`.
153
+
154
+ Push the built package to PyPi by running:
155
+ ```bash
156
+ python3 -m twine upload dist/*
157
+ # or (equivalently)
158
+ make push
159
+ ```
160
+
161
+ To run all commands for the release at once, execute:
162
+ ```bash
163
+ make release
164
+ ```
165
+
166
+ ## Author & Maintainer
167
+
168
+ Bength Pappila <brpa@kth.se>
169
+
170
+ ## Copyright and license
171
+
172
+ [LICENSE](LICENSE.md)
173
+
174
+ Creative Commons
175
+
176
+ (C) 2024, Bength Pappila
177
+
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools==68.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.pytest.ini_options]
6
+ addopts = "--cov=csle_attack_profiler -p no:warnings"
7
+ testpaths = [
8
+ "tests",
9
+ ]
10
+ log_cli = 1
11
+ log_cli_level = "INFO"
12
+ log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
13
+ log_cli_date_format="%Y-%m-%d %H:%M:%S"
14
+
15
+ [tool.mypy]
16
+ mypy_path = "src"
17
+ check_untyped_defs = true
18
+ disallow_any_generics = true
19
+ ignore_missing_imports = true
20
+ no_implicit_optional = true
21
+ show_error_codes = true
22
+ strict_equality = true
23
+ warn_redundant_casts = true
24
+ warn_return_any = true
25
+ warn_unreachable = true
26
+ warn_unused_configs = true
27
+ no_implicit_reexport = true
@@ -0,0 +1,74 @@
1
+ [metadata]
2
+ name = csle_attack_profiler
3
+ version = attr: csle_attack_profiler.__version__
4
+ description = Library with MITRE attack profiler for CSLE
5
+ author = Bength Pappila
6
+ author_email = brpa@kth.se
7
+ license = Creative Commons Attribution-ShareAlike 4.0 International
8
+ keywords = Reinforcement-Learning Cyber-Security Markov-Games Markov-Decision-Processes
9
+ license_files =
10
+ - LICENSE.md
11
+ - README.md
12
+ platforms = unix, linux
13
+ classifiers =
14
+ Programming Language :: Python :: 3
15
+ Programming Language :: Python :: 3 :: Only
16
+ Programming Language :: Python :: 3.8
17
+ Programming Language :: Python :: 3.9
18
+ Intended Audience :: Science/Research
19
+
20
+ [options]
21
+ python_requires = >=3.8
22
+ package_dir =
23
+ =src
24
+ packages = find:
25
+ zip_safe = no
26
+ install_requires =
27
+ mitreattack-python==2.0.14
28
+ csle-base==0.5.2
29
+ csle-common==0.5.2
30
+
31
+ [options.packages.find]
32
+ where = src
33
+
34
+ [options.extras_require]
35
+ testing =
36
+ pytest>=6.0
37
+ pytest-cov>=2.0
38
+ pytest-mock>=3.6.0
39
+ grpcio>=1.57.0
40
+ grpcio-tools>=1.57.0
41
+ pytest-grpc>=0.8.0
42
+ mypy>=1.4.1
43
+ mypy-extensions>=1.0.0
44
+ mypy-protobuf>=3.5.0
45
+ types-PyYAML>=6.0.12.11
46
+ types-paramiko>=3.2.0.0
47
+ types-protobuf>=4.23.0.3
48
+ types-requests>=2.31.0.1
49
+ types-urllib3>=1.26.25.13
50
+ flake8>=6.1.0
51
+ flake8-rst-docstrings>=0.3.0
52
+ tox>=3.24
53
+ sphinx>=5.3.0
54
+ sphinxcontrib-napoleon>=0.7
55
+ sphinx-rtd-theme>=1.1.1
56
+ twine>=4.0.2
57
+ build>=0.10.0
58
+
59
+ [options.package_data]
60
+ csle_attack_profiler = py.typed
61
+
62
+ [flake8]
63
+ max-line-length = 120
64
+ exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,*_pb2*,*init__*,.tox
65
+ ignore = E741, W503, W504, F821, W605
66
+ rst-roles = class, func, ref
67
+ rst-directives = envvar, exception
68
+ rst-substitutions = version
69
+ extend-ignore = D401, D400, D100, RST305, RST219, D205, D202, D200, D204, RST206, W293, D403, D402, RST306
70
+
71
+ [egg_info]
72
+ tag_build =
73
+ tag_date = 0
74
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ if __name__ == '__main__':
4
+ setup()
@@ -0,0 +1 @@
1
+ from . __version__ import __version__
@@ -0,0 +1 @@
1
+ __version__ = '0.5.2'
@@ -0,0 +1,204 @@
1
+ from csle_common.dao.emulation_action.attacker.emulation_attacker_action \
2
+ import EmulationAttackerAction
3
+ from csle_common.dao.emulation_action.attacker.emulation_attacker_action_id \
4
+ import EmulationAttackerActionId
5
+ from csle_attack_profiler.dao.tactics import Tactics
6
+ from csle_attack_profiler.dao.attack_mapping import EmulationAttackerMapping
7
+ from csle_attack_profiler.dao.attack_graph import AttackGraph
8
+ from mitreattack.stix20 import MitreAttackData
9
+ from typing import List, Dict, Union
10
+ import os
11
+
12
+
13
+ class AttackProfiler:
14
+ """
15
+ Class representing the attack profile based on the MITRE ATT&CK framework for Enterprise.
16
+ """
17
+
18
+ def __init__(self, techniques_tactics: Dict[str, List[str]], mitigations: Dict[str, List[str]],
19
+ data_sources: Dict[str, List[str]], subtechniques: Dict[str, str],
20
+ action_id: EmulationAttackerActionId) -> None:
21
+ """
22
+ Class constructor
23
+
24
+ :params techniques_tactics: the techniques and tactics used by the attacker action.
25
+ The key is the technique and the value is the tactics
26
+ :params mitigations: the mitigations used by the attacker action. The key is the technique and the
27
+ value is the mitigations
28
+ :params data_sources: the data sources used by the attacker action. The key is the technqiue and the value is
29
+ the data sources
30
+ :params sub_techniques: the sub-techniques used by the attacker action. The key is the technique and
31
+ the value is the sub-techniques
32
+ :params action_id: the id of the attacker action
33
+ """
34
+
35
+ self.techniques_tactics = techniques_tactics
36
+ self.mitigations = mitigations
37
+ self.data_sources = data_sources
38
+ self.subtechniques = subtechniques
39
+ self.action_id = action_id
40
+
41
+ @staticmethod
42
+ def get_attack_profile(attacker_action: EmulationAttackerAction) -> 'AttackProfiler':
43
+ """
44
+ Returns the attack profile of the actions
45
+
46
+ :params attacker_action: the attacker action
47
+ :return: the attack profile of the action
48
+ """
49
+ current_dir = os.path.dirname(__file__)
50
+ path = os.path.join(current_dir, "./dao/enterprise-attack.json")
51
+ mitre_attack_data = MitreAttackData(path)
52
+
53
+ # Retrieve the id from the attacker action
54
+ attacker_id = attacker_action.id
55
+ # Get the defined tactics and techniques for the attack
56
+ attack_mapping = EmulationAttackerMapping.get_attack_info(attacker_id)
57
+ if attack_mapping is None:
58
+ return AttackProfiler({}, {}, {}, {}, EmulationAttackerActionId.CONTINUE)
59
+
60
+ attack_techniques_vals = [technique.value for technique in attack_mapping['techniques']]
61
+
62
+ attacker_action_id = attacker_action.id
63
+ techniques_tactics = {}
64
+ mitigations = {}
65
+ data_sources = {}
66
+ sub_techniques = {}
67
+ # Loop over the techniques associated with the attack
68
+ for technique_name in attack_techniques_vals:
69
+ # Get technique from MitreAttackData, stix_id maps to technique in the library.
70
+ try:
71
+ obj = mitre_attack_data.get_objects_by_name(technique_name, "attack-pattern")
72
+ except Exception as e:
73
+ os.system("echo 'Error in getting technique: {}'".format(e))
74
+ continue
75
+ technique = obj[0]
76
+ stix_id = technique.id
77
+
78
+ # Collect the tactics and add it to the dictionary
79
+ tactics = [phase['phase_name'] for phase in technique.kill_chain_phases]
80
+ techniques_tactics[technique_name] = tactics
81
+
82
+ # Add the data sources to the dictionary
83
+ if hasattr(technique, 'x_mitre_data_sources'):
84
+ data_sources[technique_name] = technique.x_mitre_data_sources
85
+ # Fetch the mitigations from the technique and add it to the dictionary
86
+ try:
87
+ mitigations_object = mitre_attack_data.get_mitigations_mitigating_technique(stix_id)
88
+ mitigations_list = [mitig['object']['name'] for mitig in mitigations_object]
89
+ mitigations[technique_name] = mitigations_list
90
+ except Exception as e:
91
+ os.system("echo 'Error in getting mitigations: {}'".format(e))
92
+ continue
93
+
94
+ # Add the sub-technique to the dictionary
95
+ if 'subtechniques' in attack_mapping:
96
+ sub_techniques_mapping = [sub_technique.value for sub_technique in attack_mapping['subtechniques']]
97
+ for st in sub_techniques_mapping:
98
+ try:
99
+ sub_technique_obj = mitre_attack_data.get_objects_by_name(st, "attack-pattern")
100
+ parent_technique_obj = mitre_attack_data.get_parent_technique_of_subtechnique(
101
+ sub_technique_obj[0].id)
102
+ sub_techniques[parent_technique_obj[0]['object'].name] = st
103
+ except Exception as e:
104
+ os.system("echo 'Error in getting sub-techniques: {}'".format(e))
105
+ continue
106
+
107
+ return AttackProfiler(techniques_tactics, mitigations, data_sources, sub_techniques, attacker_action_id)
108
+
109
+ @staticmethod
110
+ def get_attack_profile_sequence(attacker_actions: List[EmulationAttackerAction],
111
+ attack_graph: Union[AttackGraph, None] = None) -> List['AttackProfiler']:
112
+ """
113
+ Returns the attack profile of the actions in a sequence
114
+
115
+ :params attacker_action: a list of attacker actions
116
+
117
+ :return: a list of attack profiles of the actions
118
+ """
119
+
120
+ attack_profiles = []
121
+ for action in attacker_actions:
122
+ attack_profiles.append(AttackProfiler.get_attack_profile(action))
123
+
124
+ # IF attack graph is provided
125
+ if attack_graph:
126
+
127
+ node = attack_graph.get_root_node()
128
+ for profile in attack_profiles:
129
+ # Get the mappings of the techniques and tactics
130
+ techniques_tactics = profile.techniques_tactics
131
+ techniques_to_keep = []
132
+ children = attack_graph.get_children(node[0], node[2])
133
+ possible_nodes = []
134
+ # First we check the techniques in node we are currently at
135
+ for technique in techniques_tactics:
136
+ # If the node.name is in the techniques_tactics, add it to the techniques_to_keep
137
+ if node[0].value in techniques_tactics[technique]:
138
+ techniques_to_keep.append(technique)
139
+ if node not in possible_nodes:
140
+ possible_nodes.append(node)
141
+ if children is None:
142
+ continue
143
+ for child in children:
144
+ # Child is a list of tuples, where the first element is the node name,
145
+ # and the second element is the node id
146
+ for technique in techniques_tactics:
147
+ if child[0].value in techniques_tactics[technique]:
148
+
149
+ techniques_to_keep.append(technique)
150
+ # If the child is not in the possible_children, add it to the list.
151
+ if attack_graph.get_node(child[0], child[1]) not in possible_nodes:
152
+ p_node = attack_graph.get_node(child[0], child[1])
153
+ if p_node is not None:
154
+ possible_nodes.append(p_node)
155
+
156
+ # If the possible node is just one node, move to that node
157
+ if len(possible_nodes) == 1:
158
+ node = possible_nodes[0]
159
+ if not techniques_to_keep:
160
+ continue
161
+ # Remove the techniques and associated tactics, data sources,
162
+ # mitigations and sub-techniques that are not in the techniques_to_keep
163
+ techniques_to_remove = set(profile.techniques_tactics.keys()) - set(techniques_to_keep)
164
+ for technique in techniques_to_remove:
165
+ try:
166
+ del profile.techniques_tactics[technique]
167
+ del profile.mitigations[technique]
168
+ del profile.data_sources[technique]
169
+ del profile.subtechniques[technique]
170
+ except Exception as e:
171
+ os.system("echo 'Error in removing techniques: {}'".format(e))
172
+ continue
173
+
174
+ # ELSE Baseline conditions
175
+ else:
176
+ initial_access = False
177
+ for profile in attack_profiles:
178
+ techniques_tactics = profile.techniques_tactics
179
+ techniques_to_remove = set()
180
+ # Loop over the mappings of the techniques to tactics
181
+ for technique in techniques_tactics:
182
+ if Tactics.DISCOVERY.value in techniques_tactics[technique] and not initial_access:
183
+ techniques_to_remove.add(technique)
184
+ elif Tactics.RECONNAISSANCE.value in techniques_tactics[technique] and initial_access:
185
+ techniques_to_remove.add(technique)
186
+ if Tactics.INITIAL_ACCESS.value in techniques_tactics[technique] and not initial_access:
187
+ initial_access = True
188
+ elif Tactics.INITIAL_ACCESS.value in techniques_tactics[technique] and initial_access:
189
+ techniques_to_remove.add(technique)
190
+ elif Tactics.LATERAL_MOVEMENT.value in techniques_tactics[technique] and not initial_access:
191
+ techniques_to_remove.add(technique)
192
+
193
+ # Remove the techniques and associated tactics, data sources, mitigations and sub-techniques
194
+ for technique in techniques_to_remove:
195
+ try:
196
+ del profile.techniques_tactics[technique]
197
+ del profile.mitigations[technique]
198
+ del profile.data_sources[technique]
199
+ del profile.subtechniques[technique]
200
+ except Exception as e:
201
+ os.system("echo 'Error in removing techniques: {}'".format(e))
202
+ continue
203
+
204
+ return attack_profiles