From f048cf313ca26f75f2b0cccc1d41e65a96e08743 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 06:18:43 -0400 Subject: [PATCH 01/19] island: Pass data_dir to LocalRun instead of using global singleton --- monkey/monkey_island/cc/app.py | 13 +++++++++---- monkey/monkey_island/cc/main.py | 10 ++++++---- monkey/monkey_island/cc/resources/local_run.py | 8 +++++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 7467c4bdd..a41b26e9a 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -117,14 +117,19 @@ def init_app_url_rules(app): app.add_url_rule("/", "serve_static_file", serve_static_file) -def init_api_resources(api): +def init_api_resources(api, data_dir): api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") api.add_resource(Environment, "/api/environment") api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") api.add_resource(Bootloader, "/api/bootloader/") - api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") + api.add_resource( + LocalRun, + "/api/local-monkey", + "/api/local-monkey/", + resource_class_kwargs={"data_dir": data_dir}, + ) api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" @@ -173,7 +178,7 @@ def init_api_resources(api): api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry") -def init_app(mongo_url): +def init_app(mongo_url, data_dir): app = Flask(__name__) api = flask_restful.Api(app) @@ -182,6 +187,6 @@ def init_app(mongo_url): init_app_config(app, mongo_url) init_app_services(app) init_app_url_rules(app) - init_api_resources(api) + init_api_resources(api, data_dir) return app diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index ba5ee856c..288baae67 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -35,8 +35,10 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") + + data_dir = env_singleton.env.get_config().data_dir_abs_path env_singleton.initialize_from_file(server_config_filename) - initialize_encryptor(env_singleton.env.get_config().data_dir_abs_path) + initialize_encryptor(data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( @@ -44,17 +46,17 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P ) bootloader_server_thread.start() - start_island_server(should_setup_only) + start_island_server(should_setup_only, data_dir) bootloader_server_thread.join() -def start_island_server(should_setup_only): +def start_island_server(should_setup_only, data_dir): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) populate_exporter_list() - app = init_app(mongo_url) + app = init_app(mongo_url, data_dir) crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 2adc60cbe..91c02a4f7 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -20,7 +20,7 @@ __author__ = "Barak" logger = logging.getLogger(__name__) -def run_local_monkey(): +def run_local_monkey(dest_dir): import platform import stat import subprocess @@ -31,7 +31,6 @@ def run_local_monkey(): return False, "OS Type not found" src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_dir = env_singleton.env.get_config().data_dir_abs_path dest_path = os.path.join(dest_dir, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may @@ -60,6 +59,9 @@ def run_local_monkey(): class LocalRun(flask_restful.Resource): + def __init__(self, data_dir): + self._data_dir = data_dir + @jwt_required def get(self): NodeService.update_dead_monkeys() @@ -75,7 +77,7 @@ class LocalRun(flask_restful.Resource): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = run_local_monkey() + local_run = run_local_monkey(self._data_dir) return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action From ba86ba039543fa4cd00df33b7fc16d77b9594bc8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 07:21:08 -0400 Subject: [PATCH 02/19] island: Decouple Database service from services.post_breach_files Since Database.reset_db() calls ConfigService.init_config() which calls ConfigService.reset_config() which calls services.post_breach_files.remove_PBA_files(), it is redundant to call remove_PBA_files() from Database.reset_db(). Removing this call has the added benefit of reducing the coupling between the Database service and services.post_breach_files --- monkey/monkey_island/cc/services/database.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py index 2efd3643a..d0656f946 100644 --- a/monkey/monkey_island/cc/services/database.py +++ b/monkey/monkey_island/cc/services/database.py @@ -6,7 +6,6 @@ from monkey_island.cc.database import mongo 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 import ConfigService -from monkey_island.cc.services.post_breach_files import remove_PBA_files logger = logging.getLogger(__name__) @@ -18,7 +17,6 @@ class Database(object): @staticmethod def reset_db(): logger.info("Resetting database") - remove_PBA_files() # We can't drop system collections. [ Database.drop_collection(x) From a7f2e023b89aefed1182bba0f4a20905fc4e857a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 07:47:40 -0400 Subject: [PATCH 03/19] island: Wrap services/post_breach_files.py functions in a static class --- monkey/monkey_island/cc/services/config.py | 6 +- .../cc/services/post_breach_files.py | 72 ++++++++++--------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index d6b34e60b..4b41501b4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -6,10 +6,10 @@ import logging from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton -import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA +from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -191,7 +191,7 @@ class ConfigService: # PBA file upload happens on pba_file_upload endpoint and corresponding config options # are set there config_json = ConfigService._filter_none_values(config_json) - monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json) + PostBreachFilesService.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -229,7 +229,7 @@ class ConfigService: @staticmethod def reset_config(): - monkey_island.cc.services.post_breach_files.remove_PBA_files() + PostBreachFilesService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 626a2a56d..aa9065b56 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -14,40 +14,42 @@ PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] -def remove_PBA_files(): - if monkey_island.cc.services.config.ConfigService.get_config(): - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - if linux_filename: - remove_file(linux_filename) - if windows_filename: - remove_file(windows_filename) +class PostBreachFilesService: + @staticmethod + def remove_PBA_files(): + if monkey_island.cc.services.config.ConfigService.get_config(): + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) + if linux_filename: + PostBreachFilesService._remove_file(linux_filename) + if windows_filename: + PostBreachFilesService._remove_file(windows_filename) + @staticmethod + def _remove_file(file_name): + file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) -def remove_file(file_name): - file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - logger.error("Can't remove previously uploaded post breach files: %s" % e) - - -def set_config_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if monkey_island.cc.services.config.ConfigService.get_config(): - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename + @staticmethod + def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if monkey_island.cc.services.config.ConfigService.get_config(): + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename + config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename From ee19eed596a8b38fcc3ce379ad547aaf27cf4582 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:21:54 -0400 Subject: [PATCH 04/19] island: Decouple PostBreachFilesService from environment_singleton --- monkey/monkey_island/cc/main.py | 2 ++ monkey/monkey_island/cc/services/initialize.py | 9 +++++++++ monkey/monkey_island/cc/services/post_breach_files.py | 6 +++--- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/initialize.py diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 288baae67..5b3bfd794 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -26,6 +26,7 @@ from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 +from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup import setup # noqa: E402 @@ -39,6 +40,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P data_dir = env_singleton.env.get_config().data_dir_abs_path env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(data_dir) + initialize_services(data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py new file mode 100644 index 000000000..45301946f --- /dev/null +++ b/monkey/monkey_island/cc/services/initialize.py @@ -0,0 +1,9 @@ +from monkey_island.cc.services.post_breach_files import PostBreachFilesService + + +def initialize_services(data_dir): + initialize_post_breach_file_service(data_dir) + + +def initialize_post_breach_file_service(data_dir): + PostBreachFilesService.DATA_DIR = data_dir diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index aa9065b56..1224f6759 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -5,8 +5,6 @@ import monkey_island.cc.services.config __author__ = "VakarisZ" -import monkey_island.cc.environment.environment_singleton as env_singleton - logger = logging.getLogger(__name__) # Where to find file names in config @@ -15,6 +13,8 @@ PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] class PostBreachFilesService: + DATA_DIR = None + @staticmethod def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): @@ -31,7 +31,7 @@ class PostBreachFilesService: @staticmethod def _remove_file(file_name): - file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) + file_path = os.path.join(PostBreachFilesService.DATA_DIR, file_name) try: if os.path.exists(file_path): os.remove(file_path) From 4190797ca208d71154c6898353d57f651bbf9677 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:30:59 -0400 Subject: [PATCH 05/19] island: Add PostBreachFilesService.get_custom_pba_directory() --- monkey/monkey_island/cc/services/post_breach_files.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 1224f6759..d0c1b6a56 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -14,6 +14,7 @@ PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] class PostBreachFilesService: DATA_DIR = None + CUSTOM_PBA_DIRNAME = "custom_pbas" @staticmethod def remove_PBA_files(): @@ -31,13 +32,19 @@ class PostBreachFilesService: @staticmethod def _remove_file(file_name): - file_path = os.path.join(PostBreachFilesService.DATA_DIR, file_name) + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), file_name) try: if os.path.exists(file_path): os.remove(file_path) except OSError as e: logger.error("Can't remove previously uploaded post breach files: %s" % e) + @staticmethod + def get_custom_pba_directory(): + return os.path.join( + PostBreachFilesService.DATA_DIR, PostBreachFilesService.CUSTOM_PBA_DIRNAME + ) + @staticmethod def set_config_PBA_files(config_json): """ From be0f7ac88134db1020d487f6a7fe381b0f2d2ee6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:33:56 -0400 Subject: [PATCH 06/19] island: Decouple PBAFileDownload from environment_singleton --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/resources/pba_file_download.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a41b26e9a..269b9a5d3 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -155,7 +155,7 @@ def init_api_resources(api, data_dir): api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") api.add_resource(Log, "/api/log", "/api/log/") api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/") - api.add_resource(PBAFileDownload, "/api/pba/download/") + api.add_resource(PBAFileDownload, "/api/pba/download/") api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource( FileUpload, diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index 4bb409eec..ec2abecfe 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,7 +1,7 @@ import flask_restful from flask import send_from_directory -import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey_island.cc.services.post_breach_files import PostBreachFilesService __author__ = "VakarisZ" @@ -12,5 +12,6 @@ class PBAFileDownload(flask_restful.Resource): """ # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, path) + def get(self, filename): + custom_pba_dir = PostBreachFilesService.get_custom_pba_directory() + return send_from_directory(custom_pba_dir, filename) From ca65330e863eb91205f6d007751e4e0270a32f01 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:44:31 -0400 Subject: [PATCH 07/19] island: Create custom PBA directory on PostBreachFilesService init --- monkey/monkey_island/cc/resources/pba_file_upload.py | 4 ---- monkey/monkey_island/cc/services/config.py | 2 ++ monkey/monkey_island/cc/services/initialize.py | 2 +- monkey/monkey_island/cc/services/post_breach_files.py | 10 ++++++++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 6ae209a12..01cae595e 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -28,10 +28,6 @@ class FileUpload(flask_restful.Resource): File upload endpoint used to exchange files with filepond component on the front-end """ - def __init__(self): - # Create all directories on the way if they don't exist - Path(env_singleton.env.get_config().data_dir_abs_path).mkdir(parents=True, exist_ok=True) - @jwt_required def get(self, file_type): """ diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 4b41501b4..3dbdd9039 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,6 +9,8 @@ import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA + +# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 45301946f..564babcac 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -6,4 +6,4 @@ def initialize_services(data_dir): def initialize_post_breach_file_service(data_dir): - PostBreachFilesService.DATA_DIR = data_dir + PostBreachFilesService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index d0c1b6a56..a56c585de 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -1,6 +1,8 @@ import logging import os +from pathlib import Path +# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. import monkey_island.cc.services.config __author__ = "VakarisZ" @@ -16,6 +18,14 @@ class PostBreachFilesService: DATA_DIR = None CUSTOM_PBA_DIRNAME = "custom_pbas" + # TODO: A number of these services should be instance objects instead of + # static/singleton hybrids. At the moment, this requires invasive refactoring that's + # not a priority. + @classmethod + def initialize(cls, data_dir): + cls.DATA_DIR = data_dir + Path(cls.get_custom_pba_directory()).mkdir(mode=0o0700, parents=True, exist_ok=True) + @staticmethod def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): From 71029cb7f9ebdf49da694968e04362b40c5bd598 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:51:48 -0400 Subject: [PATCH 08/19] island: Decouple FileUpload resource from environment_singleton --- monkey/monkey_island/cc/resources/pba_file_upload.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 01cae595e..046e79efb 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -7,12 +7,12 @@ import flask_restful from flask import Response, request, send_from_directory from werkzeug.utils import secure_filename -import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.post_breach_files import ( PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH, + PostBreachFilesService, ) __author__ = "VakarisZ" @@ -40,7 +40,7 @@ class FileUpload(flask_restful.Resource): filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH)) else: filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH)) - return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, filename) + return send_from_directory(PostBreachFilesService.get_custom_pba_directory(), filename) @jwt_required def post(self, file_type): @@ -64,7 +64,7 @@ class FileUpload(flask_restful.Resource): """ filename = secure_filename(request_.files["filepond"].filename) file_path = ( - Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename).absolute() + Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename).absolute() ) request_.files["filepond"].save(str(file_path)) ConfigService.set_config_value( @@ -84,7 +84,7 @@ class FileUpload(flask_restful.Resource): ) filename = ConfigService.get_config_value(filename_path) if filename: - file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) + file_path = Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename) FileUpload._delete_file(file_path) ConfigService.set_config_value(filename_path, "") From 5742e85ff58e0f79dc64ae6652d39aaee9daa8ba Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:56:04 -0400 Subject: [PATCH 09/19] island: Consolidate duplicate delete post breach file functionality --- monkey/monkey_island/cc/resources/pba_file_upload.py | 12 +----------- .../monkey_island/cc/services/post_breach_files.py | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 046e79efb..25b54cb28 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,6 +1,5 @@ import copy import logging -import os from pathlib import Path import flask_restful @@ -84,16 +83,7 @@ class FileUpload(flask_restful.Resource): ) filename = ConfigService.get_config_value(filename_path) if filename: - file_path = Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename) - FileUpload._delete_file(file_path) + PostBreachFilesService.remove_file(filename) ConfigService.set_config_value(filename_path, "") return {} - - @staticmethod - def _delete_file(file_path): - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - LOG.error("Couldn't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index a56c585de..d7eeb9306 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -36,12 +36,12 @@ class PostBreachFilesService: PBA_LINUX_FILENAME_PATH ) if linux_filename: - PostBreachFilesService._remove_file(linux_filename) + PostBreachFilesService.remove_file(linux_filename) if windows_filename: - PostBreachFilesService._remove_file(windows_filename) + PostBreachFilesService.remove_file(windows_filename) @staticmethod - def _remove_file(file_name): + def remove_file(file_name): file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), file_name) try: if os.path.exists(file_path): From 4364a485612b803e5c20eda4b6bbff3b8577fc90 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 10:56:51 -0400 Subject: [PATCH 10/19] island: Simplify remove_PBA_files() --- .../cc/services/post_breach_files.py | 13 ++------ .../cc/services/test_post_breach_files.py | 30 +++++++++++++++++++ whitelist.py | 1 + 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 monkey/tests/monkey_island/cc/services/test_post_breach_files.py diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index d7eeb9306..7e2ce4e45 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -28,17 +28,8 @@ class PostBreachFilesService: @staticmethod def remove_PBA_files(): - if monkey_island.cc.services.config.ConfigService.get_config(): - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - if linux_filename: - PostBreachFilesService.remove_file(linux_filename) - if windows_filename: - PostBreachFilesService.remove_file(windows_filename) + for f in os.listdir(PostBreachFilesService.get_custom_pba_directory()): + PostBreachFilesService.remove_file(f) @staticmethod def remove_file(file_name): diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py new file mode 100644 index 000000000..9eda4af55 --- /dev/null +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -0,0 +1,30 @@ +import os + +import pytest + +from monkey_island.cc.services.post_breach_files import PostBreachFilesService + + +@pytest.fixture(autouse=True) +def custom_pba_directory(tmpdir): + PostBreachFilesService.initialize(tmpdir) + + +def create_custom_pba_file(filename): + assert os.path.isdir(PostBreachFilesService.get_custom_pba_directory()) + + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) + open(file_path, "a").close() + + +def test_remove_pba_files(): + create_custom_pba_file("linux_file") + create_custom_pba_file("windows_file") + + custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) + assert len(custom_pda_dir_contents) == 2 + + PostBreachFilesService.remove_PBA_files() + + custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) + assert len(custom_pda_dir_contents) == 0 diff --git a/whitelist.py b/whitelist.py index ad346ff0b..bd147220a 100644 --- a/whitelist.py +++ b/whitelist.py @@ -20,6 +20,7 @@ set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/act patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25) patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31) mock_home_env # unused variable (monkey/tests/monkey_island/cc/server_utils/test_island_logger.py:20) +custom_pba_directory # unused variable (monkey/tests/monkey_island/cc/services/test_post_breach_files.py:20) configure_resources # unused function (monkey/tests/monkey_island/cc/environment/test_environment.py:26) change_to_mongo_mock # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:9) uses_database # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:16) From ea82e86df57a1fb61d944aab7233abf388c87b38 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 11:07:48 -0400 Subject: [PATCH 11/19] island: Add tests for PostBreachFilesService --- monkey/test.py | 4 +++ .../cc/services/test_post_breach_files.py | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 monkey/test.py diff --git a/monkey/test.py b/monkey/test.py new file mode 100644 index 000000000..19ef00bd8 --- /dev/null +++ b/monkey/test.py @@ -0,0 +1,4 @@ +try: + raise Exception("Ex") +except Exception as ex: + print(f"F: {ex}") diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index 9eda4af55..7d3431cfd 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -5,6 +5,10 @@ import pytest from monkey_island.cc.services.post_breach_files import PostBreachFilesService +def raise_(ex): + raise ex + + @pytest.fixture(autouse=True) def custom_pba_directory(tmpdir): PostBreachFilesService.initialize(tmpdir) @@ -28,3 +32,29 @@ def test_remove_pba_files(): custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) assert len(custom_pda_dir_contents) == 0 + + +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") +def test_custom_pba_dir_permissions(): + st = os.stat(PostBreachFilesService.get_custom_pba_directory()) + + assert st.st_mode == 0o40700 + + +def test_remove_failure(monkeypatch): + monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied"))) + + try: + create_custom_pba_file("windows_file") + PostBreachFilesService.remove_PBA_files() + except Exception as ex: + pytest.fail(f"Unxepected exception: {ex}") + + +def test_remove_nonexistant_file(monkeypatch): + monkeypatch.setattr(os, "remove", lambda x: raise_(FileNotFoundError("FileNotFound"))) + + try: + PostBreachFilesService.remove_file("/nonexistant/file") + except Exception as ex: + pytest.fail(f"Unxepected exception: {ex}") From 947644152601fbecbb71ed603edbd2de9c8a13ea Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 11:14:39 -0400 Subject: [PATCH 12/19] island: Remove circular dep btw ConfigService and PostBreachFilesService --- monkey/monkey_island/cc/services/config.py | 23 +++++++++++++++---- .../cc/services/post_breach_files.py | 21 +---------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 3dbdd9039..c0fb3a20c 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,9 +9,11 @@ import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA - -# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. -from monkey_island.cc.services.post_breach_files import PostBreachFilesService +from monkey_island.cc.services.post_breach_files import ( + PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH, + PostBreachFilesService, +) from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -193,7 +195,7 @@ class ConfigService: # PBA file upload happens on pba_file_upload endpoint and corresponding config options # are set there config_json = ConfigService._filter_none_values(config_json) - PostBreachFilesService.set_config_PBA_files(config_json) + ConfigService.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -204,6 +206,19 @@ class ConfigService: logger.info("monkey config was updated") return True + @staticmethod + def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if ConfigService.get_config(): + linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + + config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename + config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename + @staticmethod def init_default_config(): if ConfigService.default_config is None: diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 7e2ce4e45..1a8407564 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -2,16 +2,13 @@ import logging import os from pathlib import Path -# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. -import monkey_island.cc.services.config - __author__ = "VakarisZ" logger = logging.getLogger(__name__) # Where to find file names in config -PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] +PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] class PostBreachFilesService: @@ -45,19 +42,3 @@ class PostBreachFilesService: return os.path.join( PostBreachFilesService.DATA_DIR, PostBreachFilesService.CUSTOM_PBA_DIRNAME ) - - @staticmethod - def set_config_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if monkey_island.cc.services.config.ConfigService.get_config(): - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename From e3449d17c7d7606544c5a9a703e153d727440353 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:07:04 -0400 Subject: [PATCH 13/19] Remove file that was accidentally added --- monkey/test.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 monkey/test.py diff --git a/monkey/test.py b/monkey/test.py deleted file mode 100644 index 19ef00bd8..000000000 --- a/monkey/test.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - raise Exception("Ex") -except Exception as ex: - print(f"F: {ex}") From db142859349fe88b04d805a6fa0544e3345cd437 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:10:01 -0400 Subject: [PATCH 14/19] island: Add `dir_is_empty()` to clarify intent of `test_remove_pba_files()` --- .../cc/services/test_post_breach_files.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index 7d3431cfd..cc1c80e1f 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -25,13 +25,14 @@ def test_remove_pba_files(): create_custom_pba_file("linux_file") create_custom_pba_file("windows_file") - custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) - assert len(custom_pda_dir_contents) == 2 - + assert not dir_is_empty(PostBreachFilesService.get_custom_pba_directory()) PostBreachFilesService.remove_PBA_files() + assert dir_is_empty(PostBreachFilesService.get_custom_pba_directory()) - custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) - assert len(custom_pda_dir_contents) == 0 + +def dir_is_empty(dir_path): + dir_contents = os.listdir(dir_path) + return len(dir_contents) == 0 @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") From f86bc7f9432f826a544f437d5db745c86b30f244 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:35:46 -0400 Subject: [PATCH 15/19] island: Move run_local_monkey() to its own service --- monkey/monkey_island/cc/app.py | 13 ++-- monkey/monkey_island/cc/main.py | 6 +- .../monkey_island/cc/resources/local_run.py | 56 +----------------- .../monkey_island/cc/services/initialize.py | 6 +- .../cc/services/run_local_monkey.py | 59 +++++++++++++++++++ 5 files changed, 70 insertions(+), 70 deletions(-) create mode 100644 monkey/monkey_island/cc/services/run_local_monkey.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 269b9a5d3..9636a62a0 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -117,19 +117,14 @@ def init_app_url_rules(app): app.add_url_rule("/", "serve_static_file", serve_static_file) -def init_api_resources(api, data_dir): +def init_api_resources(api): api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") api.add_resource(Environment, "/api/environment") api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") api.add_resource(Bootloader, "/api/bootloader/") - api.add_resource( - LocalRun, - "/api/local-monkey", - "/api/local-monkey/", - resource_class_kwargs={"data_dir": data_dir}, - ) + api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" @@ -178,7 +173,7 @@ def init_api_resources(api, data_dir): api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry") -def init_app(mongo_url, data_dir): +def init_app(mongo_url): app = Flask(__name__) api = flask_restful.Api(app) @@ -187,6 +182,6 @@ def init_app(mongo_url, data_dir): init_app_config(app, mongo_url) init_app_services(app) init_app_url_rules(app) - init_api_resources(api, data_dir) + init_api_resources(api) return app diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 5b3bfd794..4bdc764c3 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -48,17 +48,17 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P ) bootloader_server_thread.start() - start_island_server(should_setup_only, data_dir) + start_island_server(should_setup_only) bootloader_server_thread.join() -def start_island_server(should_setup_only, data_dir): +def start_island_server(should_setup_only): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) populate_exporter_list() - app = init_app(mongo_url, data_dir) + app = init_app(mongo_url) crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 91c02a4f7..40a5fb8b0 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -1,67 +1,15 @@ import json -import logging -import os -import sys -from shutil import copyfile import flask_restful from flask import jsonify, make_response, request -import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.models import Monkey from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.resources.monkey_download import get_monkey_executable -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.utils.network_utils import local_ip_addresses - -__author__ = "Barak" - -logger = logging.getLogger(__name__) - - -def run_local_monkey(dest_dir): - import platform - import stat - import subprocess - - # get the monkey executable suitable to run on the server - result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) - if not result: - return False, "OS Type not found" - - src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(dest_dir, result["filename"]) - - # copy the executable to temp path (don't run the monkey from its current location as it may - # delete itself) - try: - copyfile(src_path, dest_path) - os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) - except Exception as exc: - logger.error("Copy file failed", exc_info=True) - return False, "Copy file failed: %s" % exc - - # run the monkey - try: - args = [ - '"%s" m0nk3y -s %s:%s' - % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) - ] - if sys.platform == "win32": - args = "".join(args) - subprocess.Popen(args, cwd=dest_dir, shell=True).pid - except Exception as exc: - logger.error("popen failed", exc_info=True) - return False, "popen failed: %s" % exc - - return True, "" +from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService class LocalRun(flask_restful.Resource): - def __init__(self, data_dir): - self._data_dir = data_dir - @jwt_required def get(self): NodeService.update_dead_monkeys() @@ -77,7 +25,7 @@ class LocalRun(flask_restful.Resource): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = run_local_monkey(self._data_dir) + local_run = RunLocalMonkeyService.run_local_monkey() return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 564babcac..93eb66d3f 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -1,9 +1,7 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService +from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService def initialize_services(data_dir): - initialize_post_breach_file_service(data_dir) - - -def initialize_post_breach_file_service(data_dir): PostBreachFilesService.initialize(data_dir) + RunLocalMonkeyService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py new file mode 100644 index 000000000..ce08ff615 --- /dev/null +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -0,0 +1,59 @@ +import logging +import os +import platform +import stat +import subprocess +import sys +from shutil import copyfile + +import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey_island.cc.resources.monkey_download import get_monkey_executable +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.services.utils.network_utils import local_ip_addresses + +logger = logging.getLogger(__name__) + + +class RunLocalMonkeyService: + DATA_DIR = None + + # TODO: A number of these services should be instance objects instead of + # static/singleton hybrids. At the moment, this requires invasive refactoring that's + # not a priority. + @classmethod + def initialize(cls, data_dir): + cls.DATA_DIR = data_dir + + @staticmethod + def run_local_monkey(): + # get the monkey executable suitable to run on the server + result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) + if not result: + return False, "OS Type not found" + + src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) + dest_path = os.path.join(RunLocalMonkeyService.DATA_DIR, result["filename"]) + + # copy the executable to temp path (don't run the monkey from its current location as it may + # delete itself) + try: + copyfile(src_path, dest_path) + os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) + except Exception as exc: + logger.error("Copy file failed", exc_info=True) + return False, "Copy file failed: %s" % exc + + # run the monkey + try: + args = [ + '"%s" m0nk3y -s %s:%s' + % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) + ] + if sys.platform == "win32": + args = "".join(args) + subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR, shell=True).pid + except Exception as exc: + logger.error("popen failed", exc_info=True) + return False, "popen failed: %s" % exc + + return True, "" From 2485c85d59049842b2b254c35367f76b95bd273b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:42:12 -0400 Subject: [PATCH 16/19] island: Don't use `shell=True` when running local monkey --- .../monkey_island/cc/services/run_local_monkey.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index ce08ff615..175bd58a9 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -3,7 +3,6 @@ import os import platform import stat import subprocess -import sys from shutil import copyfile import monkey_island.cc.environment.environment_singleton as env_singleton @@ -45,13 +44,11 @@ class RunLocalMonkeyService: # run the monkey try: - args = [ - '"%s" m0nk3y -s %s:%s' - % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) - ] - if sys.platform == "win32": - args = "".join(args) - subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR, shell=True).pid + ip = local_ip_addresses()[0] + port = env_singleton.env.get_island_port() + + args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"] + subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR) except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc From 253588b3acabd001cd8e83cd8fec8f60feb52acb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 09:03:13 -0400 Subject: [PATCH 17/19] island: Move PBA filename paths to config_value_paths.py --- monkey/common/config_value_paths.py | 2 ++ monkey/monkey_island/cc/resources/pba_file_upload.py | 7 ++----- monkey/monkey_island/cc/services/config.py | 12 +++++------- .../monkey_island/cc/services/post_breach_files.py | 6 ------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/monkey/common/config_value_paths.py b/monkey/common/config_value_paths.py index 4fc94ea4e..db10fb9e1 100644 --- a/monkey/common/config_value_paths.py +++ b/monkey/common/config_value_paths.py @@ -11,3 +11,5 @@ SUBNET_SCAN_LIST_PATH = ["basic_network", "scope", "subnet_scan_list"] LOCAL_NETWORK_SCAN_PATH = ["basic_network", "scope", "local_network_scan"] LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"] NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"] +PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] +PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 25b54cb28..369bc3c42 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -6,13 +6,10 @@ import flask_restful from flask import Response, request, send_from_directory from werkzeug.utils import secure_filename +from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.post_breach_files import ( - PBA_LINUX_FILENAME_PATH, - PBA_WINDOWS_FILENAME_PATH, - PostBreachFilesService, -) +from monkey_island.cc.services.post_breach_files import PostBreachFilesService __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index c0fb3a20c..7c7429756 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,11 +9,7 @@ import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA -from monkey_island.cc.services.post_breach_files import ( - PBA_LINUX_FILENAME_PATH, - PBA_WINDOWS_FILENAME_PATH, - PostBreachFilesService, -) +from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -24,6 +20,8 @@ from common.config_value_paths import ( LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, PASSWORD_LIST_PATH, + PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH, SSH_KEYS_PATH, STARTED_ON_ISLAND_PATH, USER_LIST_PATH, @@ -216,8 +214,8 @@ class ConfigService: linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename + ConfigService.set_config_value(PBA_LINUX_FILENAME_PATH, linux_filename) + ConfigService.set_config_value(PBA_WINDOWS_FILENAME_PATH, windows_filename) @staticmethod def init_default_config(): diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 1a8407564..06d2ffe48 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -2,14 +2,8 @@ import logging import os from pathlib import Path -__author__ = "VakarisZ" - logger = logging.getLogger(__name__) -# Where to find file names in config -PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] -PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] - class PostBreachFilesService: DATA_DIR = None From 79eb7442ae60490e53755dedb7029c923ef844d9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 09:49:58 -0400 Subject: [PATCH 18/19] island: Move the specifics of saving pba files to pba service --- .../cc/resources/pba_file_upload.py | 19 +++++++++++-------- .../cc/services/post_breach_files.py | 6 ++++++ .../cc/services/test_post_breach_files.py | 16 ++++++++++++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 369bc3c42..39da8324f 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,9 +1,9 @@ import copy import logging -from pathlib import Path import flask_restful from flask import Response, request, send_from_directory +from werkzeug.datastructures import FileStorage from werkzeug.utils import secure_filename from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH @@ -45,27 +45,30 @@ class FileUpload(flask_restful.Resource): :param file_type: Type indicates which file was received, linux or windows :return: Returns flask response object with uploaded file's filename """ - filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) + filename = FileUpload.upload_pba_file( + request.files["filepond"], (file_type == LINUX_PBA_TYPE) + ) response = Response(response=filename, status=200, mimetype="text/plain") return response @staticmethod - def upload_pba_file(request_, is_linux=True): + def upload_pba_file(file_storage: FileStorage, is_linux=True): """ Uploads PBA file to island's file system :param request_: Request object containing PBA file :param is_linux: Boolean indicating if this file is for windows or for linux :return: filename string """ - filename = secure_filename(request_.files["filepond"].filename) - file_path = ( - Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename).absolute() - ) - request_.files["filepond"].save(str(file_path)) + filename = secure_filename(file_storage.filename) + file_contents = file_storage.read() + + PostBreachFilesService.save_file(filename, file_contents) + ConfigService.set_config_value( (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename ) + return filename @jwt_required diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 06d2ffe48..94569db37 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -17,6 +17,12 @@ class PostBreachFilesService: cls.DATA_DIR = data_dir Path(cls.get_custom_pba_directory()).mkdir(mode=0o0700, parents=True, exist_ok=True) + @staticmethod + def save_file(filename: str, file_contents: bytes): + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) + with open(file_path, "wb") as f: + f.write(file_contents) + @staticmethod def remove_PBA_files(): for f in os.listdir(PostBreachFilesService.get_custom_pba_directory()): diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index cc1c80e1f..3c3fe82fe 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -15,10 +15,7 @@ def custom_pba_directory(tmpdir): def create_custom_pba_file(filename): - assert os.path.isdir(PostBreachFilesService.get_custom_pba_directory()) - - file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) - open(file_path, "a").close() + PostBreachFilesService.save_file(filename, b"") def test_remove_pba_files(): @@ -59,3 +56,14 @@ def test_remove_nonexistant_file(monkeypatch): PostBreachFilesService.remove_file("/nonexistant/file") except Exception as ex: pytest.fail(f"Unxepected exception: {ex}") + + +def test_save_file(): + FILE_NAME = "test_file" + FILE_CONTENTS = b"hello" + PostBreachFilesService.save_file(FILE_NAME, FILE_CONTENTS) + + expected_file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), FILE_NAME) + + assert os.path.isfile(expected_file_path) + assert FILE_CONTENTS == open(expected_file_path, "rb").read() From b8d4452e70f702c14ff8fd822cdd2a9711b0236a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 12:09:46 -0400 Subject: [PATCH 19/19] island: Rename RunLocalMonkeyService -> LocalMonkeyRunService --- monkey/monkey_island/cc/resources/local_run.py | 4 ++-- monkey/monkey_island/cc/services/initialize.py | 4 ++-- monkey/monkey_island/cc/services/run_local_monkey.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 40a5fb8b0..49517dbdb 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -6,7 +6,7 @@ from flask import jsonify, make_response, request from monkey_island.cc.models import Monkey from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService +from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService class LocalRun(flask_restful.Resource): @@ -25,7 +25,7 @@ class LocalRun(flask_restful.Resource): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = RunLocalMonkeyService.run_local_monkey() + local_run = LocalMonkeyRunService.run_local_monkey() return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 93eb66d3f..6ff0d2706 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -1,7 +1,7 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService -from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService +from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService def initialize_services(data_dir): PostBreachFilesService.initialize(data_dir) - RunLocalMonkeyService.initialize(data_dir) + LocalMonkeyRunService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index 175bd58a9..e7e18045a 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -13,7 +13,7 @@ from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) -class RunLocalMonkeyService: +class LocalMonkeyRunService: DATA_DIR = None # TODO: A number of these services should be instance objects instead of @@ -31,7 +31,7 @@ class RunLocalMonkeyService: return False, "OS Type not found" src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(RunLocalMonkeyService.DATA_DIR, result["filename"]) + dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) @@ -48,7 +48,7 @@ class RunLocalMonkeyService: port = env_singleton.env.get_island_port() args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"] - subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR) + subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR) except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc