diff --git a/monkey_island/cc/services/node.py b/monkey_island/cc/services/node.py index dc30d60d5..21c2ef194 100644 --- a/monkey_island/cc/services/node.py +++ b/monkey_island/cc/services/node.py @@ -292,3 +292,21 @@ class NodeService: {'_id': node_id}, {'$push': {'creds': creds}} ) + + @staticmethod + def get_node_or_monkey_by_ip(ip_address): + node = NodeService.get_node_by_ip(ip_address) + if node is not None: + return node + return NodeService.get_monkey_by_ip(ip_address) + + @staticmethod + def get_node_or_monkey_by_id(node_id): + node = NodeService.get_node_by_id(node_id) + if node is not None: + return node + return NodeService.get_monkey_by_id(node_id) + + @staticmethod + def get_node_hostname(node): + return node['hostname'] if 'hostname' in node else node['os']['version'] diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 51d1bf51f..609b17c31 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,9 +1,9 @@ -import datetime +import ipaddress from cc.database import mongo -from cc.services.config import ConfigService from cc.services.edge import EdgeService from cc.services.node import NodeService +from cc.utils import local_ip_addresses, get_subnets __author__ = "itay.mizeretz" @@ -38,25 +38,20 @@ class ReportService: st += "%d minutes and %d seconds" % (minutes, seconds) return st - @staticmethod - def get_breach_count(): - return mongo.db.edge.count({'exploits.result': True}) - - @staticmethod - def get_successful_exploit_types(): - exploit_types = mongo.db.command({'distinct': 'edge', 'key': 'exploits.exploiter'})['values'] - return [exploit for exploit in exploit_types if ReportService.did_exploit_type_succeed(exploit)] - @staticmethod def get_tunnels(): return [ - (NodeService.get_monkey_label_by_id(tunnel['_id']), NodeService.get_monkey_label_by_id(tunnel['tunnel'])) + { + 'type': 'tunnel', + 'origin': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['_id'])), + 'dest': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['tunnel'])) + } for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})] @staticmethod def get_scanned(): - nodes =\ - [NodeService.get_displayed_node_by_id(node['_id']) for node in mongo.db.node.find({}, {'_id': 1})]\ + nodes = \ + [NodeService.get_displayed_node_by_id(node['_id']) for node in mongo.db.node.find({}, {'_id': 1})] \ + [NodeService.get_displayed_node_by_id(monkey['_id']) for monkey in mongo.db.monkey.find({}, {'_id': 1})] nodes = [ { @@ -73,32 +68,17 @@ class ReportService: return nodes - @staticmethod - def get_reused_passwords(): - password_dict = {} - password_list = ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list']) - for password in password_list: - machines_with_password =\ - [ - NodeService.get_monkey_label_by_id(node['_id']) - for node in mongo.db.monkey.find({'creds.password': password}, {'_id': 1}) - ] - if len(machines_with_password) >= 2: - password_dict[password] = machines_with_password - - return password_dict - @staticmethod def get_exploited(): - exploited =\ + exploited = \ [NodeService.get_displayed_node_by_id(monkey['_id']) for monkey in mongo.db.monkey.find({}, {'_id': 1}) - if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey['_id']))]\ + if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey['_id']))] \ + [NodeService.get_displayed_node_by_id(node['_id']) for node in mongo.db.node.find({'exploited': True}, {'_id': 1})] exploited = [ { - 'label': monkey['hostname'] if 'hostname' in monkey else monkey['os']['version'], + 'label': NodeService.get_node_hostname(monkey), 'ip_addresses': monkey['ip_addresses'], 'exploits': [exploit['exploiter'] for exploit in monkey['exploits'] if exploit['result']] } @@ -111,8 +91,8 @@ class ReportService: PASS_TYPE_DICT = {'password': 'Password', 'lm_hash': 'LM', 'ntlm_hash': 'NTLM'} creds = [] for telem in mongo.db.telemetry.find( - {'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}}, - {'data.credentials': 1, 'monkey_guid': 1} + {'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}}, + {'data.credentials': 1, 'monkey_guid': 1} ): monkey_creds = telem['data']['credentials'] if len(monkey_creds) == 0: @@ -130,6 +110,146 @@ class ReportService: ) return creds + @staticmethod + def process_general_exploit(exploit): + ip_addr = exploit['data']['machine']['ip_addr'] + return {'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)), + 'ip_address': ip_addr} + + @staticmethod + def process_general_creds_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + + for attempt in exploit['data']['attempts']: + if attempt['result']: + processed_exploit['username'] = attempt['user'] + if len(attempt['password']) > 0: + processed_exploit['type'] = 'password' + else: + processed_exploit['type'] = 'hash' + return processed_exploit + + @staticmethod + def process_smb_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + if processed_exploit['type'] == 'password': + processed_exploit['type'] = 'smb_password' + else: + processed_exploit['type'] = 'smb_pth' + return processed_exploit + + @staticmethod + def process_wmi_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + if processed_exploit['type'] == 'password': + processed_exploit['type'] = 'wmi_password' + else: + processed_exploit['type'] = 'wmi_pth' + return processed_exploit + + @staticmethod + def process_ssh_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + processed_exploit['type'] = 'ssh' + return processed_exploit + + @staticmethod + def process_rdp_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + processed_exploit['type'] = 'rdp' + return processed_exploit + + @staticmethod + def process_sambacry_exploit(exploit): + processed_exploit = ReportService.process_general_creds_exploit(exploit) + processed_exploit['type'] = 'sambacry' + return processed_exploit + + @staticmethod + def process_elastic_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'elastic' + return processed_exploit + + @staticmethod + def process_conficker_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'conficker' + return processed_exploit + + @staticmethod + def process_shellshock_exploit(exploit): + processed_exploit = ReportService.process_general_exploit(exploit) + processed_exploit['type'] = 'shellshock' + urls = exploit['data']['info']['vulnerable_urls'] + processed_exploit['port'] = urls[0].split(':')[2].split('/')[0] + processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls] + return processed_exploit + + @staticmethod + def process_exploit(exploit): + exploiter_type = exploit['data']['exploiter'] + if exploiter_type == 'SmbExploiter': + return ReportService.process_smb_exploit(exploit) + if exploiter_type == 'WmiExploiter': + return ReportService.process_wmi_exploit(exploit) + if exploiter_type == 'SSHExploiter': + return ReportService.process_ssh_exploit(exploit) + if exploiter_type == 'RdpExploiter': + return ReportService.process_rdp_exploit(exploit) + if exploiter_type == 'SambaCryExploiter': + return ReportService.process_sambacry_exploit(exploit) + if exploiter_type == 'ElasticGroovyExploiter': + return ReportService.process_elastic_exploit(exploit) + if exploiter_type == 'Ms08_067_Exploiter': + return ReportService.process_conficker_exploit(exploit) + if exploiter_type == 'ShellShockExploiter': + return ReportService.process_shellshock_exploit(exploit) + + @staticmethod + def get_exploits(): + return [ReportService.process_exploit(exploit) for + exploit in mongo.db.telemetry.find({'telem_type': 'exploit', 'data.result': True})] + + @staticmethod + def get_monkey_subnets(monkey_guid): + return \ + [ + ipaddress.ip_interface(unicode(network['addr'] + '/' + network['netmask'])).network + for network in + mongo.db.telemetry.find_one( + {'telem_type': 'system_info_collection', 'monkey_guid': monkey_guid}, + {'data.network_info.networks': 1} + )['data']['network_info']['networks'] + ] + + @staticmethod + def get_cross_segment_issues(): + issues = [] + island_ips = local_ip_addresses() + for monkey in mongo.db.monkey.find({'tunnel': {'$exists': False}}, {'tunnel': 1, 'guid': 1, 'hostname': 1}): + found_good_ip = False + monkey_subnets = ReportService.get_monkey_subnets(monkey['guid']) + for subnet in monkey_subnets: + for ip in island_ips: + if ipaddress.ip_address(unicode(ip)) in subnet: + found_good_ip = True + break + if found_good_ip: + break + if not found_good_ip: + issues.append( + {'type': 'cross_segment', 'machine': monkey['hostname'], + 'networks': [str(subnet) for subnet in monkey_subnets], + 'server_networks': [str(subnet) for subnet in get_subnets()]} + ) + + return issues + + @staticmethod + def get_issues(): + return ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + @staticmethod def get_report(): return \ @@ -149,42 +269,9 @@ class ReportService: }, 'recommendations': { - 'issues': - [ - {'type': 'smb_password', 'machine': 'Monkey-SMB', - 'ip_addresses': ['192.168.0.1', '10.0.0.18'], 'username': 'Administrator'}, - {'type': 'smb_pth', 'machine': 'Monkey-SMB2', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'username': 'Administrator'}, - {'type': 'wmi_password', 'machine': 'Monkey-WMI', - 'ip_addresses': ['192.168.0.1', '10.0.0.18'], 'username': 'Administrator'}, - {'type': 'wmi_pth', 'machine': 'Monkey-WMI2', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'username': 'Administrator'}, - {'type': 'ssh', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'username': 'Administrator'}, - {'type': 'rdp', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'username': 'Administrator'}, - {'type': 'sambacry', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'username': 'Administrator'}, - {'type': 'elastic', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18']}, - {'type': 'shellshock', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18'], - 'port': 8080, 'paths': ['/cgi/backserver.cgi', '/cgi/login.cgi']}, - {'type': 'conficker', 'machine': 'Monkey-SMB', 'ip_addresses': ['192.168.0.1', '10.0.0.18']}, - {'type': 'cross_segment', 'machine': 'Monkey-SMB', 'network': '192.168.0.0/24', - 'server_network': '172.168.0.0/24'}, - {'type': 'tunnel', 'origin': 'Monkey-SSH', 'dest': 'Monkey-SambaCry'} - ] + 'issues': ReportService.get_issues() } } - # TODO: put implementation in template - """ - return \ - { - 'breach_count': ReportService.get_breach_count(), - 'successful_exploit_types': ReportService.get_successful_exploit_types(), - 'tunnels': ReportService.get_tunnels(), - 'reused_passwords': ReportService.get_reused_passwords() - } - """ @staticmethod def did_exploit_type_succeed(exploit_type): diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index ce5e00f4d..305d39fd6 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -65,8 +65,8 @@ class ReportPageComponent extends React.Component { }); } - generateIpListBadges(ip_addresses) { - return ip_addresses.map(ip_address => {ip_address}); + generateInfoBadges(data_array) { + return data_array.map(badge_data => {badge_data}); } generateShellshockPathListBadges(paths) { @@ -76,7 +76,7 @@ class ReportPageComponent extends React.Component { generateSmbPasswordIssue(issue) { return (