diff --git a/ScoutSuite/output/data/html/partials/azure/services.network.network_security_groups.html b/ScoutSuite/output/data/html/partials/azure/services.network.network_security_groups.html new file mode 100644 index 000000000..07338cc66 --- /dev/null +++ b/ScoutSuite/output/data/html/partials/azure/services.network.network_security_groups.html @@ -0,0 +1,80 @@ + + + + + + + + \ No newline at end of file diff --git a/ScoutSuite/output/data/html/partials/azure/services.network.network_watchers.html b/ScoutSuite/output/data/html/partials/azure/services.network.network_watchers.html new file mode 100644 index 000000000..675d67c9c --- /dev/null +++ b/ScoutSuite/output/data/html/partials/azure/services.network.network_watchers.html @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js b/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js index ba1bfdb6d..5d3b863a1 100644 --- a/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js +++ b/ScoutSuite/output/data/inc-scoutsuite/scoutsuite.js @@ -1018,6 +1018,8 @@ function make_title (title) { return 'SQL Database'; } else if (title == 'securitycenter') { return 'Security Center'; + } else if (title == 'network') { + return 'Network'; } else if (title == 'keyvault') { return 'Key Vault'; } else if (title == 'appgateway') { diff --git a/ScoutSuite/providers/azure/configs/services.py b/ScoutSuite/providers/azure/configs/services.py index 108c71443..94ccd9227 100644 --- a/ScoutSuite/providers/azure/configs/services.py +++ b/ScoutSuite/providers/azure/configs/services.py @@ -5,6 +5,7 @@ from ScoutSuite.providers.azure.services.monitor import MonitorConfig from ScoutSuite.providers.azure.services.sqldatabase import SQLDatabaseConfig from ScoutSuite.providers.azure.services.securitycenter import SecurityCenterConfig +from ScoutSuite.providers.azure.services.network import NetworkConfig from ScoutSuite.providers.azure.services.keyvault import KeyVaultConfig try: from ScoutSuite.providers.azure.services.appgateway_private import AppGatewayConfig @@ -24,6 +25,7 @@ def __init__(self, metadata=None, thread_config=4, **kwargs): self.monitor = MonitorConfig(thread_config=thread_config) self.sqldatabase = SQLDatabaseConfig(thread_config=thread_config) self.securitycenter = SecurityCenterConfig(thread_config=thread_config) + self.network = NetworkConfig(thread_config=thread_config) self.keyvault = KeyVaultConfig(thread_config=thread_config) try: diff --git a/ScoutSuite/providers/azure/metadata.json b/ScoutSuite/providers/azure/metadata.json index 4bb35baf7..317bc4fd5 100644 --- a/ScoutSuite/providers/azure/metadata.json +++ b/ScoutSuite/providers/azure/metadata.json @@ -55,6 +55,18 @@ "path": "services.appgateway.app_gateways" } } + }, + "network": { + "resources": { + "network_watchers": { + "cols": 2, + "path": "services.network.network_watchers" + }, + "network_security_groups": { + "cols": 2, + "path": "services.network.network_security_groups" + } + } } }, "redis": { diff --git a/ScoutSuite/providers/azure/rules/conditions/allow-tcp.json b/ScoutSuite/providers/azure/rules/conditions/allow-tcp.json new file mode 100644 index 000000000..135b02e26 --- /dev/null +++ b/ScoutSuite/providers/azure/rules/conditions/allow-tcp.json @@ -0,0 +1,7 @@ +{ + "conditions":[ + "or", + ["network.network_security_groups.id.security_rules.id.protocol", "equal", "*"], + ["network.network_security_groups.id.security_rules.id.protocol", "equal", "TCP"] + ] +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/conditions/exposed-to-the-internet.json b/ScoutSuite/providers/azure/rules/conditions/exposed-to-the-internet.json new file mode 100644 index 000000000..bacdf3694 --- /dev/null +++ b/ScoutSuite/providers/azure/rules/conditions/exposed-to-the-internet.json @@ -0,0 +1,12 @@ +{ + "conditions":[ + "and", + ["network.network_security_groups.id.security_rules.id.allow", "true", ""], + ["network.network_security_groups.id.security_rules.id.direction", "equal", "Inbound"], + [ + "or", + ["network.network_security_groups.id.security_rules.id.source_address_prefixes", "containAtLeastOneOf", "*"], + ["network.network_security_groups.id.security_rules.id.source_address_prefixes", "containAtLeastOneOf", "Internet"] + ] + ] +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/findings/network-security-groups-rule-inbound-service.json b/ScoutSuite/providers/azure/rules/findings/network-security-groups-rule-inbound-service.json new file mode 100644 index 000000000..53e233e8b --- /dev/null +++ b/ScoutSuite/providers/azure/rules/findings/network-security-groups-rule-inbound-service.json @@ -0,0 +1,15 @@ +{ + "dashboard_name": "Network", + "arg_names": [ "Protocol (ex: SSH, RDP, etc.)", "Protocol's port", "Associated CIS rule" ], + "key": "network-security-groups-rule-inbound-_ARG_0_", + "description": "Security rule allowing _ARG_0_ inbound access in security group", + "rationale": "You should not permit _ARG_0_(port _ARG_1_) inbound access to a network security group (CIS _ARG_2_).", + "path": "network.network_security_groups.id.security_rules.id", + "display_path": "network.network_security_groups.id", + "conditions": [ "and", + ["network.network_security_groups.id.security_rules.id.destination_ports", "containAtLeastOneOf", "_ARG_1_"], + ["_INCLUDE_(conditions/exposed-to-the-internet.json)", "", ""], + ["_INCLUDE_(conditions/allow-tcp.json)", "", ""] + ], + "id_suffix": "security_groups_rule_inbound__ARG_0_" +} diff --git a/ScoutSuite/providers/azure/rules/findings/network-watcher-not-enabled.json b/ScoutSuite/providers/azure/rules/findings/network-watcher-not-enabled.json new file mode 100644 index 000000000..1f3fd9bb6 --- /dev/null +++ b/ScoutSuite/providers/azure/rules/findings/network-watcher-not-enabled.json @@ -0,0 +1,11 @@ +{ + "dashboard_name": "Network", + "description": "Network watcher not enabled", + "rationale": "Network watchers should be enabled (CIS 6.5).", + "path": "network.network_watchers", + "display_path": "network.network_watchers", + "conditions": [ "and", + ["network.network_watchers", "empty", ""] + ], + "id_suffix": "network_watchers_disabled" +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/findings/network-watcher-not-provisioned.json b/ScoutSuite/providers/azure/rules/findings/network-watcher-not-provisioned.json new file mode 100644 index 000000000..d84ca1e9b --- /dev/null +++ b/ScoutSuite/providers/azure/rules/findings/network-watcher-not-provisioned.json @@ -0,0 +1,11 @@ +{ + "dashboard_name": "Network", + "description": "Network watcher not provisioned", + "rationale": "Network watchers should be provisioned to work (CIS 6.5).", + "path": "network.network_watchers.id", + "display_path": "network.network_watchers.id", + "conditions": [ "and", + ["network.network_watchers.id.provisioning_state", "notEqual", "Succeeded"] + ], + "id_suffix": "network_watchers_not_provisioned" +} \ No newline at end of file diff --git a/ScoutSuite/providers/azure/rules/rulesets/default.json b/ScoutSuite/providers/azure/rules/rulesets/default.json index eda5c2775..c79203c49 100644 --- a/ScoutSuite/providers/azure/rules/rulesets/default.json +++ b/ScoutSuite/providers/azure/rules/rulesets/default.json @@ -1,6 +1,47 @@ { "about": "Default ruleset for Azure.", "rules": { + "network-security-groups-rule-inbound-service.json": [ + { + "args": [ + "RDP", + "3389", + "6.1" + ], + "enabled": true, + "level": "warning" + }, + { + "args": [ + "SSH", + "22", + "6.2" + ], + "enabled": true, + "level": "warning" + }, + { + "args": [ + "SQL", + "1433", + "6.3" + ], + "enabled": true, + "level": "warning" + } + ], + "network-watcher-not-enabled.json": [ + { + "enabled": true, + "level": "warning" + } + ], + "network-watcher-not-provisioned.json": [ + { + "enabled": true, + "level": "warning" + } + ], "storageaccount-account-allowing-clear-text.json": [ { "enabled": true, diff --git a/ScoutSuite/providers/azure/services/network.py b/ScoutSuite/providers/azure/services/network.py new file mode 100644 index 000000000..a23bb0a9b --- /dev/null +++ b/ScoutSuite/providers/azure/services/network.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +from ScoutSuite.providers.azure.configs.base import AzureBaseConfig + + +class NetworkConfig(AzureBaseConfig): + targets = ( + ('network_watchers', 'Network Watchers', 'list_all', {}, False), + ('network_security_groups', 'Network Security Group', 'list_all', {}, False), + ) + + def __init__(self, thread_config): + self.network_watchers = {} + self.network_watchers_count = 0 + + self.network_security_groups = {} + self.network_security_groups_count = 0 + + super(NetworkConfig, self).__init__(thread_config) + + def parse_network_watchers(self, network_watcher, params): + network_watcher_dict = {} + network_watcher_dict['id'] = network_watcher.id + network_watcher_dict['name'] = network_watcher.name + network_watcher_dict['provisioning_state'] = network_watcher.provisioning_state + network_watcher_dict['location'] = network_watcher.location + network_watcher_dict['etag'] = network_watcher.etag + + self.network_watchers[network_watcher_dict['id']] = network_watcher_dict + + def parse_network_security_groups(self, network_security_group, params): + network_security_group_dict = {} + network_security_group_dict['id'] = network_security_group.id + network_security_group_dict['name'] = network_security_group.name + network_security_group_dict['provisioning_state'] = network_security_group.provisioning_state + network_security_group_dict['location'] = network_security_group.location + network_security_group_dict['resource_guid'] = network_security_group.resource_guid + network_security_group_dict['etag'] = network_security_group.etag + + network_security_group_dict['security_rules'] = self._parse_security_rules(network_security_group) + + exposed_ports = self._parse_exposed_ports(network_security_group) + network_security_group_dict['exposed_ports'] = exposed_ports + network_security_group_dict['exposed_port_ranges'] = self._format_ports(exposed_ports) + + self.network_security_groups[network_security_group_dict['id']] = network_security_group_dict + + def _parse_security_rules(self, network_security_group): + security_rules = {} + for sr in network_security_group.security_rules: + security_rule_dict = {} + security_rule_dict['id'] = sr.id + security_rule_dict['name'] = sr.name + security_rule_dict['allow'] = sr.access == "Allow" + security_rule_dict['priority'] = sr.priority + security_rule_dict['description'] = sr.description + security_rule_dict['provisioning_state'] = sr.provisioning_state + + security_rule_dict['protocol'] = sr.protocol + security_rule_dict['direction'] = sr.direction + + source_address_prefixes = self._merge_prefixes_or_ports(sr.source_address_prefix, + sr.source_address_prefixes) + security_rule_dict['source_address_prefixes'] = source_address_prefixes + + source_port_ranges = self._merge_prefixes_or_ports(sr.source_port_range, sr.source_port_ranges) + security_rule_dict['source_port_ranges'] = source_port_ranges + security_rule_dict['source_ports'] = self._parse_ports(source_port_ranges) + + destination_address_prefixes = self._merge_prefixes_or_ports(sr.destination_address_prefix, + sr.destination_address_prefixes) + security_rule_dict['destination_address_prefixes'] = destination_address_prefixes + + destination_port_ranges = self._merge_prefixes_or_ports(sr.destination_port_range, + sr.destination_port_ranges) + security_rule_dict['destination_port_ranges'] = destination_port_ranges + security_rule_dict['destination_ports'] = self._parse_ports(destination_port_ranges) + + security_rule_dict['etag'] = sr.etag + + security_rules[security_rule_dict['id']] = security_rule_dict + + return security_rules + + def _parse_ports(self, port_ranges): + ports = set() + for pr in port_ranges: + if pr == "*": + for p in range(0, 65535 + 1): + ports.add(p) + break + elif "-" in pr: + lower, upper = pr.split("-") + for p in range(int(lower), int(upper) + 1): + ports.add(p) + else: + ports.add(int(pr)) + ports = list(ports) + ports.sort() + return ports + + def _parse_exposed_ports(self, network_security_group): + exposed_ports = set() + + # Sort by priority. + rules = network_security_group.default_security_rules + network_security_group.security_rules + rules.sort(key=lambda x: x.priority, reverse=True) + + for sr in rules: + if sr.direction == "Inbound" and (sr.source_address_prefix == "*" + or sr.source_address_prefix == "Internet"): + port_ranges = self._merge_prefixes_or_ports(sr.destination_port_range, + sr.destination_port_ranges) + ports = self._parse_ports(port_ranges) + if sr.access == "Allow": + for p in ports: + exposed_ports.add(p) + else: + for p in ports: + exposed_ports.discard(p) + exposed_ports = list(exposed_ports) + exposed_ports.sort() + return exposed_ports + + def _merge_prefixes_or_ports(self, port_range, port_ranges): + port_ranges = port_ranges if port_ranges else [] + if port_range: + port_ranges.append(port_range) + return port_ranges + + def _format_ports(self, ports): + port_ranges = [] + start = None + for i in range(0, 65535 + 1): + if i in ports: + if not start: + start = i + else: + if start: + if i - 1 == start: + port_ranges.append(str(start)) + else: + port_ranges.append(str(start) + "-" + str(i - 1)) + start = None + return port_ranges diff --git a/ScoutSuite/providers/azure/utils.py b/ScoutSuite/providers/azure/utils.py index 733539332..cbdfe5266 100644 --- a/ScoutSuite/providers/azure/utils.py +++ b/ScoutSuite/providers/azure/utils.py @@ -13,6 +13,7 @@ from azure.mgmt.network import NetworkManagementClient from azure.mgmt.redis import RedisManagementClient + def azure_connect_service(service, credentials, region_name=None): try: if service == 'storageaccounts': @@ -25,6 +26,8 @@ def azure_connect_service(service, credentials, region_name=None): return KeyVaultManagementClient(credentials.credentials, credentials.subscription_id) elif service == 'appgateway': return NetworkManagementClient(credentials.credentials, credentials.subscription_id) + elif service == 'network': + return NetworkManagementClient(credentials.credentials, credentials.subscription_id) elif service == 'rediscache': return RedisManagementClient(credentials.credentials, credentials.subscription_id) elif service == 'securitycenter': diff --git a/ScoutSuite/utils.py b/ScoutSuite/utils.py index 283fbac30..f4eb65abd 100644 --- a/ScoutSuite/utils.py +++ b/ScoutSuite/utils.py @@ -36,7 +36,8 @@ 'securitycenter': 'Security Center', 'keyvault': 'Key Vault', 'appgateway': 'Application Gateway', - 'rediscache': 'Redis Cache' + 'rediscache': 'Redis Cache', + 'network': 'Network', }