diff --git a/monkey_island/cc/encryptor.py b/monkey_island/cc/encryptor.py new file mode 100644 index 000000000..90009d1b0 --- /dev/null +++ b/monkey_island/cc/encryptor.py @@ -0,0 +1,51 @@ +import base64 +import os + +from Crypto import Random +from Crypto.Cipher import AES + +__author__ = "itay.mizeretz" + + +class Encryptor: + _BLOCK_SIZE = 32 + _DB_PASSWORD_FILENAME = "mongo_key.bin" + + def __init__(self): + self._load_key() + + def _init_key(self): + self._cipher_key = Random.new().read(self._BLOCK_SIZE) + with open(self._DB_PASSWORD_FILENAME, 'wb') as f: + f.write(self._cipher_key) + + def _load_existing_key(self): + with open(self._DB_PASSWORD_FILENAME, 'rb') as f: + self._cipher_key = f.read() + + def _load_key(self): + if os.path.exists(self._DB_PASSWORD_FILENAME): + self._load_existing_key() + else: + self._init_key() + + def _pad(self, message): + return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( + self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) + + def _unpad(self, message): + return message[0:-ord(message[len(message) - 1])] + + def enc(self, message): + cipher_iv = Random.new().read(AES.block_size) + cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) + return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message))) + + def dec(self, enc_message): + enc_message = base64.b64decode(enc_message) + cipher_iv = enc_message[0:AES.block_size] + cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) + return self._unpad(cipher.decrypt(enc_message[AES.block_size:])) + + +encryptor = Encryptor() diff --git a/monkey_island/cc/resources/monkey.py b/monkey_island/cc/resources/monkey.py index d344949bc..997e2a72e 100644 --- a/monkey_island/cc/resources/monkey.py +++ b/monkey_island/cc/resources/monkey.py @@ -65,7 +65,7 @@ class Monkey(flask_restful.Resource): # if new monkey telem, change config according to "new monkeys" config. db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) if not db_monkey: - new_config = ConfigService.get_flat_config() + new_config = ConfigService.get_flat_config(False, True) monkey_json['config'] = monkey_json.get('config', {}) monkey_json['config'].update(new_config) else: diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 0bd30db3f..db4d17167 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -12,7 +12,7 @@ __author__ = 'Barak' class MonkeyConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config()) + return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True)) @jwt_required() def post(self): @@ -20,5 +20,5 @@ class MonkeyConfiguration(flask_restful.Resource): if config_json.has_key('reset'): ConfigService.reset_config() else: - ConfigService.update_config(config_json) + ConfigService.update_config(config_json, should_encrypt=True) return self.get() diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index e1b17ac9a..d7b21035c 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -12,6 +12,7 @@ from cc.database import mongo from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService +from cc.encryptor import encryptor __author__ = 'Barak' @@ -112,6 +113,8 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_exploit_telemetry(telemetry_json): edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) + Telemetry.encrypt_exploit_creds(telemetry_json) + new_exploit = copy.deepcopy(telemetry_json['data']) new_exploit.pop('machine') @@ -166,25 +169,49 @@ class Telemetry(flask_restful.Resource): def process_system_info_telemetry(telemetry_json): if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] - for user in creds: - ConfigService.creds_add_username(user) - if 'password' in creds[user]: - ConfigService.creds_add_password(creds[user]['password']) - if 'lm_hash' in creds[user]: - ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) - if 'ntlm_hash' in creds[user]: - ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - - for user in creds: - if -1 != user.find('.'): - new_user = user.replace('.', ',') - creds[new_user] = creds.pop(user) + Telemetry.encrypt_system_info_creds(creds) + Telemetry.add_system_info_creds_to_config(creds) + Telemetry.replace_user_dot_with_comma(creds) @staticmethod def process_trace_telemetry(telemetry_json): # Nothing to do return + @staticmethod + def replace_user_dot_with_comma(creds): + for user in creds: + if -1 != user.find('.'): + new_user = user.replace('.', ',') + creds[new_user] = creds.pop(user) + + @staticmethod + def encrypt_system_info_creds(creds): + for user in creds: + for field in ['password', 'lm_hash', 'ntlm_hash']: + if field in creds[user]: + creds[user][field] = encryptor.enc(creds[user][field]) + + @staticmethod + def add_system_info_creds_to_config(creds): + for user in creds: + ConfigService.creds_add_username(user) + if 'password' in creds[user]: + ConfigService.creds_add_password(creds[user]['password']) + if 'lm_hash' in creds[user]: + ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) + if 'ntlm_hash' in creds[user]: + ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + + @staticmethod + def encrypt_exploit_creds(telemetry_json): + attempts = telemetry_json['data']['attempts'] + for i in range(len(attempts)): + for field in ['password', 'lm_hash', 'ntlm_hash']: + credential = attempts[i][field] + if len(credential) > 0: + attempts[i][field] = encryptor.enc(credential) + TELEM_PROCESS_DICT = \ { diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 34435c415..bd6b79ca6 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,9 @@ -from cc.database import mongo +import copy +import functools from jsonschema import Draft4Validator, validators +from cc.database import mongo +from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses @@ -17,60 +20,60 @@ SCHEMA = { "type": "string", "anyOf": [ { - "type": "string", - "enum": [ - "SmbExploiter" - ], - "title": "SMB Exploiter" + "type": "string", + "enum": [ + "SmbExploiter" + ], + "title": "SMB Exploiter" }, { - "type": "string", - "enum": [ - "WmiExploiter" - ], - "title": "WMI Exploiter" + "type": "string", + "enum": [ + "WmiExploiter" + ], + "title": "WMI Exploiter" }, { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)" + "type": "string", + "enum": [ + "RdpExploiter" + ], + "title": "RDP Exploiter (UNSAFE)" }, { - "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], - "title": "MS08-067 Exploiter (UNSAFE)" + "type": "string", + "enum": [ + "Ms08_067_Exploiter" + ], + "title": "MS08-067 Exploiter (UNSAFE)" }, { - "type": "string", - "enum": [ - "SSHExploiter" - ], - "title": "SSH Exploiter" + "type": "string", + "enum": [ + "SSHExploiter" + ], + "title": "SSH Exploiter" }, { - "type": "string", - "enum": [ - "ShellShockExploiter" - ], - "title": "ShellShock Exploiter" + "type": "string", + "enum": [ + "ShellShockExploiter" + ], + "title": "ShellShock Exploiter" }, { - "type": "string", - "enum": [ - "SambaCryExploiter" - ], - "title": "SambaCry Exploiter" + "type": "string", + "enum": [ + "SambaCryExploiter" + ], + "title": "SambaCry Exploiter" }, { - "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], - "title": "ElasticGroovy Exploiter" + "type": "string", + "enum": [ + "ElasticGroovyExploiter" + ], + "title": "ElasticGroovy Exploiter" }, ] }, @@ -79,46 +82,46 @@ SCHEMA = { "type": "string", "anyOf": [ { - "type": "string", - "enum": [ - "SMBFinger" - ], - "title": "SMBFinger" + "type": "string", + "enum": [ + "SMBFinger" + ], + "title": "SMBFinger" }, { - "type": "string", - "enum": [ - "SSHFinger" - ], - "title": "SSHFinger" + "type": "string", + "enum": [ + "SSHFinger" + ], + "title": "SSHFinger" }, { - "type": "string", - "enum": [ - "PingScanner" - ], - "title": "PingScanner" + "type": "string", + "enum": [ + "PingScanner" + ], + "title": "PingScanner" }, { - "type": "string", - "enum": [ - "HTTPFinger" - ], - "title": "HTTPFinger" + "type": "string", + "enum": [ + "HTTPFinger" + ], + "title": "HTTPFinger" }, { - "type": "string", - "enum": [ - "MySQLFinger" - ], - "title": "MySQLFinger" + "type": "string", + "enum": [ + "MySQLFinger" + ], + "title": "MySQLFinger" }, { - "type": "string", - "enum": [ - "ElasticFinger" - ], - "title": "ElasticFinger" + "type": "string", + "enum": [ + "ElasticFinger" + ], + "title": "ElasticFinger" } ] } @@ -800,29 +803,56 @@ SCHEMA = { } } +ENCRYPTED_CONFIG_ARRAYS = \ + [ + ['basic', 'credentials', 'exploit_password_list'], + ['internal', 'exploits', 'exploit_lm_hash_list'], + ['internal', 'exploits', 'exploit_ntlm_hash_list'] + ] + class ConfigService: + default_config = None + def __init__(self): pass @staticmethod - def get_config(is_initial_config=False): + def get_config(is_initial_config=False, should_decrypt=True): + """ + Gets the entire global config. + :param is_initial_config: If True, the initial config will be returned instead of the current config. + :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :return: The entire global config. + """ config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} for field in ('name', '_id'): config.pop(field, None) + if should_decrypt and len(config) > 0: + ConfigService.decrypt_config(config) return config @staticmethod - def get_config_value(config_key_as_arr, is_initial_config=False): - config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr) + def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True): + """ + Get a specific config value. + :param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', 'exploit_password_list']. + :param is_initial_config: If True, returns the value of the initial config instead of the current config. + :param should_decrypt: If True, the value of the config key will be decrypted + (if it's in the list of encrypted config values). + :return: The value of the requested config key. + """ + config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: config = config[config_key_part] + if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS): + config = [encryptor.dec(x) for x in config] return config @staticmethod - def get_flat_config(is_initial_config=False): - config_json = ConfigService.get_config(is_initial_config) + def get_flat_config(is_initial_config=False, should_decrypt=True): + config_json = ConfigService.get_config(is_initial_config, should_decrypt) flat_config_json = {} for i in config_json: for j in config_json[i]: @@ -866,27 +896,38 @@ class ConfigService: ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) @staticmethod - def update_config(config_json): + def update_config(config_json, should_encrypt): + if should_encrypt: + ConfigService.encrypt_config(config_json) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) @staticmethod - def get_default_config(): - defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) - config = {} - defaultValidatingDraft4Validator(SCHEMA).validate(config) + def init_default_config(): + if ConfigService.default_config is None: + defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) + config = {} + defaultValidatingDraft4Validator(SCHEMA).validate(config) + ConfigService.default_config = config + + @staticmethod + def get_default_config(should_encrypt=False): + ConfigService.init_default_config() + config = copy.deepcopy(ConfigService.default_config) + if should_encrypt: + ConfigService.encrypt_config(config) return config @staticmethod def init_config(): - if ConfigService.get_config() != {}: + if ConfigService.get_config(should_decrypt=False) != {}: return ConfigService.reset_config() @staticmethod def reset_config(): - config = ConfigService.get_default_config() + config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) - ConfigService.update_config(config) + ConfigService.update_config(config, should_encrypt=False) @staticmethod def set_server_ips_in_config(config): @@ -928,3 +969,21 @@ class ConfigService: return validators.extend( validator_class, {"properties": set_defaults}, ) + + @staticmethod + def decrypt_config(config): + ConfigService._encrypt_or_decrypt_config(config, True) + + @staticmethod + def encrypt_config(config): + ConfigService._encrypt_or_decrypt_config(config, False) + + @staticmethod + def _encrypt_or_decrypt_config(config, is_decrypt=False): + for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: + config_arr = config + for config_key_part in config_arr_as_array: + config_arr = config_arr[config_key_part] + + for i in range(len(config_arr)): + config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index c197c55f3..cbef9d973 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -293,19 +293,19 @@ class ReportService: @staticmethod def get_config_users(): - return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True) + return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True, True) @staticmethod def get_config_passwords(): - return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True) + return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True, True) @staticmethod def get_config_exploits(): exploits_config_value = ['exploits', 'general', 'exploiter_classes'] - default_exploits = ConfigService.get_default_config() + default_exploits = ConfigService.get_default_config(False) for namespace in exploits_config_value: default_exploits = default_exploits[namespace] - exploits = ConfigService.get_config_value(exploits_config_value, True) + exploits = ConfigService.get_config_value(exploits_config_value, True, True) if exploits == default_exploits: return ['default'] @@ -315,13 +315,13 @@ class ReportService: @staticmethod def get_config_ips(): - if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True) != 'FixedRange': + if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True, True) != 'FixedRange': return [] - return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True) + return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True, True) @staticmethod def get_config_scan(): - return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True) + return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True, True) @staticmethod def get_issues_overview(issues, config_users, config_passwords): diff --git a/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey_island/deb-package/monkey_island_pip_requirements.txt index 4b4e9d523..446414ecf 100644 --- a/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -13,4 +13,5 @@ jsonschema netifaces ipaddress enum34 +PyCrypto virtualenv \ No newline at end of file diff --git a/monkey_island/requirements.txt b/monkey_island/requirements.txt index 6aea32b84..29c364c9f 100644 --- a/monkey_island/requirements.txt +++ b/monkey_island/requirements.txt @@ -12,4 +12,5 @@ Flask-JWT jsonschema netifaces ipaddress -enum34 \ No newline at end of file +enum34 +PyCrypto