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 0348a7f5d..000000000 Binary files a/monkey_island/cc/ui/src/images/nodes/pth/critical.png and /dev/null differ diff --git a/monkey_island/cc/ui/src/images/nodes/pth/normal.png b/monkey_island/cc/ui/src/images/nodes/pth/normal.png deleted file mode 100644 index 3b1e9b638..000000000 Binary files a/monkey_island/cc/ui/src/images/nodes/pth/normal.png and /dev/null differ