From 3c41bada560b3e3f8585ed343e6c936b6c8f10f3 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 27 Jun 2022 11:42:49 -0700 Subject: [PATCH 1/8] Island: Remove PasswordBasedStringEncryptor --- .../cc/server_utils/encryption/__init__.py | 4 --- .../password_based_string_encryptor.py | 33 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 monkey/monkey_island/cc/server_utils/encryption/password_based_string_encryptor.py diff --git a/monkey/monkey_island/cc/server_utils/encryption/__init__.py b/monkey/monkey_island/cc/server_utils/encryption/__init__.py index 7fa6c77a4..a6671afb6 100644 --- a/monkey/monkey_island/cc/server_utils/encryption/__init__.py +++ b/monkey/monkey_island/cc/server_utils/encryption/__init__.py @@ -2,10 +2,6 @@ from .i_encryptor import IEncryptor from .key_based_encryptor import ( KeyBasedEncryptor, ) -from .password_based_string_encryptor import ( - PasswordBasedStringEncryptor, - is_encrypted, -) from .password_based_bytes_encryptor import ( PasswordBasedBytesEncryptor, InvalidCredentialsError, diff --git a/monkey/monkey_island/cc/server_utils/encryption/password_based_string_encryptor.py b/monkey/monkey_island/cc/server_utils/encryption/password_based_string_encryptor.py deleted file mode 100644 index dac7276e9..000000000 --- a/monkey/monkey_island/cc/server_utils/encryption/password_based_string_encryptor.py +++ /dev/null @@ -1,33 +0,0 @@ -import base64 -import logging - -import pyAesCrypt - -from .i_encryptor import IEncryptor -from .password_based_bytes_encryptor import PasswordBasedBytesEncryptor - -logger = logging.getLogger(__name__) - - -class PasswordBasedStringEncryptor(IEncryptor): - - _BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef - - def __init__(self, password: str): - self.password = password - - def encrypt(self, plaintext: str) -> str: - ciphertext = PasswordBasedBytesEncryptor(self.password).encrypt(plaintext.encode()) - - return base64.b64encode(ciphertext).decode() - - def decrypt(self, ciphertext: str) -> str: - ciphertext = base64.b64decode(ciphertext) - - plaintext_stream = PasswordBasedBytesEncryptor(self.password).decrypt(ciphertext) - return plaintext_stream.decode() - - -def is_encrypted(ciphertext: str) -> bool: - ciphertext = base64.b64decode(ciphertext) - return ciphertext.startswith(b"AES") From 5c5ae5bb0df67e65c340bdbded257a4954b6f185 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 27 Jun 2022 12:07:58 -0700 Subject: [PATCH 2/8] Island: Modify ConfigurationImport and ConfigurationExport to work without PasswordBasedStringEncryptor --- .../cc/resources/configuration_export.py | 16 +------------- .../cc/resources/configuration_import.py | 22 +------------------ 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index e10e5d1a3..0865a2558 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -1,10 +1,5 @@ -import json - -from flask import request - from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.request_authentication import jwt_required -from monkey_island.cc.server_utils.encryption import PasswordBasedStringEncryptor from monkey_island.cc.services.config import ConfigService @@ -13,17 +8,8 @@ class ConfigurationExport(AbstractResource): @jwt_required def post(self): - data = json.loads(request.data) - should_encrypt = data["should_encrypt"] - plaintext_config = ConfigService.get_config() config_export = plaintext_config - if should_encrypt: - password = data["password"] - plaintext_config = json.dumps(plaintext_config) - pb_encryptor = PasswordBasedStringEncryptor(password) - config_export = pb_encryptor.encrypt(plaintext_config) - - return {"config_export": config_export, "encrypted": should_encrypt} + return {"config_export": config_export, "encrypted": False} diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 15ab3e41f..8f5d73afa 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -8,12 +8,7 @@ from flask import request from common.utils.exceptions import InvalidConfigurationError from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.request_authentication import jwt_required -from monkey_island.cc.server_utils.encryption import ( - InvalidCiphertextError, - InvalidCredentialsError, - PasswordBasedStringEncryptor, - is_encrypted, -) +from monkey_island.cc.server_utils.encryption import InvalidCiphertextError, InvalidCredentialsError from monkey_island.cc.services.config import ConfigService logger = logging.getLogger(__name__) @@ -75,9 +70,6 @@ class ConfigurationImport(AbstractResource): def _get_plaintext_config_from_request(request_contents: dict) -> dict: try: config = request_contents["config"] - if ConfigurationImport.is_config_encrypted(request_contents["config"]): - pb_encryptor = PasswordBasedStringEncryptor(request_contents["password"]) - config = pb_encryptor.decrypt(config) return json.loads(config) except (JSONDecodeError, InvalidCiphertextError): logger.exception( @@ -89,15 +81,3 @@ class ConfigurationImport(AbstractResource): def import_config(config_json): if not ConfigService.update_config(config_json, should_encrypt=True): raise InvalidConfigurationError - - @staticmethod - def is_config_encrypted(config: str): - try: - if config.startswith("{"): - return False - elif is_encrypted(config): - return True - else: - raise InvalidConfigurationError - except Exception: - raise InvalidConfigurationError From 9b4e6728b0000a4a28c9742ecb30df0879983e97 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 27 Jun 2022 12:09:50 -0700 Subject: [PATCH 3/8] UT: Remove test file for PasswordBasedStringEncryptor --- .../test_password_based_encryption.py | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py deleted file mode 100644 index 038b17ec1..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py +++ /dev/null @@ -1,47 +0,0 @@ -import pytest -from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( - MALFORMED_CIPHER_TEXT_CORRUPTED, - VALID_CIPHER_TEXT, -) - -from monkey_island.cc.server_utils.encryption import ( - InvalidCiphertextError, - InvalidCredentialsError, - PasswordBasedStringEncryptor, -) - -# Mark all tests in this module as slow -pytestmark = pytest.mark.slow - -PASSWORD = "hello123" -INCORRECT_PASSWORD = "goodbye321" - - -def test_encrypt_decrypt_string(monkey_config_json): - pb_encryptor = PasswordBasedStringEncryptor(PASSWORD) - encrypted_config = pb_encryptor.encrypt(monkey_config_json) - assert pb_encryptor.decrypt(encrypted_config) == monkey_config_json - - -def test_decrypt_string__wrong_password(monkey_config_json): - pb_encryptor = PasswordBasedStringEncryptor(INCORRECT_PASSWORD) - with pytest.raises(InvalidCredentialsError): - pb_encryptor.decrypt(VALID_CIPHER_TEXT) - - -def test_decrypt_string__malformed_corrupted(): - pb_encryptor = PasswordBasedStringEncryptor(PASSWORD) - with pytest.raises(ValueError): - pb_encryptor.decrypt(MALFORMED_CIPHER_TEXT_CORRUPTED) - - -def test_decrypt_string__no_password(monkey_config_json): - pb_encryptor = PasswordBasedStringEncryptor("") - with pytest.raises(InvalidCredentialsError): - pb_encryptor.decrypt(VALID_CIPHER_TEXT) - - -def test_decrypt_string__invalid_cyphertext(monkey_config_json): - pb_encryptor = PasswordBasedStringEncryptor("") - with pytest.raises(InvalidCiphertextError): - pb_encryptor.decrypt("") From 17a9c851997180f592c8ddfe8cafc861e95fc272 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 27 Jun 2022 12:10:36 -0700 Subject: [PATCH 4/8] UT: Remove test file for ConfigurationImport It only had tests related to encrypted configuration. --- .../cc/resources/test_configuration_import.py | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py deleted file mode 100644 index bf7ccff80..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -from tests.unit_tests.monkey_island.cc.server_utils.encryption.test_password_based_encryption import ( # noqa: E501 - PASSWORD, -) -from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( - MALFORMED_CIPHER_TEXT_CORRUPTED, -) - -from common.utils.exceptions import InvalidConfigurationError -from monkey_island.cc.resources.configuration_import import ConfigurationImport -from monkey_island.cc.server_utils.encryption import PasswordBasedStringEncryptor - - -def test_is_config_encrypted__json(monkey_config_json): - assert not ConfigurationImport.is_config_encrypted(monkey_config_json) - - -@pytest.mark.slow -def test_is_config_encrypted__ciphertext(monkey_config_json): - pb_encryptor = PasswordBasedStringEncryptor(PASSWORD) - encrypted_config = pb_encryptor.encrypt(monkey_config_json) - assert ConfigurationImport.is_config_encrypted(encrypted_config) - - -def test_is_config_encrypted__corrupt_ciphertext(): - with pytest.raises(InvalidConfigurationError): - assert ConfigurationImport.is_config_encrypted(MALFORMED_CIPHER_TEXT_CORRUPTED) - - -def test_is_config_encrypted__unknown_format(): - with pytest.raises(InvalidConfigurationError): - assert ConfigurationImport.is_config_encrypted("ABC") From de0ab88c3a76e1a5b8405ce5f2cc266c46cee467 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 28 Jun 2022 11:03:53 +0200 Subject: [PATCH 5/8] Island: Remove ConfigurationImport endpoint --- monkey/monkey_island/cc/app.py | 2 - .../cc/resources/configuration_import.py | 83 ------------------- .../ImportConfigModal.tsx | 1 + 3 files changed, 1 insertion(+), 85 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/configuration_import.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index e2253b36c..509abaa88 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -24,7 +24,6 @@ from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import ( TelemetryBlackboxEndpoint, ) from monkey_island.cc.resources.configuration_export import ConfigurationExport -from monkey_island.cc.resources.configuration_import import ConfigurationImport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.exploitations.manual_exploitation import ManualExploitation from monkey_island.cc.resources.exploitations.monkey_exploitation import MonkeyExploitation @@ -155,7 +154,6 @@ def init_api_resources(api: FlaskDIWrapper): api.add_resource(IslandMode) api.add_resource(IslandConfiguration) api.add_resource(ConfigurationExport) - api.add_resource(ConfigurationImport) api.add_resource(AgentConfiguration) api.add_resource(AgentBinaries) api.add_resource(NetMap) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py deleted file mode 100644 index 8f5d73afa..000000000 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ /dev/null @@ -1,83 +0,0 @@ -import json -import logging -from dataclasses import dataclass -from json.decoder import JSONDecodeError - -from flask import request - -from common.utils.exceptions import InvalidConfigurationError -from monkey_island.cc.resources.AbstractResource import AbstractResource -from monkey_island.cc.resources.request_authentication import jwt_required -from monkey_island.cc.server_utils.encryption import InvalidCiphertextError, InvalidCredentialsError -from monkey_island.cc.services.config import ConfigService - -logger = logging.getLogger(__name__) - - -class ImportStatuses: - UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required" - INVALID_CONFIGURATION = "invalid_configuration" - INVALID_CREDENTIALS = "invalid_credentials" - IMPORTED = "imported" - - -@dataclass -class ResponseContents: - import_status: str = ImportStatuses.IMPORTED - message: str = "" - status_code: int = 200 - config: str = "" - config_schema: str = "" - - def form_response(self): - return self.__dict__ - - -class ConfigurationImport(AbstractResource): - # API Spec: Should probably be merged with IslandConfiguration - urls = ["/api/configuration/import"] - SUCCESS = False - - @jwt_required - def post(self): - request_contents = json.loads(request.data) - try: - config = ConfigurationImport._get_plaintext_config_from_request(request_contents) - if request_contents["unsafeOptionsVerified"]: - ConfigurationImport.import_config(config) - return ResponseContents().form_response() - else: - return ResponseContents( - config=json.dumps(config), - config_schema=ConfigService.get_config_schema(), - import_status=ImportStatuses.UNSAFE_OPTION_VERIFICATION_REQUIRED, - ).form_response() - # API Spec: HTTP status code should be 401 here - except InvalidCredentialsError: - return ResponseContents( - import_status=ImportStatuses.INVALID_CREDENTIALS, - message="Invalid credentials provided", - ).form_response() - # API Spec: HTTP status code should be 400 (or something else) here - except InvalidConfigurationError: - return ResponseContents( - import_status=ImportStatuses.INVALID_CONFIGURATION, - message="Invalid configuration supplied. " - "Maybe the format is outdated or the file has been corrupted.", - ).form_response() - - @staticmethod - def _get_plaintext_config_from_request(request_contents: dict) -> dict: - try: - config = request_contents["config"] - return json.loads(config) - except (JSONDecodeError, InvalidCiphertextError): - logger.exception( - "Exception encountered when trying to extract plaintext configuration." - ) - raise InvalidConfigurationError - - @staticmethod - def import_config(config_json): - if not ConfigService.update_config(config_json, should_encrypt=True): - raise InvalidConfigurationError diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 8a600ab28..70e366d20 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -18,6 +18,7 @@ type Props = { const ConfigImportModal = (props: Props) => { + // TODO: change this endpoint to the new configuration import endpoint const configImportEndpoint = '/api/configuration/import'; const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); From 50351c9e880e7f9a6fa1ad8d18af132375e4610f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 28 Jun 2022 11:47:02 +0200 Subject: [PATCH 6/8] Island: Remove ConfigurationExport endpoint --- monkey/monkey_island/cc/app.py | 2 -- .../cc/resources/configuration_export.py | 15 --------------- .../ExportConfigModal.tsx | 1 + 3 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/configuration_export.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 509abaa88..282f93346 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -23,7 +23,6 @@ from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyB from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import ( TelemetryBlackboxEndpoint, ) -from monkey_island.cc.resources.configuration_export import ConfigurationExport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.exploitations.manual_exploitation import ManualExploitation from monkey_island.cc.resources.exploitations.monkey_exploitation import MonkeyExploitation @@ -153,7 +152,6 @@ def init_api_resources(api: FlaskDIWrapper): api.add_resource(IslandMode) api.add_resource(IslandConfiguration) - api.add_resource(ConfigurationExport) api.add_resource(AgentConfiguration) api.add_resource(AgentBinaries) api.add_resource(NetMap) diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py deleted file mode 100644 index 0865a2558..000000000 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ /dev/null @@ -1,15 +0,0 @@ -from monkey_island.cc.resources.AbstractResource import AbstractResource -from monkey_island.cc.resources.request_authentication import jwt_required -from monkey_island.cc.services.config import ConfigService - - -class ConfigurationExport(AbstractResource): - urls = ["/api/configuration/export"] - - @jwt_required - def post(self): - plaintext_config = ConfigService.get_config() - - config_export = plaintext_config - - return {"config_export": config_export, "encrypted": False} diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index d30438cbd..9dc785f73 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -12,6 +12,7 @@ type Props = { } const ConfigExportModal = (props: Props) => { + // TODO: Change this endpoint to new agent-configuration endpoint const configExportEndpoint = '/api/configuration/export'; const [pass, setPass] = useState(''); From 9774bd6f3b43deb1927c02743e1f14f7a084f05a Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 28 Jun 2022 16:35:09 +0200 Subject: [PATCH 7/8] Changelog: Add entry for removing configuration export/import endpoints --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0dd381f..b85ed376f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - "+dev" from version numbers. #1553 - agent's "--config" argument. #906 - Option to export monkey telemetries. #1998 +- "/api/configuration/import" endpoint. #2002 +- "/api/configuration/export" endpoint. #2002 ### Fixed - A bug in network map page that caused delay of telemetry log loading. #1545 From 4e6aac21533a8251c37c1bc8710db257dc70b1d7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Tue, 28 Jun 2022 09:22:49 -0700 Subject: [PATCH 8/8] UT: Remove unused fixtures and data --- .../monkey_config_standard.json | 113 ------------------ .../unit_tests/monkey_island/cc/conftest.py | 14 --- 2 files changed, 127 deletions(-) delete mode 100644 monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json deleted file mode 100644 index 678023ebb..000000000 --- a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "basic": { - "exploiters": { - "exploiter_classes": [ - "SmbExploiter", - "WmiExploiter", - "SSHExploiter", - "HadoopExploiter", - "MSSQLExploiter" - ] - }, - "credentials": { - "exploit_user_list": [ - "Administrator", - "root", - "user" - ], - "exploit_password_list": [ - "root", - "123456", - "password", - "123456789", - "qwerty", - "111111", - "iloveyou" - ] - } - }, - "basic_network": { - "scope": { - "blocked_ips": [], - "local_network_scan": true, - "depth": 2, - "subnet_scan_list": [] - }, - "network_analysis": { - "inaccessible_subnets": [] - } - }, - "internal": { - "general": { - "keep_tunnel_open_time": 60 - }, - "network": { - "tcp_scanner": { - "HTTP_PORTS": [ - 80, - 8080, - 443, - 8008, - 7001, - 9200 - ], - "tcp_target_ports": [ - 22, - 2222, - 445, - 135, - 3389, - 80, - 8080, - 443, - 8008, - 3306, - 7001, - 8088 - ], - "tcp_scan_timeout": 3000 - }, - "ping_scanner": { - "ping_scan_timeout": 1000 - } - }, - "classes": { - "finger_classes": [ - "SMBFinger", - "SSHFinger", - "HTTPFinger", - "MSSQLFinger", - "ElasticFinger" - ] - }, - "exploits": { - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "exploit_ssh_keys": [] - } - }, - "monkey": { - "post_breach": { - "custom_pba_linux_cmd": "", - "custom_pba_windows_cmd": "", - "pba_windows_filename": "", - "pba_linux_filename": "", - "post_breach_actions": [ - "communicateasbackdooruser", - "modifyshellstartupfiles", - "hiddenfiles", - "trapcommand", - "changesetuidsetgid", - "schedulejobs", - "timestomping", - "accountdiscovery" - ] - }, - "system_info": { - "system_info_collector_classes": [ - "MimikatzCollector", - "SSHCollector" - ] - } - } - } diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index ba5a2c66e..52e73dc50 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -1,28 +1,14 @@ -# Without these imports pytests can't use fixtures, -# because they are not found -import json - import pytest from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402 from monkey_island.cc.server_utils.encryption import unlock_datastore_encryptor -@pytest.fixture -def monkey_config(load_monkey_config): - return load_monkey_config("monkey_config_standard.json") - - @pytest.fixture def flat_monkey_config(load_monkey_config): return load_monkey_config("flat_config.json") -@pytest.fixture -def monkey_config_json(monkey_config): - return json.dumps(monkey_config) - - @pytest.fixture def uses_encryptor(data_for_tests_dir): secret = "m0nk3y_u53r:3cr3t_p455w0rd"