diff --git a/monkey_island/cc/resources/pthreport.py b/monkey_island/cc/resources/pthreport.py index 31233aa1e..7c4046694 100644 --- a/monkey_island/cc/resources/pthreport.py +++ b/monkey_island/cc/resources/pthreport.py @@ -3,7 +3,7 @@ import flask_restful from cc.auth import jwt_required from cc.services.pth_report import PTHReportService -__author__ = "itay.mizeretz" +__author__ = "maor.rayzin" class PTHReport(flask_restful.Resource): diff --git a/monkey_island/cc/services/pth_report.py b/monkey_island/cc/services/pth_report.py index 45a6e3668..7a615db2d 100644 --- a/monkey_island/cc/services/pth_report.py +++ b/monkey_island/cc/services/pth_report.py @@ -18,7 +18,8 @@ class PTHReportService(object): continue for sid in pth.GetSidsBySecret(secret): usernames_per_sid_list.append(pth.GetUsernameBySid(sid)) - usernames_lists.append(usernames_per_sid_list) + + usernames_lists.append({'cred_group': usernames_per_sid_list}) return usernames_lists @@ -99,24 +100,49 @@ class PTHReportService(object): 'ip': m.GetIp(), 'hostname': m.GetHostName(), 'domain': m.GetDomainName(), - 'services_names': m.GetNonCritialServers(), + 'services_names': [], 'user_count': count, 'threatening_users': threatening_users_attackers_dict } strong_users_non_crit_list.append(machine) return strong_users_non_crit_list + @staticmethod + def get_duplicated_passwords_issues(pth, password_groups): + issues = [] + issues_dict = {} + for group in password_groups: + for username in group['cred_group']: + sid = list(pth.GetSidsByUsername(username.split('\\')[1])) + machine_info = pth.GetSidInfo(sid[0]) + issues.append( + { + 'type': 'shared_password', + 'machine': machine_info.get('hostname').split('.')[0], + 'shared_with': [x for x in group['cred_group'] if x != username], + 'username': username + } + ) + + for issue in issues: + machine = issue['machine'] + if machine not in issues_dict: + issues_dict[machine] = [] + issues_dict[machine].append(issue) + + return issues_dict + @staticmethod def generate_map_nodes(pth): nodes_list = [] for node_id in pth.vertices: machine = Machine(node_id) node = { - "id": str(machine.get_monkey_id()), + "id": str(node_id), "label": '{0} : {1}'.format(machine.GetHostName(), machine.GetIp()), 'group': 'critical' if machine.IsCriticalServer() else 'normal', 'users': list(machine.GetCachedUsernames()), - 'ips': machine.GetIp(), + 'ips': [machine.GetIp()], 'services': machine.GetCriticalServicesInstalled(), 'hostname': machine.GetHostName() } @@ -127,17 +153,23 @@ class PTHReportService(object): @staticmethod def get_report(): pth = PassTheHashReport() + same_password = PTHReportService.get_duplicated_password_nodes(pth) + local_admin_shared = PTHReportService.get_shared_local_admins_nodes(pth) + strong_users_on_crit_services = PTHReportService.get_strong_users_on_crit_services(pth) + strong_users_on_non_crit_services = PTHReportService.get_strong_users_on_non_crit_services(pth) + issues = PTHReportService.get_duplicated_passwords_issues(pth, same_password) report = \ { 'report_info': { - 'same_password': PTHReportService.get_duplicated_password_nodes(pth), - 'local_admin_shared': PTHReportService.get_shared_local_admins_nodes(pth), - 'strong_users_on_crit_services': PTHReportService.get_strong_users_on_crit_services(pth), - 'strong_users_on_non_crit_services': PTHReportService.get_strong_users_on_non_crit_services(pth) + 'same_password': same_password, + 'local_admin_shared': local_admin_shared, + 'strong_users_on_crit_services': strong_users_on_crit_services, + 'strong_users_on_non_crit_services': strong_users_on_non_crit_services, + 'pth_issues': issues }, - 'map': + 'pthmap': { 'nodes': PTHReportService.generate_map_nodes(pth), 'edges': pth.edges @@ -318,4 +350,3 @@ class PTHReportService(object): # for m in pth.GetAttackersBySecret(secret): # print """
  • {hostname}
  • """.format(ip=m.GetIp(), hostname=m.GetHostName()) # print """""" - diff --git a/monkey_island/cc/services/pth_report_utils.py b/monkey_island/cc/services/pth_report_utils.py index 06c40f023..b4839f852 100644 --- a/monkey_island/cc/services/pth_report_utils.py +++ b/monkey_island/cc/services/pth_report_utils.py @@ -212,7 +212,8 @@ class Machine(object): "Username": eval(user.get("Name")), "Disabled": user.get("Disabled") == "true", "PasswordRequired": user.get("PasswordRequired") == "true", - "PasswordExpires": user.get("PasswordExpires") == "true", } + "PasswordExpires": user.get("PasswordExpires") == "true", + 'hostname': doc.get('data').get('hostname'), } if not self.IsDomainController(): for dc in self.GetDomainControllers(): @@ -555,6 +556,7 @@ class Machine(object): return names + class PassTheHashReport(object): # _instance = None # def __new__(class_, *args, **kwargs): @@ -570,7 +572,7 @@ class PassTheHashReport(object): self.edges = self.get_edges_by_sid() # Useful for non-cached domain users #self.edges |= self.GetEdgesBySamHash() # This will add edges based only on password hash without caring about username - @cache + def GetAllMachines(self): cur = mongo.db.telemetry.find({"telem_type": "system_info_collection"}) @@ -610,13 +612,12 @@ class PassTheHashReport(object): attacker_monkey = NodeService.get_monkey_by_guid(attacker) victim_monkey = NodeService.get_monkey_by_guid(victim) - attacker_label = NodeService.get_node_label(attacker_monkey) - victim_label = NodeService.get_node_label(victim_monkey) + attacker_label = NodeService.get_monkey_label(attacker_monkey) + victim_label = NodeService.get_monkey_label(victim_monkey) RIGHT_ARROW = u"\u2192" return "%s %s %s" % (attacker_label, RIGHT_ARROW, victim_label) - @cache def get_edges_by_sid(self): edges_list = [] @@ -637,7 +638,7 @@ class PassTheHashReport(object): 'to': victim, 'users': relevant_users_list, '_label': PassTheHashReport.__get_edge_label(attacker, victim), - 'id': uuid.uuid4() + 'id': str(uuid.uuid4()) }) return edges_list @@ -858,9 +859,9 @@ class PassTheHashReport(object): attackers = set() - for atck, vic, _ in self.edges: - if vic == victim: - attackers.add(atck) + for edge in self.edges: + if edge.get('to', None) == victim: + attackers.add(edge.get('from', None)) return set(map(Machine, attackers)) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 03939b01d..ed75da059 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -47,6 +47,7 @@ class ReportPageComponent extends AuthComponent { allMonkeysAreDead: false, runStarted: true }; + this.getPth } componentDidMount() { @@ -117,7 +118,7 @@ class ReportPageComponent extends AuthComponent { .then(res => { this.setState({ pthreport: res.report_info, - pthmap: res.map + pthmap: res.pthmap }); }); } @@ -389,6 +390,7 @@ class ReportPageComponent extends AuthComponent {
    {this.generateIssues(this.state.report.recommendations.issues)} + {this.generateIssues(this.state.pthreport.pth_issues)}
    ); @@ -399,6 +401,7 @@ class ReportPageComponent extends AuthComponent { (100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length; return (
    +

    The Network from the Monkey's Eyes

    @@ -452,7 +455,15 @@ class ReportPageComponent extends AuthComponent {
    - + { /* TODO: use dynamic data */} +
    ); @@ -728,6 +739,18 @@ class ReportPageComponent extends AuthComponent { ); } + generateSharedCredsIssue(issue) { + return ( +
  • + Some users are sharing passwords, this should be fixed by changing passwords. + + The user {issue.username} is sharing access password with: + {this.generateInfoBadges(issue.shared_with)}. + +
  • + ); + } + generateTunnelIssue(issue) { return (
  • @@ -800,6 +823,9 @@ class ReportPageComponent extends AuthComponent { case 'cross_segment': data = this.generateCrossSegmentIssue(issue); break; + case 'shared_password': + data = this.generateSharedCredsIssue(issue); + break; case 'tunnel': data = this.generateTunnelIssue(issue); break;