From 7111f5b0e2b7b595bd66e0f82d04870be910161d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 21 Feb 2018 12:55:36 +0200 Subject: [PATCH 01/13] Add inaccessible_subnet_groups config value --- chaos_monkey/config.py | 1 + chaos_monkey/example.conf | 1 + monkey_island/cc/services/config.py | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index e62820816..fadc56258 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -184,6 +184,7 @@ class Configuration(object): range_class = FixedRange range_fixed = ['', ] + inaccessible_subnet_groups = [] blocked_ips = ['', ] diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 6f70f888a..c3be6354f 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -11,6 +11,7 @@ "range_fixed": [ "" ], + "inaccessible_subnet_groups": [], "blocked_ips": [""], "current_server": "41.50.73.31:5000", "alive": true, diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 361854f05..b2792e6b3 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -235,6 +235,28 @@ SCHEMA = { "List of IPs/subnets to include when using FixedRange" " (Only relevant for Fixed Range)." " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" + }, + "inaccessible_subnet_groups": { + "title": "Inaccessible IP/subnet groups", + "type": "array", + "uniqueItems": True, + "items": { + "type": "array", + "title": "Subnet group", + "items": { + "type": "string" + }, + "minItems": 2, + "uniqueItems": True, + "description": "List of IPs/subnets." + " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\"," + " \"192.168.0.5/24\"" + }, + "default": [ + ], + "description": + "List of IP/subnet groups. Each group should consist of subnets that aren't supposed" + " to be accessible to one another." } } } From d6240ff5029761da368a8d34822c2a546e4f248d Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 17:32:02 +0200 Subject: [PATCH 02/13] move inaccessible_subnet_groups to seperate category --- monkey_island/cc/services/config.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index d940019d7..e20ac4ce2 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -211,7 +211,13 @@ SCHEMA = { "description": "List of IPs/subnets the monkey should scan." " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\"" - }, + } + } + }, + "network_analysis": { + "title": "Network Analysis", + "type": "object", + "properties": { "inaccessible_subnet_groups": { "title": "Inaccessible IP/subnet groups", "type": "array", @@ -232,7 +238,8 @@ SCHEMA = { ], "description": "List of IP/subnet groups. Each group should consist of subnets that aren't supposed" - " to be accessible to one another." + " to be accessible to one another. If the monkey is in one subnet it'll scan the other" + " subnets in the same group." } } } From daef61e492ab598f58875494f5dcd1efa1b5eea4 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 26 Feb 2018 17:32:27 +0200 Subject: [PATCH 03/13] Scan inaccessible subnets --- infection_monkey/network/network_scanner.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index 7bdddc904..9f83474a7 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -35,8 +35,18 @@ class NetworkScanner(object): self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list] if WormConfiguration.local_network_scan: self._ranges += get_interfaces_ranges() + self._ranges += self._get_inaccessible_subnets_ips() LOG.info("Base local networks to scan are: %r", self._ranges) + def _get_inaccessible_subnets_ips(self): + subnets_to_scan = [] + for subnet_group in WormConfiguration.inaccessible_subnet_groups: + for subnet_str in subnet_group: + if NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], subnet_str): + subnets_to_scan += [NetworkRange.get_range_obj(x) for x in subnet_group if x != subnet_str] + break + return subnets_to_scan + def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): assert issubclass(scan_type, HostScanner) @@ -74,3 +84,10 @@ class NetworkScanner(object): if SCAN_DELAY: time.sleep(SCAN_DELAY) + + @staticmethod + def _is_any_ip_in_subnet(ip_addresses, subnet_str): + for ip_address in ip_addresses: + if NetworkRange.get_range_obj(subnet_str).is_in_range(ip_address): + return True + return False From cacb60b13223f8c662f4775fade289ed85239690 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 14:03:50 +0200 Subject: [PATCH 04/13] Add parent path for python paths (for common code) --- monkey_island/cc/main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index e0f6ab079..3a6afb7ca 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -6,6 +6,11 @@ import sys import time BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PARENT_PATH = os.path.dirname(BASE_PATH) + +if PARENT_PATH not in sys.path: + sys.path.insert(0, PARENT_PATH) + if BASE_PATH not in sys.path: sys.path.insert(0, BASE_PATH) From aae2a3a8de7bf2eb45fc73078661f6db9bf74aa5 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 14:05:23 +0200 Subject: [PATCH 05/13] rename existing cross_segment_issue to island_cross_segment_issue --- monkey_island/cc/services/report.py | 13 +++++++------ .../cc/ui/src/components/pages/ReportPage.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 250acc17a..4adcf4580 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -35,7 +35,7 @@ class ReportService: CONFICKER = 5 class WARNINGS_DICT(Enum): - CROSS_SEGMENT = 0 + ISLAND_CROSS_SEGMENT = 0 TUNNEL = 1 @staticmethod @@ -253,7 +253,7 @@ class ReportService: ] @staticmethod - def get_cross_segment_issues(): + def get_island_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}): @@ -268,7 +268,7 @@ class ReportService: break if not found_good_ip: issues.append( - {'type': 'cross_segment', 'machine': monkey['hostname'], + {'type': 'island_cross_segment', 'machine': monkey['hostname'], 'networks': [str(subnet) for subnet in monkey_subnets], 'server_networks': [str(subnet) for subnet in get_subnets()]} ) @@ -277,7 +277,8 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues() + issues = ReportService.get_exploits() + ReportService.get_tunnels() \ + + ReportService.get_island_cross_segment_issues() issues_dict = {} for issue in issues: machine = issue['machine'] @@ -349,8 +350,8 @@ class ReportService: for machine in issues: for issue in issues[machine]: - if issue['type'] == 'cross_segment': - warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True + if issue['type'] == 'island_cross_segment': + warnings_byte_array[ReportService.WARNINGS_DICT.ISLAND_CROSS_SEGMENT.value] = True elif issue['type'] == 'tunnel': warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 56c2c3881..5c77f9c46 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -26,7 +26,7 @@ class ReportPageComponent extends AuthComponent { Warning = { - CROSS_SEGMENT: 0, + ISLAND_CROSS_SEGMENT: 0, TUNNEL: 1 }; @@ -333,7 +333,7 @@ class ReportPageComponent extends AuthComponent {
The Monkey uncovered the following possible set of issues:
    - {this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ? + {this.state.report.overview.warnings[this.Warning.ISLAND_CROSS_SEGMENT] ?
  • Weak segmentation - Machines from different segments are able to communicate.
  • : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ? @@ -664,7 +664,7 @@ class ReportPageComponent extends AuthComponent { case 'conficker': data = this.generateConfickerIssue(issue); break; - case 'cross_segment': + case 'island_cross_segment': data = this.generateCrossSegmentIssue(issue); break; case 'tunnel': From 57ae31406a9dddea7dc93eb88788e9bb8e7c7840 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 14:17:50 +0200 Subject: [PATCH 06/13] Implement backend --- monkey_island/cc/services/report.py | 85 +++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 4adcf4580..3c30ba20e 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,3 +1,5 @@ +import itertools + import ipaddress from enum import Enum @@ -6,6 +8,7 @@ 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 +from common.network.range import NetworkRange __author__ = "itay.mizeretz" @@ -33,6 +36,7 @@ class ReportService: SAMBACRY = 3 SHELLSHOCK = 4 CONFICKER = 5 + CROSS_SEGMENT = 6 class WARNINGS_DICT(Enum): ISLAND_CROSS_SEGMENT = 0 @@ -275,6 +279,74 @@ class ReportService: return issues + @staticmethod + def get_cross_segment_ip(ip_addresses, source_subnet, target_subnet): + for ip_address in ip_addresses: + if target_subnet.is_in_range(ip_address): + return None + for ip_address in ip_addresses: + if source_subnet.is_in_range(ip_address): + return ip_address + return None + + @staticmethod + def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet): + if source_subnet == target_subnet: + return [] + source_subnet_range = NetworkRange.get_range_obj(source_subnet) + target_subnet_range = NetworkRange.get_range_obj(target_subnet) + + cross_segment_issues = [] + + for scan in scans: + target_ip = scan['data']['machine']['ip_addr'] + if target_subnet_range.is_in_range(unicode(target_ip)): + monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) + cross_segment_ip = ReportService.get_cross_segment_ip(monkey['ip_addresses'], source_subnet_range, + target_subnet_range) + if cross_segment_ip is not None: + cross_segment_issues.append( + { + 'source': cross_segment_ip, + 'target': target_ip, + 'services': scan['data']['machine']['services'] + }) + + return cross_segment_issues + + @staticmethod + def get_cross_segment_issues_per_subnet_group(scans, subnet_group): + cross_segment_issues = [] + + for subnet_pair in itertools.product(subnet_group, subnet_group): + source_subnet = subnet_pair[0] + target_subnet = subnet_pair[1] + pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet) + if len(pair_issues) != 0: + cross_segment_issues.append( + { + 'source_subnet': source_subnet, + 'target_subnet': target_subnet, + 'issues': pair_issues + }) + + return cross_segment_issues + + @staticmethod + def get_cross_segement_issues(): + scans = mongo.db.telemetry.find({'telem_type': 'scan'}, + {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1}) + + cross_segment_issues = [] + + subnet_groups = ConfigService.get_config_value( + ['basic_network', 'network_analysis', 'inaccessible_subnet_groups']) + + for subnet_group in subnet_groups: + cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) + + return cross_segment_issues + @staticmethod def get_issues(): issues = ReportService.get_exploits() + ReportService.get_tunnels() \ @@ -323,8 +395,8 @@ class ReportService: return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True) @staticmethod - def get_issues_overview(issues, config_users, config_passwords): - issues_byte_array = [False] * 6 + def get_issues_overview(issues, cross_segment_issues, config_users, config_passwords): + issues_byte_array = [False] * 7 for machine in issues: for issue in issues[machine]: @@ -342,6 +414,9 @@ class ReportService: elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True + if len(cross_segment_issues) != 0: + issues_byte_array[ReportService.ISSUES_DICT.CROSS_SEGMENT.value] = True + return issues_byte_array @staticmethod @@ -376,6 +451,7 @@ class ReportService: issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() + cross_segment_issues = ReportService.get_cross_segement_issues() report = \ { @@ -389,14 +465,15 @@ class ReportService: 'config_scan': ReportService.get_config_scan(), 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_duration': ReportService.get_monkey_duration(), - 'issues': ReportService.get_issues_overview(issues, config_users, config_passwords), + 'issues': ReportService.get_issues_overview(issues, cross_segment_issues, config_users, config_passwords), 'warnings': ReportService.get_warnings_overview(issues) }, 'glance': { 'scanned': ReportService.get_scanned(), 'exploited': ReportService.get_exploited(), - 'stolen_creds': ReportService.get_stolen_creds() + 'stolen_creds': ReportService.get_stolen_creds(), + 'cross_segment_issues': cross_segment_issues }, 'recommendations': { From 74a928cfe7bf34c6a7b5d965629ef6a6cfe09775 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 14:45:17 +0200 Subject: [PATCH 07/13] if machine has IPs in 2 different subnets in same group, don't scan other subnet --- infection_monkey/network/network_scanner.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index d1386583e..49390295e 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -1,9 +1,9 @@ import logging import time +from common.network.range import * from config import WormConfiguration from info import local_ips, get_interfaces_ranges -from common.network.range import * from model import VictimHost from . import HostScanner @@ -44,8 +44,16 @@ class NetworkScanner(object): for subnet_group in WormConfiguration.inaccessible_subnet_groups: for subnet_str in subnet_group: if NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], subnet_str): - subnets_to_scan += [NetworkRange.get_range_obj(x) for x in subnet_group if x != subnet_str] + # If machine has IPs from 2 different subnets in the same group, there's no point checking the other + # subnet. + for other_subnet_str in subnet_group: + if other_subnet_str == subnet_str: + continue + if not NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], + other_subnet_str): + subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) break + return subnets_to_scan def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): From 08995796ef986510eff149a925a157f0d78db9ab Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 15:54:19 +0200 Subject: [PATCH 08/13] Make both segmentation issues and island segmentation issues present the same warning --- monkey_island/cc/services/report.py | 25 +++++++++---------- .../cc/ui/src/components/pages/ReportPage.js | 6 ++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 3c30ba20e..b4d604c3a 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -36,10 +36,9 @@ class ReportService: SAMBACRY = 3 SHELLSHOCK = 4 CONFICKER = 5 - CROSS_SEGMENT = 6 class WARNINGS_DICT(Enum): - ISLAND_CROSS_SEGMENT = 0 + CROSS_SEGMENT = 0 TUNNEL = 1 @staticmethod @@ -395,8 +394,8 @@ class ReportService: return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True) @staticmethod - def get_issues_overview(issues, cross_segment_issues, config_users, config_passwords): - issues_byte_array = [False] * 7 + def get_issues_overview(issues, config_users, config_passwords): + issues_byte_array = [False] * 6 for machine in issues: for issue in issues[machine]: @@ -414,22 +413,22 @@ class ReportService: elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True - if len(cross_segment_issues) != 0: - issues_byte_array[ReportService.ISSUES_DICT.CROSS_SEGMENT.value] = True - return issues_byte_array @staticmethod - def get_warnings_overview(issues): + def get_warnings_overview(issues, cross_segment_issues): warnings_byte_array = [False] * 2 for machine in issues: for issue in issues[machine]: if issue['type'] == 'island_cross_segment': - warnings_byte_array[ReportService.WARNINGS_DICT.ISLAND_CROSS_SEGMENT.value] = True + warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True elif issue['type'] == 'tunnel': warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True + if len(cross_segment_issues) != 0: + warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True + return warnings_byte_array @staticmethod @@ -465,15 +464,15 @@ class ReportService: 'config_scan': ReportService.get_config_scan(), 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_duration': ReportService.get_monkey_duration(), - 'issues': ReportService.get_issues_overview(issues, cross_segment_issues, config_users, config_passwords), - 'warnings': ReportService.get_warnings_overview(issues) + 'issues': ReportService.get_issues_overview(issues, config_users, config_passwords), + 'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues), + 'cross_segment_issues': cross_segment_issues }, 'glance': { 'scanned': ReportService.get_scanned(), 'exploited': ReportService.get_exploited(), - 'stolen_creds': ReportService.get_stolen_creds(), - 'cross_segment_issues': cross_segment_issues + 'stolen_creds': ReportService.get_stolen_creds() }, 'recommendations': { diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index 5c77f9c46..a71f764eb 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -26,7 +26,7 @@ class ReportPageComponent extends AuthComponent { Warning = { - ISLAND_CROSS_SEGMENT: 0, + CROSS_SEGMENT: 0, TUNNEL: 1 }; @@ -333,7 +333,7 @@ class ReportPageComponent extends AuthComponent {
    The Monkey uncovered the following possible set of issues:
      - {this.state.report.overview.warnings[this.Warning.ISLAND_CROSS_SEGMENT] ? + {this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ?
    • Weak segmentation - Machines from different segments are able to communicate.
    • : null} {this.state.report.overview.warnings[this.Warning.TUNNEL] ? @@ -603,7 +603,7 @@ class ReportPageComponent extends AuthComponent { ); } - generateCrossSegmentIssue(issue) { + generateIslandCrossSegmentIssue(issue) { return (
    • Segment your network and make sure there is no communication between machines from different segments. From 3efc638d1f5b864bb63d34f6caf6d6bf19fd39d0 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 27 Feb 2018 15:54:45 +0200 Subject: [PATCH 09/13] Add segmentation issues section --- monkey_island/cc/services/report.py | 1 + .../cc/ui/src/components/pages/ReportPage.js | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index b4d604c3a..de0210bc4 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -307,6 +307,7 @@ class ReportService: cross_segment_issues.append( { 'source': cross_segment_ip, + 'hostname': monkey['hostname'], 'target': target_ip, 'services': scan['data']['machine']['services'] }) diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index a71f764eb..0e57b2ac4 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -346,6 +346,21 @@ class ReportPageComponent extends AuthComponent {
    }
+ { this.state.report.overview.cross_segment_issues.length > 0 ? +
+

+ Segmentation Issues +

+
+ The Monkey uncovered the following set of segmentation issues: +
    + {this.state.report.overview.cross_segment_issues.map(x => this.generateCrossSegmentIssue(x))} +
+
+
+ : + '' + } ); } @@ -429,6 +444,22 @@ class ReportPageComponent extends AuthComponent { return data_array.map(badge_data => {badge_data}); } + generateCrossSegmentIssue(crossSegmentIssue) { + return
  • + {'Communication possible from ' + crossSegmentIssue['source_subnet'] + ' to ' + crossSegmentIssue['target_subnet']} + +
      + {crossSegmentIssue['issues'].map(x => +
    • + {'IP ' + x['source'] + ' (' + x['hostname'] + ') connected to IP ' + x['target'] + + ' using the services: ' + Object.keys(x['services']).join(', ')} +
    • + )} +
    +
    +
  • ; + } + generateShellshockPathListBadges(paths) { return paths.map(path => {path}); } From c91aee3129aa536caeeaec52582f36de80c16e95 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 23 May 2018 12:27:06 +0300 Subject: [PATCH 10/13] Add documentation --- infection_monkey/network/network_scanner.py | 6 +++++ monkey_island/cc/services/report.py | 28 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index b4cc6a8e6..65fd473b3 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -40,6 +40,12 @@ class NetworkScanner(object): LOG.info("Base local networks to scan are: %r", self._ranges) def _get_inaccessible_subnets_ips(self): + """ + For each of the machine's IPs, checks if it's in one of the subnet groups specified in the + 'inaccessible_subnet_groups' config value. If so, all other subnets in the same group shouldn't be accessible. + All these subnets are returned. + :return: A list of subnets that shouldn't be accessible from the machine the monkey is running on. + """ subnets_to_scan = [] for subnet_group in WormConfiguration.inaccessible_subnet_groups: for subnet_str in subnet_group: diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index 1b6aaac75..a4dcb7f57 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -313,7 +313,14 @@ class ReportService: return issues @staticmethod - def get_cross_segment_ip(ip_addresses, source_subnet, target_subnet): + def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): + """ + Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet. + :param ip_addresses: List of IP addresses to test. + :param source_subnet: Subnet to want an IP to not be in. + :param target_subnet: Subnet we want an IP to be in. + :return: + """ for ip_address in ip_addresses: if target_subnet.is_in_range(ip_address): return None @@ -324,6 +331,13 @@ class ReportService: @staticmethod def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet): + """ + Gets list of cross segment issues from source_subnet to target_subnet. + :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + :param source_subnet: The subnet which shouldn't be able to access target_subnet. + :param target_subnet: The subnet which shouldn't be accessible from source_subnet. + :return: + """ if source_subnet == target_subnet: return [] source_subnet_range = NetworkRange.get_range_obj(source_subnet) @@ -335,8 +349,10 @@ class ReportService: target_ip = scan['data']['machine']['ip_addr'] if target_subnet_range.is_in_range(unicode(target_ip)): monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) - cross_segment_ip = ReportService.get_cross_segment_ip(monkey['ip_addresses'], source_subnet_range, - target_subnet_range) + cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], + source_subnet_range, + target_subnet_range) + if cross_segment_ip is not None: cross_segment_issues.append( { @@ -350,6 +366,12 @@ class ReportService: @staticmethod def get_cross_segment_issues_per_subnet_group(scans, subnet_group): + """ + Gets list of cross segment issues within given subnet_group. + :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + :param subnet_group: List of subnets which shouldn't be accessible from each other. + :return: Cross segment issues regarding the subnets in the group. + """ cross_segment_issues = [] for subnet_pair in itertools.product(subnet_group, subnet_group): From d831769d1fec1afd5d97a34c5c457d508fe8e333 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 23 May 2018 18:06:30 +0300 Subject: [PATCH 11/13] Fix CR --- monkey_island/cc/services/config.py | 11 +++- monkey_island/cc/services/report.py | 61 +++++++++++++++++-- .../cc/ui/src/components/pages/ReportPage.js | 13 ++-- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 8294e0bb3..5bddc1901 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -241,9 +241,14 @@ SCHEMA = { "default": [ ], "description": - "List of IP/subnet groups. Each group should consist of subnets that aren't supposed" - " to be accessible to one another. If the monkey is in one subnet it'll scan the other" - " subnets in the same group." + "You can use this feature to test for network segmentation, by proving lists of" + " IP/subnet groups that should not be accessible to each other. Each input group" + " consists of subnets that should not be accessible to each other. If the Monkey" + " is inside of one of the subnets it will attempt to connect to machines in the" + " other subnet." + " Example, by providing input 192.168.1.0/24, 192.168.2.0/24, 192.168.3.1-192.168.3.10," + " a Monkey with the IP address 192.168.2.5 will try to access machines inside" + " 192.168.1.0/24 or 192.168.3.1-192.168.3.10." } } } diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index a4dcb7f57..ba2fc56b3 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -1,4 +1,5 @@ import itertools +import functools import ipaddress from enum import Enum @@ -329,11 +330,52 @@ class ReportService: return ip_address return None + @staticmethod + def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range): + """ + Gets list of cross segment issues of a single machine. Meaning a machine has an interface for each of the + subnets. + :param source_subnet_range: The subnet range which shouldn't be able to access target_subnet. + :param target_subnet_range: The subnet range which shouldn't be accessible from source_subnet. + :return: + """ + cross_segment_issues = [] + + for monkey in mongo.db.monkey.find({}, {'ip_addresses': 1, 'hostname': 1}): + ip_in_src = None + ip_in_dst = None + for ip_addr in monkey['ip_addresses']: + if source_subnet_range.is_in_range(unicode(ip_addr)): + ip_in_src = ip_addr + break + + # No point searching the dst subnet if there are no IPs in src subnet. + if not ip_in_src: + continue + + for ip_addr in monkey['ip_addresses']: + if target_subnet_range.is_in_range(unicode(ip_addr)): + ip_in_dst = ip_addr + break + + if ip_in_dst: + cross_segment_issues.append( + { + 'source': ip_in_src, + 'hostname': monkey['hostname'], + 'target': ip_in_dst, + 'services': None, + 'is_self': True + }) + + return cross_segment_issues + @staticmethod def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet): """ Gets list of cross segment issues from source_subnet to target_subnet. :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + This should be a PyMongo cursor object. :param source_subnet: The subnet which shouldn't be able to access target_subnet. :param target_subnet: The subnet which shouldn't be accessible from source_subnet. :return: @@ -345,6 +387,7 @@ class ReportService: cross_segment_issues = [] + scans.rewind() # If we iterated over scans already we need to rewind. for scan in scans: target_ip = scan['data']['machine']['ip_addr'] if target_subnet_range.is_in_range(unicode(target_ip)): @@ -359,16 +402,19 @@ class ReportService: 'source': cross_segment_ip, 'hostname': monkey['hostname'], 'target': target_ip, - 'services': scan['data']['machine']['services'] + 'services': scan['data']['machine']['services'], + 'is_self': False }) - return cross_segment_issues + return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine( + source_subnet_range, target_subnet_range) @staticmethod def get_cross_segment_issues_per_subnet_group(scans, subnet_group): """ Gets list of cross segment issues within given subnet_group. :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + This should be a PyMongo cursor object. :param subnet_group: List of subnets which shouldn't be accessible from each other. :return: Cross segment issues regarding the subnets in the group. """ @@ -405,8 +451,15 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() \ - + ReportService.get_island_cross_segment_issues() + ReportService.get_azure_issues() + ISSUE_GENERATORS = [ + ReportService.get_exploits, + ReportService.get_tunnels, + ReportService.get_island_cross_segment_issues, + ReportService.get_azure_issues + ] + + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + issues_dict = {} for issue in issues: machine = issue['machine'] diff --git a/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey_island/cc/ui/src/components/pages/ReportPage.js index d92b06f7d..278d6152f 100644 --- a/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -456,10 +456,15 @@ class ReportPageComponent extends AuthComponent {
      {crossSegmentIssue['issues'].map(x => -
    • - {'IP ' + x['source'] + ' (' + x['hostname'] + ') connected to IP ' + x['target'] - + ' using the services: ' + Object.keys(x['services']).join(', ')} -
    • + x['is_self'] ? +
    • + {'Machine ' + x['hostname'] + ' has both ips: ' + x['source'] + ' and ' + x['target']} +
    • + : +
    • + {'IP ' + x['source'] + ' (' + x['hostname'] + ') connected to IP ' + x['target'] + + ' using the services: ' + Object.keys(x['services']).join(', ')} +
    • )}
    From bafa0e42a00ad7079274ccdd8d40cc567e803582 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 21 Aug 2018 11:34:26 +0300 Subject: [PATCH 12/13] Make feature simpler Change config value phrasing --- infection_monkey/config.py | 2 +- infection_monkey/network/network_scanner.py | 10 +++---- monkey_island/cc/services/config.py | 31 ++++++++------------- monkey_island/cc/services/report.py | 2 +- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/infection_monkey/config.py b/infection_monkey/config.py index 328bbf719..72586a231 100644 --- a/infection_monkey/config.py +++ b/infection_monkey/config.py @@ -185,7 +185,7 @@ class Configuration(object): local_network_scan = True subnet_scan_list = ['', ] - inaccessible_subnet_groups = [] + inaccessible_subnets = [] blocked_ips = ['', ] diff --git a/infection_monkey/network/network_scanner.py b/infection_monkey/network/network_scanner.py index 65fd473b3..20133a9a7 100644 --- a/infection_monkey/network/network_scanner.py +++ b/infection_monkey/network/network_scanner.py @@ -41,18 +41,18 @@ class NetworkScanner(object): def _get_inaccessible_subnets_ips(self): """ - For each of the machine's IPs, checks if it's in one of the subnet groups specified in the - 'inaccessible_subnet_groups' config value. If so, all other subnets in the same group shouldn't be accessible. + For each of the machine's IPs, checks if it's in one of the subnets specified in the + 'inaccessible_subnets' config value. If so, all other subnets in the config value shouldn't be accessible. All these subnets are returned. :return: A list of subnets that shouldn't be accessible from the machine the monkey is running on. """ subnets_to_scan = [] - for subnet_group in WormConfiguration.inaccessible_subnet_groups: - for subnet_str in subnet_group: + if len(WormConfiguration.inaccessible_subnets) > 1: + for subnet_str in WormConfiguration.inaccessible_subnets: if NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], subnet_str): # If machine has IPs from 2 different subnets in the same group, there's no point checking the other # subnet. - for other_subnet_str in subnet_group: + for other_subnet_str in WormConfiguration.inaccessible_subnets: if other_subnet_str == subnet_str: continue if not NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], diff --git a/monkey_island/cc/services/config.py b/monkey_island/cc/services/config.py index 5bddc1901..b00bfe61c 100644 --- a/monkey_island/cc/services/config.py +++ b/monkey_island/cc/services/config.py @@ -222,33 +222,24 @@ SCHEMA = { "title": "Network Analysis", "type": "object", "properties": { - "inaccessible_subnet_groups": { - "title": "Inaccessible IP/subnet groups", + "inaccessible_subnets": { + "title": "Network segmentation testing", "type": "array", "uniqueItems": True, "items": { - "type": "array", - "title": "Subnet group", - "items": { - "type": "string" - }, - "minItems": 2, - "uniqueItems": True, - "description": "List of IPs/subnets." - " Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\"," - " \"192.168.0.5/24\"" + "type": "string" }, "default": [ ], "description": - "You can use this feature to test for network segmentation, by proving lists of" - " IP/subnet groups that should not be accessible to each other. Each input group" - " consists of subnets that should not be accessible to each other. If the Monkey" - " is inside of one of the subnets it will attempt to connect to machines in the" - " other subnet." - " Example, by providing input 192.168.1.0/24, 192.168.2.0/24, 192.168.3.1-192.168.3.10," - " a Monkey with the IP address 192.168.2.5 will try to access machines inside" - " 192.168.1.0/24 or 192.168.3.1-192.168.3.10." + "Test for network segmentation by providing a list of" + " subnets that should NOT be accessible to each other." + " For example, given the following configuration:" + " '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'" + " a Monkey running on 10.0.0.5 will try to access machines in the following" + " subnets: 11.0.0.2/32, 12.2.3.0/24." + " An alert on successful connections will be shown in the report" + " Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5" } } } diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index ba2fc56b3..159e52476 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -442,7 +442,7 @@ class ReportService: cross_segment_issues = [] subnet_groups = ConfigService.get_config_value( - ['basic_network', 'network_analysis', 'inaccessible_subnet_groups']) + ['basic_network', 'network_analysis', 'inaccessible_subnets']) for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) From 369795e3751754b5723adf8ad907b533c843df71 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Tue, 21 Aug 2018 17:17:21 +0300 Subject: [PATCH 13/13] small fixes to make everything work --- monkey_island/cc/services/report.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/monkey_island/cc/services/report.py b/monkey_island/cc/services/report.py index a49708c81..192d7d140 100644 --- a/monkey_island/cc/services/report.py +++ b/monkey_island/cc/services/report.py @@ -488,14 +488,14 @@ class ReportService: return cross_segment_issues @staticmethod - def get_cross_segement_issues(): + def get_cross_segment_issues(): scans = mongo.db.telemetry.find({'telem_type': 'scan'}, {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1}) cross_segment_issues = [] - subnet_groups = ConfigService.get_config_value( - ['basic_network', 'network_analysis', 'inaccessible_subnets']) + # For now the feature is limited to 1 group. + subnet_groups = [ConfigService.get_config_value(['basic_network', 'network_analysis', 'inaccessible_subnets'])] for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) @@ -504,11 +504,10 @@ class ReportService: @staticmethod def get_issues(): - issues = ReportService.get_exploits() + ReportService.get_tunnels() +\ ISSUE_GENERATORS = [ ReportService.get_exploits, ReportService.get_tunnels, - ReportService.get_cross_segment_issues, + ReportService.get_island_cross_segment_issues, ReportService.get_azure_issues ] @@ -622,7 +621,7 @@ class ReportService: issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() - cross_segment_issues = ReportService.get_cross_segement_issues() + cross_segment_issues = ReportService.get_cross_segment_issues() report = \ {