From ce10ef00e4734252320f91ee6db9426d3c1d99b9 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 27 Nov 2017 15:20:59 +0200 Subject: [PATCH] Everything implemented on backend --- monkey_island/cc/services/node.py | 18 ++ monkey_island/cc/services/report.py | 223 ++++++++++++------ .../cc/ui/src/components/pages/ReportPage.js | 26 +- monkey_island/cc/utils.py | 9 + monkey_island/requirements.txt | 3 +- 5 files changed, 197 insertions(+), 82 deletions(-) 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 (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a SMB attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a SMB attack.
The attack succeeded by authenticating over SMB protocol with user {issue.username} and its password.
@@ -91,7 +91,7 @@ class ReportPageComponent extends React.Component { generateSmbPthIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a SMB attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a SMB attack.
The attack succeeded by using a pass-the-hash attack over SMB protocol with user {issue.username}.
@@ -106,7 +106,7 @@ class ReportPageComponent extends React.Component { generateWmiPasswordIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a WMI attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a WMI attack.
The attack succeeded by authenticating over WMI protocol with user {issue.username} and its password.
@@ -121,7 +121,7 @@ class ReportPageComponent extends React.Component { generateWmiPthIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a WMI attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a WMI attack.
The attack succeeded by using a pass-the-hash attack over WMI protocol with user {issue.username}.
@@ -136,7 +136,7 @@ class ReportPageComponent extends React.Component { generateSshIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a SSH attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a SSH attack.
The attack succeeded by authenticating over SSH protocol with user {issue.username} and its password.
@@ -151,7 +151,7 @@ class ReportPageComponent extends React.Component { generateRdpIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a RDP attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a RDP attack.
The attack succeeded by authenticating over RDP protocol with user {issue.username} and its password.
@@ -166,7 +166,7 @@ class ReportPageComponent extends React.Component { generateSambaCryIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a SambaCry attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a SambaCry attack.
The attack succeeded by authenticating over SMB protocol with user {issue.username} and its password, and by using the SambaCry vulnerability.
@@ -182,7 +182,7 @@ class ReportPageComponent extends React.Component { generateElasticIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to an Elastic Groovy attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to an Elastic Groovy attack.
The attack succeeded because the Elastic Search server was not parched against CVE-2015-1427.
@@ -197,7 +197,7 @@ class ReportPageComponent extends React.Component { generateShellshockIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a ShellShock attack. + The machine {issue.machine} with the following IP address {issue.ip_address} was vulnerable to a ShellShock attack.
The attack succeeded because the HTTP server running on port {issue.port} was vulnerable to a shell injection attack on the paths: {this.generateShellshockPathListBadges(issue.paths)}.
@@ -212,7 +212,7 @@ class ReportPageComponent extends React.Component { generateConfickerIssue(issue) { return (
- The machine {issue.machine} with the following IP addresses {this.generateIpListBadges(issue.ip_addresses)} was vulnerable to a Conficker attack. + The machine {issue.machine} with the following address {issue.ip_address} was vulnerable to a Conficker attack.
The attack succeeded because the target machine uses an outdated and unpatched operating system vulnerable to Conficker.
@@ -227,7 +227,7 @@ class ReportPageComponent extends React.Component { generateCrossSegmentIssue(issue) { return (
- The network can probably be segmented. A monkey instance on {issue.machine} in the {issue.network} network could directly access the Monkey Island C&C server in the {issue.server_network} network. + The network can probably be segmented. A monkey instance on {issue.machine} in the networks {this.generateInfoBadges(issue.networks)} could directly access the Monkey Island C&C server in the networks {this.generateInfoBadges(issue.server_networks)}.
In order to protect the network, the following steps should be performed:
    diff --git a/monkey_island/cc/utils.py b/monkey_island/cc/utils.py index 34026b157..d59c23825 100644 --- a/monkey_island/cc/utils.py +++ b/monkey_island/cc/utils.py @@ -4,6 +4,7 @@ import sys import array import struct +import ipaddress from netifaces import interfaces, ifaddresses, AF_INET from cc.database import mongo @@ -56,3 +57,11 @@ def local_ip_addresses(): addresses = ifaddresses(interface).get(AF_INET, []) ip_list.extend([link['addr'] for link in addresses if link['addr'] != '127.0.0.1']) return ip_list + + +def get_subnets(): + subnets = [] + for interface in interfaces(): + addresses = ifaddresses(interface).get(AF_INET, []) + subnets.extend([ipaddress.ip_interface(link['addr'] + '/' + link['netmask']).network for link in addresses if link['addr'] != '127.0.0.1']) + return subnets diff --git a/monkey_island/requirements.txt b/monkey_island/requirements.txt index 275c8b96a..1aa7288c5 100644 --- a/monkey_island/requirements.txt +++ b/monkey_island/requirements.txt @@ -9,4 +9,5 @@ flask Flask-Pymongo Flask-Restful jsonschema -netifaces \ No newline at end of file +netifaces +ipaddress \ No newline at end of file