Merge pull request #86 from guardicore/feature/secure-island-db

Feature/secure island db
This commit is contained in:
itaymmguardicore 2018-03-08 20:03:40 +02:00 committed by GitHub
commit 1a3ca06ce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 108 deletions

View File

@ -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()

View File

@ -65,7 +65,7 @@ class Monkey(flask_restful.Resource):
# if new monkey telem, change config according to "new monkeys" config. # if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
if not db_monkey: 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'] = monkey_json.get('config', {})
monkey_json['config'].update(new_config) monkey_json['config'].update(new_config)
else: else:

View File

@ -12,7 +12,7 @@ __author__ = 'Barak'
class MonkeyConfiguration(flask_restful.Resource): class MonkeyConfiguration(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self): 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() @jwt_required()
def post(self): def post(self):
@ -20,5 +20,5 @@ class MonkeyConfiguration(flask_restful.Resource):
if config_json.has_key('reset'): if config_json.has_key('reset'):
ConfigService.reset_config() ConfigService.reset_config()
else: else:
ConfigService.update_config(config_json) ConfigService.update_config(config_json, should_encrypt=True)
return self.get() return self.get()

View File

@ -12,6 +12,7 @@ from cc.database import mongo
from cc.services.config import ConfigService from cc.services.config import ConfigService
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.encryptor import encryptor
__author__ = 'Barak' __author__ = 'Barak'
@ -112,6 +113,8 @@ class Telemetry(flask_restful.Resource):
@staticmethod @staticmethod
def process_exploit_telemetry(telemetry_json): def process_exploit_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_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 = copy.deepcopy(telemetry_json['data'])
new_exploit.pop('machine') new_exploit.pop('machine')
@ -166,25 +169,49 @@ class Telemetry(flask_restful.Resource):
def process_system_info_telemetry(telemetry_json): def process_system_info_telemetry(telemetry_json):
if 'credentials' in telemetry_json['data']: if 'credentials' in telemetry_json['data']:
creds = telemetry_json['data']['credentials'] creds = telemetry_json['data']['credentials']
for user in creds: Telemetry.encrypt_system_info_creds(creds)
ConfigService.creds_add_username(user) Telemetry.add_system_info_creds_to_config(creds)
if 'password' in creds[user]: Telemetry.replace_user_dot_with_comma(creds)
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)
@staticmethod @staticmethod
def process_trace_telemetry(telemetry_json): def process_trace_telemetry(telemetry_json):
# Nothing to do # Nothing to do
return 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 = \ TELEM_PROCESS_DICT = \
{ {

View File

@ -1,6 +1,9 @@
from cc.database import mongo import copy
import functools
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
from cc.database import mongo
from cc.encryptor import encryptor
from cc.environment.environment import env from cc.environment.environment import env
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
@ -17,60 +20,60 @@ SCHEMA = {
"type": "string", "type": "string",
"anyOf": [ "anyOf": [
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"SmbExploiter" "SmbExploiter"
], ],
"title": "SMB Exploiter" "title": "SMB Exploiter"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"WmiExploiter" "WmiExploiter"
], ],
"title": "WMI Exploiter" "title": "WMI Exploiter"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"RdpExploiter" "RdpExploiter"
], ],
"title": "RDP Exploiter (UNSAFE)" "title": "RDP Exploiter (UNSAFE)"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"Ms08_067_Exploiter" "Ms08_067_Exploiter"
], ],
"title": "MS08-067 Exploiter (UNSAFE)" "title": "MS08-067 Exploiter (UNSAFE)"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"SSHExploiter" "SSHExploiter"
], ],
"title": "SSH Exploiter" "title": "SSH Exploiter"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"ShellShockExploiter" "ShellShockExploiter"
], ],
"title": "ShellShock Exploiter" "title": "ShellShock Exploiter"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"SambaCryExploiter" "SambaCryExploiter"
], ],
"title": "SambaCry Exploiter" "title": "SambaCry Exploiter"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"ElasticGroovyExploiter" "ElasticGroovyExploiter"
], ],
"title": "ElasticGroovy Exploiter" "title": "ElasticGroovy Exploiter"
}, },
] ]
}, },
@ -79,46 +82,46 @@ SCHEMA = {
"type": "string", "type": "string",
"anyOf": [ "anyOf": [
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"SMBFinger" "SMBFinger"
], ],
"title": "SMBFinger" "title": "SMBFinger"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"SSHFinger" "SSHFinger"
], ],
"title": "SSHFinger" "title": "SSHFinger"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"PingScanner" "PingScanner"
], ],
"title": "PingScanner" "title": "PingScanner"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"HTTPFinger" "HTTPFinger"
], ],
"title": "HTTPFinger" "title": "HTTPFinger"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"MySQLFinger" "MySQLFinger"
], ],
"title": "MySQLFinger" "title": "MySQLFinger"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": [
"ElasticFinger" "ElasticFinger"
], ],
"title": "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: class ConfigService:
default_config = None
def __init__(self): def __init__(self):
pass pass
@staticmethod @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 {} config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
for field in ('name', '_id'): for field in ('name', '_id'):
config.pop(field, None) config.pop(field, None)
if should_decrypt and len(config) > 0:
ConfigService.decrypt_config(config)
return config return config
@staticmethod @staticmethod
def get_config_value(config_key_as_arr, is_initial_config=False): def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True):
config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr) """
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}) 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: for config_key_part in config_key_as_arr:
config = config[config_key_part] 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 return config
@staticmethod @staticmethod
def get_flat_config(is_initial_config=False): def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config) config_json = ConfigService.get_config(is_initial_config, should_decrypt)
flat_config_json = {} flat_config_json = {}
for i in config_json: for i in config_json:
for j in config_json[i]: 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) ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
@staticmethod @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) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
@staticmethod @staticmethod
def get_default_config(): def init_default_config():
defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator) if ConfigService.default_config is None:
config = {} defaultValidatingDraft4Validator = ConfigService._extend_config_with_default(Draft4Validator)
defaultValidatingDraft4Validator(SCHEMA).validate(config) 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 return config
@staticmethod @staticmethod
def init_config(): def init_config():
if ConfigService.get_config() != {}: if ConfigService.get_config(should_decrypt=False) != {}:
return return
ConfigService.reset_config() ConfigService.reset_config()
@staticmethod @staticmethod
def reset_config(): def reset_config():
config = ConfigService.get_default_config() config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config) ConfigService.set_server_ips_in_config(config)
ConfigService.update_config(config) ConfigService.update_config(config, should_encrypt=False)
@staticmethod @staticmethod
def set_server_ips_in_config(config): def set_server_ips_in_config(config):
@ -928,3 +969,21 @@ class ConfigService:
return validators.extend( return validators.extend(
validator_class, {"properties": set_defaults}, 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])

View File

@ -293,19 +293,19 @@ class ReportService:
@staticmethod @staticmethod
def get_config_users(): 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 @staticmethod
def get_config_passwords(): 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 @staticmethod
def get_config_exploits(): def get_config_exploits():
exploits_config_value = ['exploits', 'general', 'exploiter_classes'] 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: for namespace in exploits_config_value:
default_exploits = default_exploits[namespace] 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: if exploits == default_exploits:
return ['default'] return ['default']
@ -315,13 +315,13 @@ class ReportService:
@staticmethod @staticmethod
def get_config_ips(): 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 []
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 @staticmethod
def get_config_scan(): 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 @staticmethod
def get_issues_overview(issues, config_users, config_passwords): def get_issues_overview(issues, config_users, config_passwords):

View File

@ -13,4 +13,5 @@ jsonschema
netifaces netifaces
ipaddress ipaddress
enum34 enum34
PyCrypto
virtualenv virtualenv

View File

@ -12,4 +12,5 @@ Flask-JWT
jsonschema jsonschema
netifaces netifaces
ipaddress ipaddress
enum34 enum34
PyCrypto