From 462b20f58780a5cc5cfc14388a32cfd66bac75bd Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 12 Oct 2021 15:39:56 +0530 Subject: [PATCH 01/14] island: Add related attack techniques to internal config values 'exploit_ntlm_hash_list' and 'exploit_lm_hash_list' --- monkey/monkey_island/cc/services/config_schema/internal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index b6e926dfb..84baa6ca5 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -340,6 +340,7 @@ INTERNAL = { "items": {"type": "string"}, "default": [], "description": "List of LM hashes to use on exploits using credentials", + "related_attack_techniques": ["T1075"], }, "exploit_ntlm_hash_list": { "title": "Exploit NTLM hash list", @@ -348,6 +349,7 @@ INTERNAL = { "items": {"type": "string"}, "default": [], "description": "List of NTLM hashes to use on exploits using credentials", + "related_attack_techniques": ["T1075"], }, "exploit_ssh_keys": { "title": "SSH key pairs list", From 80811334d7564ea53de078528fd234146a31882e Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 12 Oct 2021 15:41:03 +0530 Subject: [PATCH 02/14] island: Reword message for unscanned attack techniques --- .../cc/services/attack/technique_reports/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 30405dd69..00df909f2 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -143,7 +143,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta): reasons.append(f"- Monkey did not run on any {cls.relevant_systems[0]} systems.") if cls.tech_id in config_schema_per_attack_technique: reasons.append( - "- The following configuration options were disabled:
" + "- The following configuration options were disabled or empty:
" f"{cls._get_relevant_config_values(config_schema_per_attack_technique)}" ) From 7bdbdb1bfbbeaaa2118d748384b4754a208da7d0 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 12 Oct 2021 15:42:40 +0530 Subject: [PATCH 03/14] island: Go through internal config when generating reverse schema for unscanned attack techniques' reasons --- .../config_schema_per_attack_technique.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index 9b7cd6bb2..1b4c39413 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -25,6 +25,21 @@ def get_config_schema_per_attack_technique(schema: Dict) -> Dict[str, Dict[str, definition_type, config_field, attack_technique, reverse_schema ) + properties = schema["properties"] + for prop in properties: + property_type = properties[prop]["title"] + for tab_name in properties[prop]["properties"]: + tab = properties[prop]["properties"][tab_name] + for config_option_name in tab["properties"]: + config_option = tab["properties"][config_option_name] + for attack_technique in config_option.get("related_attack_techniques", []): + # No config values could be a reason that related attack techniques are left + # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. + config_field = f"{config_option['title']} ({tab['title']})" + _add_config_field_to_reverse_schema( + property_type, config_field, attack_technique, reverse_schema + ) + return reverse_schema From 1adf462ac39a166a9af367a24c17757162f4002b Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 12 Oct 2021 15:54:41 +0530 Subject: [PATCH 04/14] tests: Modify unit tests as per changes to reverse schema and attack report generation --- .../test_technique_reports.py | 7 ++++--- .../test_config_schema_per_attack_technique.py | 1 + .../monkey_island/cc/services/conftest.py | 18 +++++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py index 82a23a9ae..05f051ac2 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py @@ -12,6 +12,7 @@ FAKE_CONFIG_SCHEMA_PER_ATTACK_TECHNIQUE = { "T0000": { "Definition Type 1": ["Config Option 1", "Config Option 2"], "Definition Type 2": ["Config Option 5", "Config Option 6"], + "Property Type 1": ["Config Option 1 (Tab 1)"], }, "T0001": { "Definition Type 1": ["Config Option 1"], @@ -23,7 +24,7 @@ FAKE_CONFIG_SCHEMA_PER_ATTACK_TECHNIQUE = { @pytest.fixture(scope="function", autouse=True) def mock_config_schema_per_attack_technique(monkeypatch, fake_schema): monkeypatch.setattr( - ("monkey_island.cc.services.attack.technique_reports." "__init__.SCHEMA"), + ("monkey_island.cc.services.attack.technique_reports.__init__.SCHEMA"), fake_schema, ) @@ -42,7 +43,7 @@ class FakeAttackTechnique_TwoRelevantSystems(AttackTechnique): class ExpectedMsgs_TwoRelevantSystems(Enum): UNSCANNED: str = ( "UNSCANNED due to one of the following reasons:\n" - "- The following configuration options were disabled:
" + "- The following configuration options were disabled or empty:
" "- Definition Type 1 — Config Option 1
" "- Definition Type 2 — Config Option 5
" ) @@ -65,7 +66,7 @@ class ExpectedMsgs_OneRelevantSystem(Enum): UNSCANNED: str = ( "UNSCANNED due to one of the following reasons:\n" "- Monkey did not run on any System 1 systems.\n" - "- The following configuration options were disabled:
" + "- The following configuration options were disabled or empty:
" "- Definition Type 1 — Config Option 1
" "- Definition Type 2 — Config Option 5
" ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py index bacdae5dd..fdaf36374 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py @@ -6,6 +6,7 @@ REVERSE_FAKE_SCHEMA = { "T0000": { "Definition Type 1": ["Config Option 1", "Config Option 2"], "Definition Type 2": ["Config Option 5", "Config Option 6"], + "Property Type 1": ["Config Option 1 (Tab 1)"], }, "T0001": { "Definition Type 1": ["Config Option 1"], diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py index b89be55f9..90ffdb7d7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py @@ -66,5 +66,21 @@ def fake_schema(): }, ], }, - } + }, + "properties": { + "property_type_1": { + "title": "Property Type 1", + "properties": { + "tab_1": { + "title": "Tab 1", + "properties": { + "config_option_1": { + "title": "Config Option 1", + "related_attack_techniques": ["T0000"], + }, + }, + } + }, + } + }, } From e42a9d8b8f32fc4c61c3a04600eefb83a29263a7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 12 Oct 2021 16:00:40 +0530 Subject: [PATCH 05/14] CHANGELOG: Add entry for modified ATT&CK report messages --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e358f3d5..d4ba8e014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Resetting login credentials also cleans the contents of the database. #1495 - ATT&CK report messages (more accurate now). #1483 - T1086 (PowerShell) now also reports if ps1 scripts were run by PBAs. #1513 +- ATT&CK report messages to include empty internal config options as reasons for unscanned attack + techniques. #1518 ### Removed - Internet access check on agent start. #1402 From 55fcfa981389dfa86bae9285c4f158a716e1b1b9 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 18:06:42 +0530 Subject: [PATCH 06/14] island: Move code for generating reverse schema into functions for better readibility --- .../config_schema_per_attack_technique.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index 1b4c39413..9003e25a1 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -15,6 +15,13 @@ def get_config_schema_per_attack_technique(schema: Dict) -> Dict[str, Dict[str, """ reverse_schema = {} + _crawl_config_schema_definitions_for_reverse_schema(schema, reverse_schema) + _crawl_config_schema_properties_for_reverse_schema(schema, reverse_schema) + + return reverse_schema + + +def _crawl_config_schema_definitions_for_reverse_schema(schema: Dict, reverse_schema: Dict): definitions = schema["definitions"] for definition in definitions: definition_type = definitions[definition]["title"] @@ -25,6 +32,8 @@ def get_config_schema_per_attack_technique(schema: Dict) -> Dict[str, Dict[str, definition_type, config_field, attack_technique, reverse_schema ) + +def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_schema: Dict): properties = schema["properties"] for prop in properties: property_type = properties[prop]["title"] @@ -40,8 +49,6 @@ def get_config_schema_per_attack_technique(schema: Dict) -> Dict[str, Dict[str, property_type, config_field, attack_technique, reverse_schema ) - return reverse_schema - def _add_config_field_to_reverse_schema( definition_type: str, config_field: str, attack_technique: str, reverse_schema: Dict From b24b8439c5a7c3a163ccd96a29626326fef5bd08 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 18:08:06 +0530 Subject: [PATCH 07/14] island: Change 'tab' to 'category' in reverse schema generation --- .../config_schema_per_attack_technique.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index 9003e25a1..e73a1be75 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -37,14 +37,14 @@ def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_sch properties = schema["properties"] for prop in properties: property_type = properties[prop]["title"] - for tab_name in properties[prop]["properties"]: - tab = properties[prop]["properties"][tab_name] - for config_option_name in tab["properties"]: - config_option = tab["properties"][config_option_name] + for category_name in properties[prop]["properties"]: + category = properties[prop]["properties"][category_name] + for config_option_name in category["properties"]: + config_option = category["properties"][config_option_name] for attack_technique in config_option.get("related_attack_techniques", []): # No config values could be a reason that related attack techniques are left # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. - config_field = f"{config_option['title']} ({tab['title']})" + config_field = f"{config_option['title']} ({category['title']})" _add_config_field_to_reverse_schema( property_type, config_field, attack_technique, reverse_schema ) From 08e57f38242e9be0a7993f82ed17c80118862c9b Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 18:18:47 +0530 Subject: [PATCH 08/14] island: Use '.get()' when accessing value in dictionary during reverse schema generation --- .../config_schema/config_schema_per_attack_technique.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index e73a1be75..e78b055a2 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -25,7 +25,7 @@ def _crawl_config_schema_definitions_for_reverse_schema(schema: Dict, reverse_sc definitions = schema["definitions"] for definition in definitions: definition_type = definitions[definition]["title"] - for field in definitions[definition]["anyOf"]: + for field in definitions[definition].get("anyOf", []): config_field = field["title"] for attack_technique in field.get("attack_techniques", []): _add_config_field_to_reverse_schema( @@ -37,9 +37,9 @@ def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_sch properties = schema["properties"] for prop in properties: property_type = properties[prop]["title"] - for category_name in properties[prop]["properties"]: + for category_name in properties[prop].get("properties", []): category = properties[prop]["properties"][category_name] - for config_option_name in category["properties"]: + for config_option_name in category.get("properties", []): config_option = category["properties"][config_option_name] for attack_technique in config_option.get("related_attack_techniques", []): # No config values could be a reason that related attack techniques are left From ffd8f4edfe90fd283cc460a9e7395dd74f3205fb Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 19:55:46 +0530 Subject: [PATCH 09/14] island: Check related attack techniques recursively when generating reverse schema so it doesn't break when another level of nesting is added --- .../config_schema_per_attack_technique.py | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index e78b055a2..f6320f525 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -39,15 +39,40 @@ def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_sch property_type = properties[prop]["title"] for category_name in properties[prop].get("properties", []): category = properties[prop]["properties"][category_name] - for config_option_name in category.get("properties", []): - config_option = category["properties"][config_option_name] - for attack_technique in config_option.get("related_attack_techniques", []): - # No config values could be a reason that related attack techniques are left - # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. - config_field = f"{config_option['title']} ({category['title']})" - _add_config_field_to_reverse_schema( - property_type, config_field, attack_technique, reverse_schema - ) + _crawl_properties( + config_option_path=property_type, + config_option=category, + reverse_schema=reverse_schema, + ) + + +def _crawl_properties(config_option_path: str, config_option: Dict, reverse_schema: Dict): + config_option_path = " -> ".join([config_option_path, config_option["title"]]) + + for config_option_name in config_option.get("properties", []): + new_config_option = config_option["properties"][config_option_name] + _check_related_attack_techniques( + config_option_path=config_option_path, + config_option=new_config_option, + reverse_schema=reverse_schema, + ) + + # check for "properties" and each property's related techniques recursively; + # the levels of nesting and where related techniques are declared won't + # always be fixed in the config schema + _crawl_properties(config_option_path, new_config_option, reverse_schema) + + +def _check_related_attack_techniques( + config_option_path: str, config_option: Dict, reverse_schema: Dict +): + for attack_technique in config_option.get("related_attack_techniques", []): + # No config values could be a reason that related attack techniques are left + # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. + config_field = f"{config_option['title']}" + _add_config_field_to_reverse_schema( + config_option_path, config_field, attack_technique, reverse_schema + ) def _add_config_field_to_reverse_schema( From f7f2e69152c09439b8f901538763f8e4d1c49b94 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 19:56:35 +0530 Subject: [PATCH 10/14] tests: Modify tests to test reverse schema generation with multiple levels of nesting --- ...test_config_schema_per_attack_technique.py | 13 +++-- .../monkey_island/cc/services/conftest.py | 51 +++++++++++++++++-- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py index fdaf36374..02010ac30 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py @@ -6,12 +6,15 @@ REVERSE_FAKE_SCHEMA = { "T0000": { "Definition Type 1": ["Config Option 1", "Config Option 2"], "Definition Type 2": ["Config Option 5", "Config Option 6"], - "Property Type 1": ["Config Option 1 (Tab 1)"], - }, - "T0001": { - "Definition Type 1": ["Config Option 1"], - "Definition Type 2": ["Config Option 5"], + "Property Type 1 -> Category 1": ["Config Option 1"], + "Property Type 2 -> Category 1": ["Config Option 1"], + "Property Type 2 -> Category 2 -> Config Option 1": ["Config Option 1.1"], + "Property Type 2 -> Category 2 -> Config Option 2": ["Config Option 2.1"], + "Property Type 2 -> Category 2 -> Config Option 2 -> Config Option 2.1": [ + "Config Option 2.1.1" + ], }, + "T0001": {"Definition Type 1": ["Config Option 1"], "Definition Type 2": ["Config Option 5"]}, } diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py index 90ffdb7d7..bd0744f17 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/conftest.py @@ -71,16 +71,59 @@ def fake_schema(): "property_type_1": { "title": "Property Type 1", "properties": { - "tab_1": { - "title": "Tab 1", + "category_1": { + "title": "Category 1", "properties": { "config_option_1": { "title": "Config Option 1", "related_attack_techniques": ["T0000"], }, }, - } + }, }, - } + }, + "property_type_2": { + "title": "Property Type 2", + "properties": { + "category_1": { + "title": "Category 1", + "properties": { + "config_option_1": { + "title": "Config Option 1", + "related_attack_techniques": ["T0000"], + }, + }, + }, + "category_2": { + "title": "Category 2", + "properties": { + "config_option_1": { + "title": "Config Option 1", + "properties": { + "config_option_1.1": { + "title": "Config Option 1.1", + "related_attack_techniques": ["T0000"], + }, + }, + }, + "config_option_2": { + "title": "Config Option 2", + "properties": { + "config_option_2.1": { + "title": "Config Option 2.1", + "properties": { + "config_option_2.1.1": { + "title": "Config Option 2.1.1", + "related_attack_techniques": ["T0000"], + } + }, + "related_attack_techniques": ["T0000"], + }, + }, + }, + }, + }, + }, + }, }, } From b6923edbe9804e376451b13f84ec01e6b43cd26b Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 13 Oct 2021 22:26:56 +0530 Subject: [PATCH 11/14] tests: Modify technique reports' tests --- .../test_technique_reports.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py index 05f051ac2..1da9860b9 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/attack/technique_reports/test_technique_reports.py @@ -8,18 +8,6 @@ from monkey_island.cc.services.attack.technique_reports.__init__ import ( disabled_msg, ) -FAKE_CONFIG_SCHEMA_PER_ATTACK_TECHNIQUE = { - "T0000": { - "Definition Type 1": ["Config Option 1", "Config Option 2"], - "Definition Type 2": ["Config Option 5", "Config Option 6"], - "Property Type 1": ["Config Option 1 (Tab 1)"], - }, - "T0001": { - "Definition Type 1": ["Config Option 1"], - "Definition Type 2": ["Config Option 5"], - }, -} - @pytest.fixture(scope="function", autouse=True) def mock_config_schema_per_attack_technique(monkeypatch, fake_schema): @@ -30,7 +18,7 @@ def mock_config_schema_per_attack_technique(monkeypatch, fake_schema): class FakeAttackTechnique_TwoRelevantSystems(AttackTechnique): - tech_id = "T0001" + tech_id = "T0000" relevant_systems = ["System 1", "System 2"] unscanned_msg = "UNSCANNED" scanned_msg = "SCANNED" @@ -44,8 +32,14 @@ class ExpectedMsgs_TwoRelevantSystems(Enum): UNSCANNED: str = ( "UNSCANNED due to one of the following reasons:\n" "- The following configuration options were disabled or empty:
" - "- Definition Type 1 — Config Option 1
" - "- Definition Type 2 — Config Option 5
" + "- Definition Type 1 — Config Option 1, Config Option 2
" + "- Definition Type 2 — Config Option 5, Config Option 6
" + "- Property Type 1 -> Category 1 — Config Option 1
" + "- Property Type 2 -> Category 1 — Config Option 1
" + "- Property Type 2 -> Category 2 -> Config Option 1 — Config Option 1.1
" + "- Property Type 2 -> Category 2 -> Config Option 2 — Config Option 2.1
" + "- Property Type 2 -> Category 2 -> Config Option 2 -> Config Option 2.1 — Config Option " + "2.1.1
" ) SCANNED: str = "SCANNED" USED: str = "USED" From 74095b6fc61a1436c07dae7a9766957899497036 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 14 Oct 2021 00:23:58 +0530 Subject: [PATCH 12/14] island: Modify logic for reverse schema generation recursion --- .../config_schema/config_schema_per_attack_technique.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index f6320f525..b945adbc8 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -47,8 +47,11 @@ def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_sch def _crawl_properties(config_option_path: str, config_option: Dict, reverse_schema: Dict): - config_option_path = " -> ".join([config_option_path, config_option["title"]]) - + config_option_path = ( + f"{config_option_path} -> {config_option['title']}" + if "title" in config_option + else config_option_path + ) for config_option_name in config_option.get("properties", []): new_config_option = config_option["properties"][config_option_name] _check_related_attack_techniques( @@ -69,7 +72,7 @@ def _check_related_attack_techniques( for attack_technique in config_option.get("related_attack_techniques", []): # No config values could be a reason that related attack techniques are left # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. - config_field = f"{config_option['title']}" + config_field = config_option["title"] _add_config_field_to_reverse_schema( config_option_path, config_field, attack_technique, reverse_schema ) From faa4c18cab95cb572b9e7c78fdd7b26ad845be75 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 14 Oct 2021 14:15:32 +0530 Subject: [PATCH 13/14] island: Create class for reverse schema generation to avoid output arguments --- .../attack/technique_reports/__init__.py | 6 +- .../config_schema_per_attack_technique.py | 141 +++++++++--------- 2 files changed, 71 insertions(+), 76 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 00df909f2..529cf7b95 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -9,7 +9,7 @@ from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.config_schema.config_schema import SCHEMA from monkey_island.cc.services.config_schema.config_schema_per_attack_technique import ( - get_config_schema_per_attack_technique, + ConfigSchemaPerAttackTechnique, ) logger = logging.getLogger(__name__) @@ -122,8 +122,8 @@ class AttackTechnique(object, metaclass=abc.ABCMeta): return disabled_msg if status == ScanStatus.UNSCANNED.value: if not cls.config_schema_per_attack_technique: - cls.config_schema_per_attack_technique = get_config_schema_per_attack_technique( - SCHEMA + cls.config_schema_per_attack_technique = ( + ConfigSchemaPerAttackTechnique().get_config_schema_per_attack_technique(SCHEMA) ) unscanned_msg = cls._get_unscanned_msg_with_reasons( cls.unscanned_msg, cls.config_schema_per_attack_technique diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py index b945adbc8..547161936 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema_per_attack_technique.py @@ -1,86 +1,81 @@ from typing import Dict, List -def get_config_schema_per_attack_technique(schema: Dict) -> Dict[str, Dict[str, List[str]]]: - """ - :return: dictionary mapping each attack technique to relevant config fields; example - - { - "T1003": { - "System Info Collectors": [ - "Mimikatz collector", - "Azure credential collector" - ] +class ConfigSchemaPerAttackTechnique: + def __init__(self) -> None: + self.reverse_schema = {} + + def get_config_schema_per_attack_technique( + self, schema: Dict + ) -> Dict[str, Dict[str, List[str]]]: + """ + :return: dictionary mapping each attack technique to relevant config fields; example - + { + "T1003": { + "System Info Collectors": [ + "Mimikatz collector", + "Azure credential collector" + ] + } } - } - """ - reverse_schema = {} + """ + self._crawl_config_schema_definitions_for_reverse_schema(schema) + self._crawl_config_schema_properties_for_reverse_schema(schema) - _crawl_config_schema_definitions_for_reverse_schema(schema, reverse_schema) - _crawl_config_schema_properties_for_reverse_schema(schema, reverse_schema) + return self.reverse_schema - return reverse_schema + def _crawl_config_schema_definitions_for_reverse_schema(self, schema: Dict): + definitions = schema["definitions"] + for definition in definitions: + definition_type = definitions[definition]["title"] + for field in definitions[definition].get("anyOf", []): + config_field = field["title"] + for attack_technique in field.get("attack_techniques", []): + self._add_config_field_to_reverse_schema( + definition_type, config_field, attack_technique + ) - -def _crawl_config_schema_definitions_for_reverse_schema(schema: Dict, reverse_schema: Dict): - definitions = schema["definitions"] - for definition in definitions: - definition_type = definitions[definition]["title"] - for field in definitions[definition].get("anyOf", []): - config_field = field["title"] - for attack_technique in field.get("attack_techniques", []): - _add_config_field_to_reverse_schema( - definition_type, config_field, attack_technique, reverse_schema + def _crawl_config_schema_properties_for_reverse_schema(self, schema: Dict): + properties = schema["properties"] + for prop in properties: + property_type = properties[prop]["title"] + for category_name in properties[prop].get("properties", []): + category = properties[prop]["properties"][category_name] + self._crawl_properties( + config_option_path=property_type, + config_option=category, ) - -def _crawl_config_schema_properties_for_reverse_schema(schema: Dict, reverse_schema: Dict): - properties = schema["properties"] - for prop in properties: - property_type = properties[prop]["title"] - for category_name in properties[prop].get("properties", []): - category = properties[prop]["properties"][category_name] - _crawl_properties( - config_option_path=property_type, - config_option=category, - reverse_schema=reverse_schema, + def _crawl_properties(self, config_option_path: str, config_option: Dict): + config_option_path = ( + f"{config_option_path} -> {config_option['title']}" + if "title" in config_option + else config_option_path + ) + for config_option_name in config_option.get("properties", []): + new_config_option = config_option["properties"][config_option_name] + self._check_related_attack_techniques( + config_option_path=config_option_path, + config_option=new_config_option, ) + # check for "properties" and each property's related techniques recursively; + # the levels of nesting and where related techniques are declared won't + # always be fixed in the config schema + self._crawl_properties(config_option_path, new_config_option) -def _crawl_properties(config_option_path: str, config_option: Dict, reverse_schema: Dict): - config_option_path = ( - f"{config_option_path} -> {config_option['title']}" - if "title" in config_option - else config_option_path - ) - for config_option_name in config_option.get("properties", []): - new_config_option = config_option["properties"][config_option_name] - _check_related_attack_techniques( - config_option_path=config_option_path, - config_option=new_config_option, - reverse_schema=reverse_schema, - ) + def _check_related_attack_techniques(self, config_option_path: str, config_option: Dict): + for attack_technique in config_option.get("related_attack_techniques", []): + # No config values could be a reason that related attack techniques are left + # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. + config_field = config_option["title"] + self._add_config_field_to_reverse_schema( + config_option_path, config_field, attack_technique + ) - # check for "properties" and each property's related techniques recursively; - # the levels of nesting and where related techniques are declared won't - # always be fixed in the config schema - _crawl_properties(config_option_path, new_config_option, reverse_schema) - - -def _check_related_attack_techniques( - config_option_path: str, config_option: Dict, reverse_schema: Dict -): - for attack_technique in config_option.get("related_attack_techniques", []): - # No config values could be a reason that related attack techniques are left - # unscanned. See https://github.com/guardicore/monkey/issues/1518 for more. - config_field = config_option["title"] - _add_config_field_to_reverse_schema( - config_option_path, config_field, attack_technique, reverse_schema - ) - - -def _add_config_field_to_reverse_schema( - definition_type: str, config_field: str, attack_technique: str, reverse_schema: Dict -) -> None: - reverse_schema.setdefault(attack_technique, {}) - reverse_schema[attack_technique].setdefault(definition_type, []) - reverse_schema[attack_technique][definition_type].append(config_field) + def _add_config_field_to_reverse_schema( + self, definition_type: str, config_field: str, attack_technique: str + ) -> None: + self.reverse_schema.setdefault(attack_technique, {}) + self.reverse_schema[attack_technique].setdefault(definition_type, []) + self.reverse_schema[attack_technique][definition_type].append(config_field) From ae6ebcf3c79abaa86607dab1e9f8f04224fb5f57 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 14 Oct 2021 14:16:16 +0530 Subject: [PATCH 14/14] tests: Modify unit test for reverse schema generation --- .../test_config_schema_per_attack_technique.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py index 02010ac30..86383366e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/config_schema/test_config_schema_per_attack_technique.py @@ -1,5 +1,5 @@ from monkey_island.cc.services.config_schema.config_schema_per_attack_technique import ( - get_config_schema_per_attack_technique, + ConfigSchemaPerAttackTechnique, ) REVERSE_FAKE_SCHEMA = { @@ -19,4 +19,7 @@ REVERSE_FAKE_SCHEMA = { def test_get_config_schema_per_attack_technique(monkeypatch, fake_schema): - assert get_config_schema_per_attack_technique(fake_schema) == REVERSE_FAKE_SCHEMA + assert ( + ConfigSchemaPerAttackTechnique().get_config_schema_per_attack_technique(fake_schema) + == REVERSE_FAKE_SCHEMA + )