From b443652b0ea58cc3a7892ab0a7f8fce0614356ec Mon Sep 17 00:00:00 2001 From: "maor.rayzin" Date: Tue, 16 Oct 2018 18:53:56 +0300 Subject: [PATCH] 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(), } }