diff --git a/infection_monkey/system_info/windows_info_collector.py b/infection_monkey/system_info/windows_info_collector.py index e9e7f71fd..1444f5805 100644 --- a/infection_monkey/system_info/windows_info_collector.py +++ b/infection_monkey/system_info/windows_info_collector.py @@ -15,18 +15,9 @@ LOG = logging.getLogger(__name__) __author__ = 'uri' -WMI_CLASSES = set(["Win32_OperatingSystem", - "Win32_ComputerSystem", - "Win32_LoggedOnUser", - "Win32_UserAccount", - "Win32_UserProfile", - "Win32_Group", - "Win32_GroupUser", - "Win32_Product", - "Win32_Service", - "Win32_OptionalFeature", - #"Win32_Process", - ]) +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 diff --git a/monkey_island/cc/app.py b/monkey_island/cc/app.py index 33d1bb53b..a965ab070 100644 --- a/monkey_island/cc/app.py +++ b/monkey_island/cc/app.py @@ -19,7 +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.pthreport import Report from cc.resources.node import Node from cc.resources.report import Report from cc.resources.root import Root @@ -107,6 +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/') + api.add_resource(Report, '/api/pthreport', '/api/pthreport/') return app diff --git a/monkey_island/cc/resources/pthmap.py b/monkey_island/cc/resources/pthmap.py index 5230ef30e..d19afd56f 100644 --- a/monkey_island/cc/resources/pthmap.py +++ b/monkey_island/cc/resources/pthmap.py @@ -1,13 +1,10 @@ -import hashlib -import binascii import copy import flask_restful -from pthreport import PassTheHashReport, Machine + from cc.auth import jwt_required -from cc.services.edge import EdgeService -from cc.services.node import NodeService -from cc.database import mongo +from cc.services.pth_report_utils import PassTheHashReport, Machine + class PthMap(flask_restful.Resource): @jwt_required() diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index b89a8b078..2484f7b07 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -1,1250 +1,13 @@ -import hashlib -import binascii -import copy +import flask_restful -if __name__ == "__main__": - from pymongo import MongoClient +from cc.auth import jwt_required +from cc.services.pth_report import PTHReportService - class mongo(object): - db = MongoClient().monkeyisland -else: - from cc.database import mongo - -DsRole_RoleStandaloneWorkstation = 0 -DsRole_RoleMemberWorkstation = 1 -DsRole_RoleStandaloneServer = 2 -DsRole_RoleMemberServer = 3 -DsRole_RoleBackupDomainController = 4 -DsRole_RolePrimaryDomainController = 5 +__author__ = "itay.mizeretz" -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) +class Report(flask_restful.Resource): -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] - - 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["data"]["mimikatz"] - - @cache - def GetHostName(self): - doc = self.latest_system_info - - for comp in doc["data"]["Win32_ComputerSystem"]: - return eval(comp["Name"]) - - return None - - @cache - def GetIp(self): - doc = self.latest_system_info - - for addr in doc["data"]["network_info"]["networks"]: - return str(addr["addr"]) - - return None - - @cache - def GetDomainName(self): - doc = self.latest_system_info - - for comp in doc["data"]["Win32_ComputerSystem"]: - return eval(comp["Domain"]) - - return None - - @cache - def GetDomainRole(self): - doc = self.latest_system_info - - for comp in doc["data"]["Win32_ComputerSystem"]: - return comp["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["data"]["Win32_UserAccount"]: - if eval(user["Name"]) != username: - continue - - if user["SIDType"] != SidTypeUser: - continue - - if domain and user["Domain"] != domain: - 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 - - @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 - - if user["SIDType"] != SidTypeUser: - continue - - 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(): - domain = dc.GetSidInfo(sid) - - if domain != None: - return domain - - return None - - @cache - def GetCriticalServicesInstalled(self): - def IsNameOfCriticalService(name): - services = ("W3svc", "MSExchangeServiceHost", "MSSQLServer", "dns") - 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["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 - - if eval(service["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)) - - @cache - def GetGroupSidByGroupName(self, group_name): - doc = self.latest_system_info - - for group in doc["data"]["Win32_Group"]: - if eval(group["Name"]) != group_name: - continue - - if not is_group_sid_type(group["SIDType"]): - continue - - return eval(group["SID"]) - - return None - - @cache - def GetUsersByGroupSid(self, sid): - doc = self.latest_system_info - - users = dict() - - for group_user in doc["data"]["Win32_GroupUser"]: - if eval(group_user["GroupComponent"]["SID"]) != sid: - continue - - if not is_group_sid_type(group_user["GroupComponent"]["SIDType"]): - continue - - if "PartComponent" not in group_user.keys(): - 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"]) - - 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["monkey_guid"]).IsDomainController(): - continue - - GUIDs.add(doc["monkey_guid"]) - - return GUIDs - - @cache - def GetLocalAdmins(self): - 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): - doc = self.latest_system_info - - SIDs = set() - - for user in doc["data"]["Win32_UserAccount"]: - if user["SIDType"] != SidTypeUser: - continue - - SIDs.add(eval(user["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["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 {} - - 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) - - @cache - 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["data"]["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["data"]["credentials"]: - names.add(username) - - 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.machines = map(Machine, self.vertices) - - 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): - cur = mongo.db.telemetry.find({"telem_type":"system_info_collection"}) - - GUIDs = set() - - for doc in cur: - GUIDs.add(doc["monkey_guid"]) - - return GUIDs - - @cache - 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) - - @cache - def ReprSecretList(self, secret_list, victim): - label = set() - - for secret in secret_list: - label |= Machine(victim).GetUsernamesBySecret(secret) - - return ",\n".join(label) - - @cache - def GetEdgesBySid(self): - edges = set() - - for attacker in self.vertices: - cached = self.GetCachedSids(Machine(attacker)) - - for victim in self.vertices: - if attacker == victim: - continue - - admins = Machine(victim).GetAdmins() - - if len(cached & admins) > 0: - label = self.ReprSidList(cached & admins, attacker, victim) - edges.add((attacker, victim, label)) - - return edges - - @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 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: - 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 atck, vic, _ in self.edges: - if vic == victim: - attackers.add(atck) - - 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): - threatening_users |= (self.GetCachedSids(attacker) & victim.GetAdmins()) - - return threatening_users - - @cache - def GetSharedAdmins(self, m): - shared_admins = set() - - for other in self.machines: - if m == other: - continue - - shared_admins |= (m.GetLocalAdminSids() & other.GetLocalAdminSids()) - - shared_admins -= m.GetDomainAdminsOfMachine() - return shared_admins - -def main(): - pth = PassTheHashReport() - - print """""" - print "

Pass The Hash Report

" - - 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 """""" - print """
User CountUsers That Share This Password
{count}
    """ - for sid in pth.GetSidsBySecret(secret): - print """
  • {username}
  • """.format(sid=sid, username=pth.GetUsernameBySid(sid)) - 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 m, 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.GetSharedAdmins(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

" - - 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 """""".format(count=count) - - print """""" - print """
Critical ServerHostnameDomainCritical Services InstalledThreatening User CountThreatening 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} 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 """
""" - - - 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 """""".format(count=count) - - print """""" - print """
Critical ServerHostnameDomainThreatening User CountThreatening Users
{ip}{hostname}{domain}{count}
    """ - - for sid in pth.GetThreateningUsersByVictim(m): - 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 """
""" - - 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) \ No newline at end of file + @jwt_required() + def get(self): + return PTHReportService.get_report() diff --git a/monkey_island/cc/resources/pthreporthtml.py b/monkey_island/cc/resources/pthreporthtml.py deleted file mode 100644 index 8aa10870f..000000000 --- a/monkey_island/cc/resources/pthreporthtml.py +++ /dev/null @@ -1,21 +0,0 @@ -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/services/pth_report.py b/monkey_island/cc/services/pth_report.py new file mode 100644 index 000000000..3f01839f3 --- /dev/null +++ b/monkey_island/cc/services/pth_report.py @@ -0,0 +1,302 @@ +from cc.services.pth_report_utils import PassTheHashReport + + +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): + usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) + usernames_lists.append(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(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_names': m.GetCriticalServicesInstalled(), + 'user_count': count, + 'threatening_users': threatening_users_attackers_dict + } + strong_users_crit_list.append(machine) + return strong_users_crit_list + + @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': m.GetNonCritialServers(), + '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_report(): + pth = PassTheHashReport() + + 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) + }, + 'map': + { + + } + } + return report + + # print """