From 23a0f74b2b4c0b1a58c4a585314480664047e771 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 8 Dec 2021 09:30:44 -0500 Subject: [PATCH 01/18] Agent: Add initial compile_scan_target_list() For the moment, this only handles the ranges_to_scan parameter. Other parameters need to be handled. --- .../network/scan_target_generator.py | 25 +++++++ .../network/test_scan_target_generator.py | 65 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 monkey/infection_monkey/network/scan_target_generator.py create mode 100644 monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py new file mode 100644 index 000000000..0aac33cc9 --- /dev/null +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -0,0 +1,25 @@ +from typing import List + +from common.network.network_range import NetworkRange + + +def compile_scan_target_list( + local_ips: List[str], + ranges_to_scan: List[str], + inaccessible_subnets: List[str], + blocklisted_ips: List[str], + enable_local_network_scan: bool, +) -> List[str]: + scan_target_list = _get_ips_from_ranges_to_scan(ranges_to_scan) + + return scan_target_list + + +def _get_ips_from_ranges_to_scan(ranges_to_scan): + scan_target_list = [] + + network_ranges = [NetworkRange.get_range_obj(_range) for _range in ranges_to_scan] + for _range in network_ranges: + scan_target_list.extend([ip for ip in _range]) + + return scan_target_list diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py new file mode 100644 index 000000000..89a0775d8 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -0,0 +1,65 @@ +import pytest + +from infection_monkey.network.scan_target_generator import compile_scan_target_list + + +def compile_ranges_only(ranges): + return compile_scan_target_list( + local_ips=[], + ranges_to_scan=ranges, + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + +def test_single_subnet(): + scan_targets = compile_ranges_only(["10.0.0.0/24"]) + + assert len(scan_targets) == 255 + + for i in range(0, 255): + assert f"10.0.0.{i}" in scan_targets + + +@pytest.mark.parametrize("single_ip", ["10.0.0.2", "10.0.0.2/32", "10.0.0.2-10.0.0.2"]) +def test_single_ip(single_ip): + print(single_ip) + scan_targets = compile_ranges_only([single_ip]) + + assert len(scan_targets) == 1 + assert "10.0.0.2" in scan_targets + assert "10.0.0.2" == scan_targets[0] + + +def test_multiple_subnet(): + scan_targets = compile_ranges_only(["10.0.0.0/24", "192.168.56.8/29"]) + + assert len(scan_targets) == 262 + + for i in range(0, 255): + assert f"10.0.0.{i}" in scan_targets + + for i in range(8, 15): + assert f"192.168.56.{i}" in scan_targets + + +def test_middle_of_range_subnet(): + scan_targets = compile_ranges_only(["192.168.56.4/29"]) + + assert len(scan_targets) == 7 + + for i in range(0, 7): + assert f"192.168.56.{i}" in scan_targets + + +@pytest.mark.parametrize( + "ip_range", ["192.168.56.25-192.168.56.33", "192.168.56.25 - 192.168.56.33"] +) +def test_ip_range(ip_range): + scan_targets = compile_ranges_only([ip_range]) + + assert len(scan_targets) == 9 + + for i in range(25, 34): + assert f"192.168.56.{i}" in scan_targets From a0d679285c41028f0df4ef6fc8e4991238a0e2f7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 8 Dec 2021 16:34:52 +0200 Subject: [PATCH 02/18] Agent: strip whitespace from IP's when generating a list of ip's This fixes a bug where get_range_object("10.0.0.0 - 10.0.0.6") doesn't work because of the whitespaces --- monkey/common/network/network_range.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 1ab37ba57..1ab199943 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -54,6 +54,7 @@ class NetworkRange(object, metaclass=ABCMeta): def check_if_range(address_str): if -1 != address_str.find("-"): ips = address_str.split("-") + ips = [ip.strip() for ip in ips] try: ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) except ValueError: From 8d383d28329fb8283132c4d20bbd08cb44488ed8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 8 Dec 2021 09:41:23 -0500 Subject: [PATCH 03/18] Agent: Remove duplicate IPs in compile_scan_target_list() --- .../network/scan_target_generator.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 0aac33cc9..41b570622 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Set from common.network.network_range import NetworkRange @@ -10,16 +10,18 @@ def compile_scan_target_list( blocklisted_ips: List[str], enable_local_network_scan: bool, ) -> List[str]: - scan_target_list = _get_ips_from_ranges_to_scan(ranges_to_scan) + scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) + scan_target_list = list(scan_targets) + scan_target_list.sort() return scan_target_list -def _get_ips_from_ranges_to_scan(ranges_to_scan): - scan_target_list = [] +def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: + scan_targets = set() network_ranges = [NetworkRange.get_range_obj(_range) for _range in ranges_to_scan] for _range in network_ranges: - scan_target_list.extend([ip for ip in _range]) + scan_targets.update(set(_range)) - return scan_target_list + return scan_targets From 913ba02e0bd9bef0a49241fd563369449d3eaa7e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 8 Dec 2021 09:55:48 -0500 Subject: [PATCH 04/18] Agent: Remove blocklisted IPs from scan targets --- .../network/scan_target_generator.py | 12 ++++++ .../network/test_scan_target_generator.py | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 41b570622..cdcfbdb31 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -12,8 +12,11 @@ def compile_scan_target_list( ) -> List[str]: scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) + _remove_blocklisted_ips(scan_targets, blocklisted_ips) + scan_target_list = list(scan_targets) scan_target_list.sort() + return scan_target_list @@ -25,3 +28,12 @@ def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: scan_targets.update(set(_range)) return scan_targets + + +def _remove_blocklisted_ips(scan_targets: Set[str], blocked_ips: List[str]): + for blocked_ip in blocked_ips: + try: + scan_targets.remove(blocked_ip) + except KeyError: + # We don't need to remove the blocked ip if it's already missing from the scan_targets + pass diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 89a0775d8..9e1b5fc0b 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -63,3 +63,43 @@ def test_ip_range(ip_range): for i in range(25, 34): assert f"192.168.56.{i}" in scan_targets + + +def test_no_duplicates(): + scan_targets = compile_ranges_only(["192.168.56.0/29", "192.168.56.2", "192.168.56.4"]) + + assert len(scan_targets) == 7 + + for i in range(0, 7): + assert f"192.168.56.{i}" in scan_targets + + +def test_blocklisted_ips(): + blocklisted_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + + scan_targets = compile_scan_target_list( + local_ips=[], + ranges_to_scan=["10.0.0.0/24"], + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 252 + for blocked_ip in blocklisted_ips: + assert blocked_ip not in scan_targets + + +@pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) +def test_only_ip_blocklisted(ranges_to_scan): + blocklisted_ips = ["10.0.0.5"] + + scan_targets = compile_scan_target_list( + local_ips=[], + ranges_to_scan=ranges_to_scan, + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 From 4bc07442acc1e968e1577b3c144e5e63270f0869 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 8 Dec 2021 17:27:38 +0200 Subject: [PATCH 05/18] Agent: fix network_range.py to generate a correct range object for ip strings with /32 cidr notation This will fix the case where user inputs 10.0.0.10/32 expecting 10.0.0.10 getting scanned, but getting an error instead --- monkey/common/network/network_range.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 1ab199943..e58fffcec 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -44,9 +44,11 @@ class NetworkRange(object, metaclass=ABCMeta): if not address_str: # Empty string return None address_str = address_str.strip() + if address_str.endswith("/32"): + address_str = address_str[:-3] if NetworkRange.check_if_range(address_str): return IpRange(ip_range=address_str) - if -1 != address_str.find("/"): + if "/" in address_str: return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) From 244bde320d53d481bfb3e748827c65f64e840a8a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 8 Dec 2021 10:12:37 -0500 Subject: [PATCH 06/18] Agent: Remove local IPs from scan targets --- .../network/scan_target_generator.py | 15 ++++-- .../network/test_scan_target_generator.py | 52 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index cdcfbdb31..862e37aef 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -12,6 +12,7 @@ def compile_scan_target_list( ) -> List[str]: scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) + _remove_local_ips(scan_targets, local_ips) _remove_blocklisted_ips(scan_targets, blocklisted_ips) scan_target_list = list(scan_targets) @@ -30,10 +31,18 @@ def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: return scan_targets +def _remove_local_ips(scan_targets: Set[str], local_ips: List[str]): + _remove_ips_from_scan_targets(scan_targets, local_ips) + + def _remove_blocklisted_ips(scan_targets: Set[str], blocked_ips: List[str]): - for blocked_ip in blocked_ips: + _remove_ips_from_scan_targets(scan_targets, blocked_ips) + + +def _remove_ips_from_scan_targets(scan_targets: Set[str], ips_to_remove: List[str]): + for ip in ips_to_remove: try: - scan_targets.remove(blocked_ip) + scan_targets.remove(ip) except KeyError: - # We don't need to remove the blocked ip if it's already missing from the scan_targets + # We don't need to remove the ip if it's already missing from the scan_targets pass diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 9e1b5fc0b..089644187 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -103,3 +103,55 @@ def test_only_ip_blocklisted(ranges_to_scan): ) assert len(scan_targets) == 0 + + +def test_local_ips_removed_from_targets(): + local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + + scan_targets = compile_scan_target_list( + local_ips=local_ips, + ranges_to_scan=["10.0.0.0/24"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 252 + for ip in local_ips: + assert ip not in scan_targets + + +@pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) +def test_only_scan_ip_is_local(ranges_to_scan): + local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + + scan_targets = compile_scan_target_list( + local_ips=local_ips, + ranges_to_scan=ranges_to_scan, + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_local_ips_and_blocked_ips_removed_from_targets(): + local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + blocked_ips = ["10.0.0.63", "192.168.1.77", "0.0.0.0"] + + scan_targets = compile_scan_target_list( + local_ips=local_ips, + ranges_to_scan=["10.0.0.0/24", "192.168.1.0/24"], + inaccessible_subnets=[], + blocklisted_ips=blocked_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == (2 * (256 - 1)) - len(local_ips) - (len(blocked_ips) - 1) + + for ip in local_ips: + assert ip not in scan_targets + + for ip in blocked_ips: + assert ip not in scan_targets From 27884ad44d99d1cbac59d82dad8933f3ff9e986e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 8 Dec 2021 11:14:14 -0500 Subject: [PATCH 07/18] Agent: Implement "enable_local_network_scan" IP address generation --- .../network/scan_target_generator.py | 24 ++- .../network/test_scan_target_generator.py | 180 ++++++++++++++++-- 2 files changed, 183 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 862e37aef..1f4e44e86 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,10 +1,17 @@ +from collections import namedtuple from typing import List, Set from common.network.network_range import NetworkRange +# TODO: Convert to class and validate the format of the address and netmask +# Example: address="192.168.1.1", netmask="/24" +NetworkInterface = namedtuple("NetworkInterface", ("address", "netmask")) + +# TODO: Validate all parameters +# TODO: Implement inaccessible_subnets def compile_scan_target_list( - local_ips: List[str], + local_network_interfaces: List[NetworkInterface], ranges_to_scan: List[str], inaccessible_subnets: List[str], blocklisted_ips: List[str], @@ -12,7 +19,10 @@ def compile_scan_target_list( ) -> List[str]: scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) - _remove_local_ips(scan_targets, local_ips) + if enable_local_network_scan: + scan_targets.update(_get_ips_to_scan_from_local_interface(local_network_interfaces)) + + _remove_interface_ips(scan_targets, local_network_interfaces) _remove_blocklisted_ips(scan_targets, blocklisted_ips) scan_target_list = list(scan_targets) @@ -31,8 +41,14 @@ def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: return scan_targets -def _remove_local_ips(scan_targets: Set[str], local_ips: List[str]): - _remove_ips_from_scan_targets(scan_targets, local_ips) +def _get_ips_to_scan_from_local_interface(interfaces: List[NetworkInterface]) -> Set[str]: + ranges = [f"{interface.address}{interface.netmask}" for interface in interfaces] + return _get_ips_from_ranges_to_scan(ranges) + + +def _remove_interface_ips(scan_targets: Set[str], interfaces: List[NetworkInterface]): + interface_ips = [interface.address for interface in interfaces] + _remove_ips_from_scan_targets(scan_targets, interface_ips) def _remove_blocklisted_ips(scan_targets: Set[str], blocked_ips: List[str]): diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 089644187..cf69b7a30 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -1,11 +1,14 @@ import pytest -from infection_monkey.network.scan_target_generator import compile_scan_target_list +from infection_monkey.network.scan_target_generator import ( + NetworkInterface, + compile_scan_target_list, +) def compile_ranges_only(ranges): return compile_scan_target_list( - local_ips=[], + local_network_interfaces=[], ranges_to_scan=ranges, inaccessible_subnets=[], blocklisted_ips=[], @@ -78,7 +81,7 @@ def test_blocklisted_ips(): blocklisted_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] scan_targets = compile_scan_target_list( - local_ips=[], + local_network_interfaces=[], ranges_to_scan=["10.0.0.0/24"], inaccessible_subnets=[], blocklisted_ips=blocklisted_ips, @@ -95,7 +98,7 @@ def test_only_ip_blocklisted(ranges_to_scan): blocklisted_ips = ["10.0.0.5"] scan_targets = compile_scan_target_list( - local_ips=[], + local_network_interfaces=[], ranges_to_scan=ranges_to_scan, inaccessible_subnets=[], blocklisted_ips=blocklisted_ips, @@ -105,11 +108,16 @@ def test_only_ip_blocklisted(ranges_to_scan): assert len(scan_targets) == 0 -def test_local_ips_removed_from_targets(): - local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] +def test_local_network_interface_ips_removed_from_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] scan_targets = compile_scan_target_list( - local_ips=local_ips, + local_network_interfaces=local_network_interfaces, ranges_to_scan=["10.0.0.0/24"], inaccessible_subnets=[], blocklisted_ips=[], @@ -117,16 +125,21 @@ def test_local_ips_removed_from_targets(): ) assert len(scan_targets) == 252 - for ip in local_ips: - assert ip not in scan_targets + for interface in local_network_interfaces: + assert interface.address not in scan_targets @pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) def test_only_scan_ip_is_local(ranges_to_scan): - local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] scan_targets = compile_scan_target_list( - local_ips=local_ips, + local_network_interfaces=local_network_interfaces, ranges_to_scan=ranges_to_scan, inaccessible_subnets=[], blocklisted_ips=[], @@ -136,22 +149,155 @@ def test_only_scan_ip_is_local(ranges_to_scan): assert len(scan_targets) == 0 -def test_local_ips_and_blocked_ips_removed_from_targets(): - local_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] +def test_local_network_interface_ips_and_blocked_ips_removed_from_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] blocked_ips = ["10.0.0.63", "192.168.1.77", "0.0.0.0"] scan_targets = compile_scan_target_list( - local_ips=local_ips, + local_network_interfaces=local_network_interfaces, ranges_to_scan=["10.0.0.0/24", "192.168.1.0/24"], inaccessible_subnets=[], blocklisted_ips=blocked_ips, enable_local_network_scan=False, ) - assert len(scan_targets) == (2 * (256 - 1)) - len(local_ips) - (len(blocked_ips) - 1) + assert len(scan_targets) == (2 * (256 - 1)) - len(local_network_interfaces) - ( + len(blocked_ips) - 1 + ) - for ip in local_ips: - assert ip not in scan_targets + for interface in local_network_interfaces: + assert interface.address not in scan_targets for ip in blocked_ips: assert ip not in scan_targets + + +def test_local_subnet_added(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 254 + + for ip in range(0, 5): + assert f"10.0.0.{ip} in scan_targets" + for ip in range(6, 255): + assert f"10.0.0.{ip} in scan_targets" + + +def test_multiple_local_subnets_added(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("172.33.66.99", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 2 * (255 - 1) + + for ip in range(0, 5): + assert f"10.0.0.{ip} in scan_targets" + for ip in range(6, 255): + assert f"10.0.0.{ip} in scan_targets" + + for ip in range(0, 99): + assert f"172.33.66.{ip} in scan_targets" + for ip in range(100, 255): + assert f"172.33.66.{ip} in scan_targets" + + +def test_blocklisted_ips_missing_from_local_subnets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("172.33.66.99", "/24"), + ] + blocklisted_ips = ["10.0.0.12", "10.0.0.13", "172.33.66.25"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 2 * (255 - 1) - len(blocklisted_ips) + + for ip in blocklisted_ips: + assert ip not in scan_targets + + +def test_local_subnets_and_ranges_added(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["172.33.66.40/30"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 254 + 3 + + for ip in range(0, 5): + assert f"10.0.0.{ip} in scan_targets" + for ip in range(6, 255): + assert f"10.0.0.{ip} in scan_targets" + + for ip in range(40, 43): + assert f"172.33.66.{ip} in scan_targets" + + +def test_local_network_interfaces_specified_but_disabled(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["172.33.66.40/30"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in range(40, 43): + assert f"172.33.66.{ip} in scan_targets" + + +def test_local_network_interfaces_subnet_masks(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + NetworkInterface("172.60.145.144", "/30"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 4 + + for ip in [108, 110, 145, 146]: + assert f"172.60.145.{ip}" in scan_targets From 2329f803820834fdac20db71707e5d0ccdc627d8 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 14 Dec 2021 14:58:50 +0200 Subject: [PATCH 08/18] Island, UT: Implement segmentation scan targets in scan target generation --- .../network/network_scanner.py | 4 + .../network/scan_target_generator.py | 42 +++++++- .../network/test_scan_target_generator.py | 101 ++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index c7e39909e..cfd2cb742 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -41,6 +41,8 @@ class NetworkScanner(object): self._ranges += self._get_inaccessible_subnets_ips() logger.info("Base local networks to scan are: %r", self._ranges) + # TODO remove afret agent refactoring, + # it's already handled in network.scan_target_generator._get_inaccessible_subnets_ips def _get_inaccessible_subnets_ips(self): """ For each of the machine's IPs, checks if it's in one of the subnets specified in the @@ -113,6 +115,8 @@ class NetworkScanner(object): time.sleep(WormConfiguration.tcp_scan_interval / float(1000)) @staticmethod + # TODO remove afret agent refactoring, + # it's already handled in network.scan_target_generator._is_any_ip_in_subnet 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): diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 1f4e44e86..1e0b4055e 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,3 +1,4 @@ +import itertools from collections import namedtuple from typing import List, Set @@ -9,7 +10,6 @@ NetworkInterface = namedtuple("NetworkInterface", ("address", "netmask")) # TODO: Validate all parameters -# TODO: Implement inaccessible_subnets def compile_scan_target_list( local_network_interfaces: List[NetworkInterface], ranges_to_scan: List[str], @@ -22,6 +22,12 @@ def compile_scan_target_list( if enable_local_network_scan: scan_targets.update(_get_ips_to_scan_from_local_interface(local_network_interfaces)) + if inaccessible_subnets: + inaccessible_subnets = _get_segmentation_check_targets( + inaccessible_subnets, local_network_interfaces + ) + scan_targets.update(inaccessible_subnets) + _remove_interface_ips(scan_targets, local_network_interfaces) _remove_blocklisted_ips(scan_targets, blocklisted_ips) @@ -62,3 +68,37 @@ def _remove_ips_from_scan_targets(scan_targets: Set[str], ips_to_remove: List[st except KeyError: # We don't need to remove the ip if it's already missing from the scan_targets pass + + +def _get_segmentation_check_targets( + inaccessible_subnets: List[str], local_interfaces: List[NetworkInterface] +): + subnets_to_scan = set() + local_ips = [interface.address for interface in local_interfaces] + + inaccessible_subnets = _convert_to_range_object(inaccessible_subnets) + subnet_pairs = itertools.product(inaccessible_subnets, inaccessible_subnets) + + for (subnet1, subnet2) in subnet_pairs: + if _is_segmentation_check_required(local_ips, subnet1, subnet2): + ips = _get_ips_from_ranges_to_scan(subnet2) + subnets_to_scan.update(ips) + + return subnets_to_scan + + +def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]: + return [NetworkRange.get_range_obj(subnet) for subnet in subnets] + + +def _is_segmentation_check_required( + local_ips: List[str], subnet1: NetworkRange, subnet2: NetworkRange +): + return _is_any_ip_in_subnet(local_ips, subnet1) and not _is_any_ip_in_subnet(local_ips, subnet2) + + +def _is_any_ip_in_subnet(ip_addresses: List[str], subnet: NetworkRange): + for ip_address in ip_addresses: + if subnet.is_in_range(ip_address): + return True + return False diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index cf69b7a30..702298db8 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -301,3 +301,104 @@ def test_local_network_interfaces_subnet_masks(): for ip in [108, 110, 145, 146]: assert f"172.60.145.{ip}" in scan_targets + + +def test_segmentation_targets(): + local_network_interfaces = [NetworkInterface("172.60.145.109", "/24")] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.144/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [144, 145, 146]: + assert f"172.60.145.{ip}" in scan_targets + + +def test_segmentation_clash_with_blocked(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.149/30"] + + blocked = ["172.60.145.148", "172.60.145.149", "172.60.145.150"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=blocked, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_segmentation_clash_with_targets(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.149/30"] + + targets = ["172.60.145.149", "172.60.145.150"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [148, 149, 150]: + assert f"172.60.145.{ip}" in scan_targets + + +def test_segmentation_one_network(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.1/24"] + + targets = ["172.60.145.149/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + +def test_segmentation_inaccessible_networks(): + local_network_interfaces = [ + NetworkInterface("172.60.1.1", "/24"), + NetworkInterface("172.60.2.1", "/24"), + ] + + inaccessible_subnets = ["172.60.144.1/24", "172.60.146.1/24"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 From 58da5b85a077b4d4738b64f048c8abf530849e7d Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 14 Dec 2021 17:08:55 +0200 Subject: [PATCH 09/18] Island, UT: fix target generator bug when big ip is specified first 192.168.56.2-192.168.56.1 is now a valid range, will return both of these addresses --- monkey/common/network/network_range.py | 19 ++++++++++--------- .../network/test_scan_target_generator.py | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index e58fffcec..479f5f0d7 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -4,6 +4,7 @@ import random import socket import struct from abc import ABCMeta, abstractmethod +from typing import Tuple logger = logging.getLogger(__name__) @@ -55,15 +56,20 @@ class NetworkRange(object, metaclass=ABCMeta): @staticmethod def check_if_range(address_str): if -1 != address_str.find("-"): - ips = address_str.split("-") - ips = [ip.strip() for ip in ips] try: - ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) + NetworkRange._range_to_ips(address_str) except ValueError: return False return True return False + @staticmethod + def _range_to_ips(ip_range: str) -> Tuple[str, str]: + ips = ip_range.split("-") + ips = [ip.strip() for ip in ips] + ips = sorted(ips, key=lambda ip: socket.inet_aton(ip)) + return ips[0], ips[1] + @staticmethod def _ip_to_number(address): return struct.unpack(">L", socket.inet_aton(address))[0] @@ -97,12 +103,7 @@ class IpRange(NetworkRange): def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): super(IpRange, self).__init__(shuffle=shuffle) if ip_range is not None: - addresses = ip_range.split("-") - if len(addresses) != 2: - raise ValueError( - "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range - ) - self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] + self._lower_end_ip, self._higher_end_ip = IpRange._range_to_ips(ip_range) elif (lower_end_ip is not None) and (higher_end_ip is not None): self._lower_end_ip = lower_end_ip.strip() self._higher_end_ip = higher_end_ip.strip() diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 702298db8..8d3166268 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -57,7 +57,8 @@ def test_middle_of_range_subnet(): @pytest.mark.parametrize( - "ip_range", ["192.168.56.25-192.168.56.33", "192.168.56.25 - 192.168.56.33"] + "ip_range", + ["192.168.56.25-192.168.56.33", "192.168.56.25 - 192.168.56.33", "192.168.56.33-192.168.56.25"], ) def test_ip_range(ip_range): scan_targets = compile_ranges_only([ip_range]) From 59ff3d39ced4907f18d7a3a844266f9a491d69b3 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 14 Dec 2021 17:23:44 +0200 Subject: [PATCH 10/18] UT: small readability improvement in test_scan --- .../network/test_scan_target_generator.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 8d3166268..a28ae8275 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -1,3 +1,5 @@ +from itertools import chain + import pytest from infection_monkey.network.scan_target_generator import ( @@ -191,9 +193,7 @@ def test_local_subnet_added(): assert len(scan_targets) == 254 - for ip in range(0, 5): - assert f"10.0.0.{ip} in scan_targets" - for ip in range(6, 255): + for ip in chain(range(0, 5), range(6, 255)): assert f"10.0.0.{ip} in scan_targets" @@ -213,14 +213,10 @@ def test_multiple_local_subnets_added(): assert len(scan_targets) == 2 * (255 - 1) - for ip in range(0, 5): - assert f"10.0.0.{ip} in scan_targets" - for ip in range(6, 255): + for ip in chain(range(0, 5), range(6, 255)): assert f"10.0.0.{ip} in scan_targets" - for ip in range(0, 99): - assert f"172.33.66.{ip} in scan_targets" - for ip in range(100, 255): + for ip in chain(range(0, 99), range(100, 255)): assert f"172.33.66.{ip} in scan_targets" From cabadeb7d1af20f6e266dc40ddbe5804e6d4ba51 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 15 Dec 2021 13:06:07 +0200 Subject: [PATCH 11/18] Agent, UT: Implement scan target validation This changes validate scan target inputs and skip invalid ones. If an invalid blocked IP is specified, then an unhandled exception is raised. --- monkey/common/network/network_range.py | 14 +++- .../network/scan_target_generator.py | 37 +++++++-- .../network/test_scan_target_generator.py | 83 +++++++++++++++++++ 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 479f5f0d7..b7c8f14a4 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -9,6 +9,10 @@ from typing import Tuple logger = logging.getLogger(__name__) +class InvalidNetworkRangeError(Exception): + """Raise when invalid network range is provided""" + + class NetworkRange(object, metaclass=ABCMeta): def __init__(self, shuffle=True): self._shuffle = shuffle @@ -53,6 +57,13 @@ class NetworkRange(object, metaclass=ABCMeta): return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) + @staticmethod + def validate_range(address_str: str): + try: + NetworkRange.get_range_obj(address_str) + except (ValueError, OSError) as e: + raise InvalidNetworkRangeError(e) + @staticmethod def check_if_range(address_str): if -1 != address_str.find("-"): @@ -178,10 +189,9 @@ class SingleIpRange(NetworkRange): ip = socket.gethostbyname(string_) domain_name = string_ except socket.error: - logger.error( + raise ValueError( "Your specified host: {} is not found as a domain name and" " it's not an IP address".format(string_) ) - return None, string_ # If a string_ was entered instead of IP we presume that it was domain name and translate it return ip, domain_name diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 1e0b4055e..79768c067 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,15 +1,16 @@ import itertools +import logging from collections import namedtuple from typing import List, Set -from common.network.network_range import NetworkRange +from common.network.network_range import InvalidNetworkRangeError, NetworkRange -# TODO: Convert to class and validate the format of the address and netmask -# Example: address="192.168.1.1", netmask="/24" NetworkInterface = namedtuple("NetworkInterface", ("address", "netmask")) -# TODO: Validate all parameters +logger = logging.getLogger(__name__) + + def compile_scan_target_list( local_network_interfaces: List[NetworkInterface], ranges_to_scan: List[str], @@ -17,6 +18,7 @@ def compile_scan_target_list( blocklisted_ips: List[str], enable_local_network_scan: bool, ) -> List[str]: + scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) if enable_local_network_scan: @@ -40,15 +42,20 @@ def compile_scan_target_list( def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: scan_targets = set() + ranges_to_scan = _filter_invalid_ranges( + ranges_to_scan, "Bad network range input for targets to scan:" + ) + network_ranges = [NetworkRange.get_range_obj(_range) for _range in ranges_to_scan] for _range in network_ranges: scan_targets.update(set(_range)) - return scan_targets def _get_ips_to_scan_from_local_interface(interfaces: List[NetworkInterface]) -> Set[str]: ranges = [f"{interface.address}{interface.netmask}" for interface in interfaces] + + ranges = _filter_invalid_ranges(ranges, "Local network interface returns an invalid IP:") return _get_ips_from_ranges_to_scan(ranges) @@ -58,6 +65,9 @@ def _remove_interface_ips(scan_targets: Set[str], interfaces: List[NetworkInterf def _remove_blocklisted_ips(scan_targets: Set[str], blocked_ips: List[str]): + filtered_blocked_ips = _filter_invalid_ranges(blocked_ips, "Invalid blocked IP provided:") + if not len(filtered_blocked_ips) == len(blocked_ips): + raise InvalidNetworkRangeError("Received an invalid blocked IP. Aborting just in case.") _remove_ips_from_scan_targets(scan_targets, blocked_ips) @@ -76,6 +86,11 @@ def _get_segmentation_check_targets( subnets_to_scan = set() local_ips = [interface.address for interface in local_interfaces] + local_ips = _filter_invalid_ranges(local_ips, "Invalid local IP found: ") + inaccessible_subnets = _filter_invalid_ranges( + inaccessible_subnets, "Invalid segmentation scan target: " + ) + inaccessible_subnets = _convert_to_range_object(inaccessible_subnets) subnet_pairs = itertools.product(inaccessible_subnets, inaccessible_subnets) @@ -87,6 +102,18 @@ def _get_segmentation_check_targets( return subnets_to_scan +def _filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]: + filtered = [] + for target_range in ranges: + try: + NetworkRange.validate_range(target_range) + except InvalidNetworkRangeError as e: + logger.error(f"{error_msg} {e}") + continue + filtered.append(target_range) + return filtered + + def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]: return [NetworkRange.get_range_obj(subnet) for subnet in subnets] diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index a28ae8275..af194b300 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -1,7 +1,9 @@ from itertools import chain import pytest +from network.scan_target_generator import _filter_invalid_ranges +from common.network.network_range import InvalidNetworkRangeError from infection_monkey.network.scan_target_generator import ( NetworkInterface, compile_scan_target_list, @@ -399,3 +401,84 @@ def test_segmentation_inaccessible_networks(): ) assert len(scan_targets) == 0 + + +def test_invalid_inputs(): + local_network_interfaces = [ + NetworkInterface("172.60.999.109", "/30"), + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = [ + "172.60.145.1 - 172.60.145.1111", + "172.60.147.888/30" "172.60.147.8/30", + "172.60.147.148/30", + ] + + targets = ["172.60.145.149/33", "1.-1.1.1", "1.a.2.2", "172.60.145.151/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [148, 149, 150]: + assert f"172.60.145.{ip}" in scan_targets + + +def test_range_filtering(): + invalid_ranges = [ + # Invalid IP segment + "172.60.999.109", + "172.60.-1.109", + "172.60.999.109 - 172.60.1.109", + "172.60.999.109/32", + "172.60.999.109/24", + # Invalid CIDR + "172.60.1.109/33", + "172.60.1.109/-1", + # Typos + "172.60.9.109 -t 172.60.1.109", + "172.60..9.109", + "172.60,9.109", + " 172.60 .9.109 ", + ] + + valid_ranges = [ + " 172.60.9.109 ", + "172.60.9.109 - 172.60.1.109", + "172.60.9.109- 172.60.1.109", + "0.0.0.0", + "localhost" + ] + + invalid_ranges.extend(valid_ranges) + + remaining = _filter_invalid_ranges(invalid_ranges, "Test error:") + for _range in remaining: + assert _range in valid_ranges + assert len(remaining) == len(valid_ranges) + + +def test_invalid_blocklisted_ip(): + local_network_interfaces = [NetworkInterface("172.60.145.109", "/30")] + + inaccessible_subnets = ["172.60.147.8/30", "172.60.147.148/30"] + + targets = ["172.60.145.151/30"] + + blocklisted = ["172.60.145.153", "172.60.145.753"] + + with pytest.raises(InvalidNetworkRangeError): + compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=blocklisted, + enable_local_network_scan=False, + ) From 549eb5d389abf6dd09ccf7c45215ce4231924c0d Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 16 Dec 2021 12:03:40 +0200 Subject: [PATCH 12/18] Agent, UT: Implement domain names in scan_target_generator.py Change the ip strings to NetworkAddress named tuple that has ip and domain name. This tuple better describes the target and is necessary because VictimHost uses domain names --- monkey/common/network/network_range.py | 2 +- .../network/scan_target_generator.py | 78 +++++++++++++------ .../model/test_victim_host_generator.py | 5 -- .../network/test_scan_target_generator.py | 60 +++++++++----- 4 files changed, 93 insertions(+), 52 deletions(-) diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index b7c8f14a4..5b1342370 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -178,7 +178,7 @@ class SingleIpRange(NetworkRange): :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ # The most common use case is to enter ip/range into "Scan IP/subnet list" - domain_name = "" + domain_name = None # Try casting user's input as IP try: diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 79768c067..927123d48 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,12 +1,12 @@ import itertools import logging from collections import namedtuple -from typing import List, Set +from typing import List from common.network.network_range import InvalidNetworkRangeError, NetworkRange NetworkInterface = namedtuple("NetworkInterface", ("address", "netmask")) - +NetworkAddress = namedtuple("NetworkAddress", ("ip", "domain")) logger = logging.getLogger(__name__) @@ -17,73 +17,101 @@ def compile_scan_target_list( inaccessible_subnets: List[str], blocklisted_ips: List[str], enable_local_network_scan: bool, -) -> List[str]: - +) -> List[NetworkAddress]: scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) if enable_local_network_scan: - scan_targets.update(_get_ips_to_scan_from_local_interface(local_network_interfaces)) + scan_targets.extend(_get_ips_to_scan_from_local_interface(local_network_interfaces)) if inaccessible_subnets: inaccessible_subnets = _get_segmentation_check_targets( inaccessible_subnets, local_network_interfaces ) - scan_targets.update(inaccessible_subnets) + scan_targets.extend(inaccessible_subnets) - _remove_interface_ips(scan_targets, local_network_interfaces) - _remove_blocklisted_ips(scan_targets, blocklisted_ips) + scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces) + scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips) + scan_targets = _remove_redundant_targets(scan_targets) + scan_targets.sort() - scan_target_list = list(scan_targets) - scan_target_list.sort() - - return scan_target_list + return scan_targets -def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> Set[str]: - scan_targets = set() +def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]: + target_dict = {} + for target in targets: + domain_name = target.domain + ip = target.ip + if ip not in target_dict or (target_dict[ip] is None and domain_name is not None): + target_dict[ip] = domain_name + return [NetworkAddress(key, value) for (key, value) in target_dict.items()] + + +def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]: + addresses = [] + for address in range_obj: + if hasattr(range_obj, "domain_name"): + addresses.append(NetworkAddress(address, range_obj.domain_name)) + else: + addresses.append(NetworkAddress(address, None)) + return addresses + + +def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> List[NetworkAddress]: + scan_targets = [] ranges_to_scan = _filter_invalid_ranges( ranges_to_scan, "Bad network range input for targets to scan:" ) network_ranges = [NetworkRange.get_range_obj(_range) for _range in ranges_to_scan] + for _range in network_ranges: - scan_targets.update(set(_range)) + scan_targets.extend(_range_to_addresses(_range)) return scan_targets -def _get_ips_to_scan_from_local_interface(interfaces: List[NetworkInterface]) -> Set[str]: +def _get_ips_to_scan_from_local_interface( + interfaces: List[NetworkInterface], +) -> List[NetworkAddress]: ranges = [f"{interface.address}{interface.netmask}" for interface in interfaces] ranges = _filter_invalid_ranges(ranges, "Local network interface returns an invalid IP:") return _get_ips_from_ranges_to_scan(ranges) -def _remove_interface_ips(scan_targets: Set[str], interfaces: List[NetworkInterface]): +def _remove_interface_ips( + scan_targets: List[NetworkAddress], interfaces: List[NetworkInterface] +) -> List[NetworkAddress]: interface_ips = [interface.address for interface in interfaces] - _remove_ips_from_scan_targets(scan_targets, interface_ips) + return _remove_ips_from_scan_targets(scan_targets, interface_ips) -def _remove_blocklisted_ips(scan_targets: Set[str], blocked_ips: List[str]): +def _remove_blocklisted_ips( + scan_targets: List[NetworkAddress], blocked_ips: List[str] +) -> List[NetworkAddress]: filtered_blocked_ips = _filter_invalid_ranges(blocked_ips, "Invalid blocked IP provided:") if not len(filtered_blocked_ips) == len(blocked_ips): raise InvalidNetworkRangeError("Received an invalid blocked IP. Aborting just in case.") - _remove_ips_from_scan_targets(scan_targets, blocked_ips) + return _remove_ips_from_scan_targets(scan_targets, filtered_blocked_ips) -def _remove_ips_from_scan_targets(scan_targets: Set[str], ips_to_remove: List[str]): +def _remove_ips_from_scan_targets( + scan_targets: List[NetworkAddress], ips_to_remove: List[str] +) -> List[NetworkAddress]: for ip in ips_to_remove: try: - scan_targets.remove(ip) + scan_targets = [address for address in scan_targets if address.ip != ip] except KeyError: # We don't need to remove the ip if it's already missing from the scan_targets pass + return scan_targets def _get_segmentation_check_targets( inaccessible_subnets: List[str], local_interfaces: List[NetworkInterface] -): - subnets_to_scan = set() +) -> List[NetworkAddress]: + subnets_to_scan = [] local_ips = [interface.address for interface in local_interfaces] local_ips = _filter_invalid_ranges(local_ips, "Invalid local IP found: ") @@ -97,7 +125,7 @@ def _get_segmentation_check_targets( for (subnet1, subnet2) in subnet_pairs: if _is_segmentation_check_required(local_ips, subnet1, subnet2): ips = _get_ips_from_ranges_to_scan(subnet2) - subnets_to_scan.update(ips) + subnets_to_scan.extend(ips) return subnets_to_scan diff --git a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py index c60992fee..0133102eb 100644 --- a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py @@ -39,8 +39,3 @@ class TestVictimHostGenerator(TestCase): victims = list(generator.generate_victims_from_range(self.local_host_range)) self.assertEqual(len(victims), 1) self.assertEqual(victims[0].domain_name, "localhost") - - # don't generate for other victims - victims = list(generator.generate_victims_from_range(self.random_single_ip_range)) - self.assertEqual(len(victims), 1) - self.assertEqual(victims[0].domain_name, "") diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index af194b300..41600897d 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -1,7 +1,7 @@ from itertools import chain import pytest -from network.scan_target_generator import _filter_invalid_ranges +from network.scan_target_generator import NetworkAddress, _filter_invalid_ranges from common.network.network_range import InvalidNetworkRangeError from infection_monkey.network.scan_target_generator import ( @@ -26,7 +26,7 @@ def test_single_subnet(): assert len(scan_targets) == 255 for i in range(0, 255): - assert f"10.0.0.{i}" in scan_targets + assert NetworkAddress(f"10.0.0.{i}", None) in scan_targets @pytest.mark.parametrize("single_ip", ["10.0.0.2", "10.0.0.2/32", "10.0.0.2-10.0.0.2"]) @@ -35,8 +35,8 @@ def test_single_ip(single_ip): scan_targets = compile_ranges_only([single_ip]) assert len(scan_targets) == 1 - assert "10.0.0.2" in scan_targets - assert "10.0.0.2" == scan_targets[0] + assert NetworkAddress("10.0.0.2", None) in scan_targets + assert NetworkAddress("10.0.0.2", None) == scan_targets[0] def test_multiple_subnet(): @@ -45,10 +45,10 @@ def test_multiple_subnet(): assert len(scan_targets) == 262 for i in range(0, 255): - assert f"10.0.0.{i}" in scan_targets + assert NetworkAddress(f"10.0.0.{i}", None) in scan_targets for i in range(8, 15): - assert f"192.168.56.{i}" in scan_targets + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets def test_middle_of_range_subnet(): @@ -57,7 +57,7 @@ def test_middle_of_range_subnet(): assert len(scan_targets) == 7 for i in range(0, 7): - assert f"192.168.56.{i}" in scan_targets + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets @pytest.mark.parametrize( @@ -70,7 +70,7 @@ def test_ip_range(ip_range): assert len(scan_targets) == 9 for i in range(25, 34): - assert f"192.168.56.{i}" in scan_targets + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets def test_no_duplicates(): @@ -79,7 +79,7 @@ def test_no_duplicates(): assert len(scan_targets) == 7 for i in range(0, 7): - assert f"192.168.56.{i}" in scan_targets + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets def test_blocklisted_ips(): @@ -134,6 +134,24 @@ def test_local_network_interface_ips_removed_from_targets(): assert interface.address not in scan_targets +def test_no_redundant_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["127.0.0.0", "127.0.0.1", "localhost"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 2 + assert NetworkAddress(ip="127.0.0.0", domain=None) in scan_targets + assert NetworkAddress(ip="127.0.0.1", domain="localhost") in scan_targets + + @pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) def test_only_scan_ip_is_local(ranges_to_scan): local_network_interfaces = [ @@ -196,7 +214,7 @@ def test_local_subnet_added(): assert len(scan_targets) == 254 for ip in chain(range(0, 5), range(6, 255)): - assert f"10.0.0.{ip} in scan_targets" + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets def test_multiple_local_subnets_added(): @@ -216,10 +234,10 @@ def test_multiple_local_subnets_added(): assert len(scan_targets) == 2 * (255 - 1) for ip in chain(range(0, 5), range(6, 255)): - assert f"10.0.0.{ip} in scan_targets" + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets for ip in chain(range(0, 99), range(100, 255)): - assert f"172.33.66.{ip} in scan_targets" + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets def test_blocklisted_ips_missing_from_local_subnets(): @@ -257,12 +275,12 @@ def test_local_subnets_and_ranges_added(): assert len(scan_targets) == 254 + 3 for ip in range(0, 5): - assert f"10.0.0.{ip} in scan_targets" + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets for ip in range(6, 255): - assert f"10.0.0.{ip} in scan_targets" + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets for ip in range(40, 43): - assert f"172.33.66.{ip} in scan_targets" + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets def test_local_network_interfaces_specified_but_disabled(): @@ -279,7 +297,7 @@ def test_local_network_interfaces_specified_but_disabled(): assert len(scan_targets) == 3 for ip in range(40, 43): - assert f"172.33.66.{ip} in scan_targets" + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets def test_local_network_interfaces_subnet_masks(): @@ -299,7 +317,7 @@ def test_local_network_interfaces_subnet_masks(): assert len(scan_targets) == 4 for ip in [108, 110, 145, 146]: - assert f"172.60.145.{ip}" in scan_targets + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets def test_segmentation_targets(): @@ -318,7 +336,7 @@ def test_segmentation_targets(): assert len(scan_targets) == 3 for ip in [144, 145, 146]: - assert f"172.60.145.{ip}" in scan_targets + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets def test_segmentation_clash_with_blocked(): @@ -361,7 +379,7 @@ def test_segmentation_clash_with_targets(): assert len(scan_targets) == 3 for ip in [148, 149, 150]: - assert f"172.60.145.{ip}" in scan_targets + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets def test_segmentation_one_network(): @@ -428,7 +446,7 @@ def test_invalid_inputs(): assert len(scan_targets) == 3 for ip in [148, 149, 150]: - assert f"172.60.145.{ip}" in scan_targets + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets def test_range_filtering(): @@ -454,7 +472,7 @@ def test_range_filtering(): "172.60.9.109 - 172.60.1.109", "172.60.9.109- 172.60.1.109", "0.0.0.0", - "localhost" + "localhost", ] invalid_ranges.extend(valid_ranges) From ec9aaf6b389339093374396ea56a6ab02c4428f4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 08:27:33 -0500 Subject: [PATCH 13/18] Agent: Clarify some names in scan_target_generator --- .../network/scan_target_generator.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 927123d48..aaee67a0d 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -38,13 +38,13 @@ def compile_scan_target_list( def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]: - target_dict = {} + reverse_dns = {} for target in targets: domain_name = target.domain ip = target.ip - if ip not in target_dict or (target_dict[ip] is None and domain_name is not None): - target_dict[ip] = domain_name - return [NetworkAddress(key, value) for (key, value) in target_dict.items()] + if ip not in reverse_dns or (reverse_dns[ip] is None and domain_name is not None): + reverse_dns[ip] = domain_name + return [NetworkAddress(key, value) for (key, value) in reverse_dns.items()] def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]: @@ -111,7 +111,7 @@ def _remove_ips_from_scan_targets( def _get_segmentation_check_targets( inaccessible_subnets: List[str], local_interfaces: List[NetworkInterface] ) -> List[NetworkAddress]: - subnets_to_scan = [] + ips_to_scan = [] local_ips = [interface.address for interface in local_interfaces] local_ips = _filter_invalid_ranges(local_ips, "Invalid local IP found: ") @@ -125,21 +125,21 @@ def _get_segmentation_check_targets( for (subnet1, subnet2) in subnet_pairs: if _is_segmentation_check_required(local_ips, subnet1, subnet2): ips = _get_ips_from_ranges_to_scan(subnet2) - subnets_to_scan.extend(ips) + ips_to_scan.extend(ips) - return subnets_to_scan + return ips_to_scan def _filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]: - filtered = [] + valid_ranges = [] for target_range in ranges: try: NetworkRange.validate_range(target_range) except InvalidNetworkRangeError as e: logger.error(f"{error_msg} {e}") continue - filtered.append(target_range) - return filtered + valid_ranges.append(target_range) + return valid_ranges def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]: From 7c786b08831a9ccac6e24b7414d84241d66a5e36 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 08:29:45 -0500 Subject: [PATCH 14/18] Agent: Improve performance of _remove_ips_from_scan_targets() --- monkey/infection_monkey/network/scan_target_generator.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index aaee67a0d..d8f3339d7 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -99,13 +99,8 @@ def _remove_blocklisted_ips( def _remove_ips_from_scan_targets( scan_targets: List[NetworkAddress], ips_to_remove: List[str] ) -> List[NetworkAddress]: - for ip in ips_to_remove: - try: - scan_targets = [address for address in scan_targets if address.ip != ip] - except KeyError: - # We don't need to remove the ip if it's already missing from the scan_targets - pass - return scan_targets + ips_to_remove_set = set(ips_to_remove) + return [address for address in scan_targets if address.ip not in ips_to_remove_set] def _get_segmentation_check_targets( From db246d6740a6a995e66d7292eee3aba0569d6abe Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 08:33:38 -0500 Subject: [PATCH 15/18] UT: Fix imports in test_scan_target_generator --- .../infection_monkey/network/test_scan_target_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 41600897d..af01a7372 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -1,11 +1,12 @@ from itertools import chain import pytest -from network.scan_target_generator import NetworkAddress, _filter_invalid_ranges from common.network.network_range import InvalidNetworkRangeError from infection_monkey.network.scan_target_generator import ( + NetworkAddress, NetworkInterface, + _filter_invalid_ranges, compile_scan_target_list, ) From bfed27301a513d619cbe493181c22cbc0dc5eb17 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 08:47:29 -0500 Subject: [PATCH 16/18] Agent: Change `not ==` to `!=` in _remove_blocklisted_ips() --- monkey/infection_monkey/network/scan_target_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index d8f3339d7..3a2e77470 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -91,7 +91,7 @@ def _remove_blocklisted_ips( scan_targets: List[NetworkAddress], blocked_ips: List[str] ) -> List[NetworkAddress]: filtered_blocked_ips = _filter_invalid_ranges(blocked_ips, "Invalid blocked IP provided:") - if not len(filtered_blocked_ips) == len(blocked_ips): + if len(filtered_blocked_ips) != len(blocked_ips): raise InvalidNetworkRangeError("Received an invalid blocked IP. Aborting just in case.") return _remove_ips_from_scan_targets(scan_targets, filtered_blocked_ips) From c8469f552185e5c8bf500bddb129da84c47a7a89 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 08:56:35 -0500 Subject: [PATCH 17/18] Agent: Move _filter_invalid_ranges to NetworkRanges --- monkey/common/network/network_range.py | 14 +++++++- .../network/scan_target_generator.py | 26 +++++--------- .../common/network/test_network_range.py | 35 +++++++++++++++++++ .../network/test_scan_target_generator.py | 35 ------------------- 4 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 monkey/tests/unit_tests/common/network/test_network_range.py diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 5b1342370..326e365ce 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -4,7 +4,7 @@ import random import socket import struct from abc import ABCMeta, abstractmethod -from typing import Tuple +from typing import List, Tuple logger = logging.getLogger(__name__) @@ -57,6 +57,18 @@ class NetworkRange(object, metaclass=ABCMeta): return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) + @staticmethod + def filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]: + valid_ranges = [] + for target_range in ranges: + try: + NetworkRange.validate_range(target_range) + except InvalidNetworkRangeError as e: + logger.error(f"{error_msg} {e}") + continue + valid_ranges.append(target_range) + return valid_ranges + @staticmethod def validate_range(address_str: str): try: diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 3a2e77470..734cc90c6 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -60,7 +60,7 @@ def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]: def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> List[NetworkAddress]: scan_targets = [] - ranges_to_scan = _filter_invalid_ranges( + ranges_to_scan = NetworkRange.filter_invalid_ranges( ranges_to_scan, "Bad network range input for targets to scan:" ) @@ -76,7 +76,9 @@ def _get_ips_to_scan_from_local_interface( ) -> List[NetworkAddress]: ranges = [f"{interface.address}{interface.netmask}" for interface in interfaces] - ranges = _filter_invalid_ranges(ranges, "Local network interface returns an invalid IP:") + ranges = NetworkRange.filter_invalid_ranges( + ranges, "Local network interface returns an invalid IP:" + ) return _get_ips_from_ranges_to_scan(ranges) @@ -90,7 +92,9 @@ def _remove_interface_ips( def _remove_blocklisted_ips( scan_targets: List[NetworkAddress], blocked_ips: List[str] ) -> List[NetworkAddress]: - filtered_blocked_ips = _filter_invalid_ranges(blocked_ips, "Invalid blocked IP provided:") + filtered_blocked_ips = NetworkRange.filter_invalid_ranges( + blocked_ips, "Invalid blocked IP provided:" + ) if len(filtered_blocked_ips) != len(blocked_ips): raise InvalidNetworkRangeError("Received an invalid blocked IP. Aborting just in case.") return _remove_ips_from_scan_targets(scan_targets, filtered_blocked_ips) @@ -109,8 +113,8 @@ def _get_segmentation_check_targets( ips_to_scan = [] local_ips = [interface.address for interface in local_interfaces] - local_ips = _filter_invalid_ranges(local_ips, "Invalid local IP found: ") - inaccessible_subnets = _filter_invalid_ranges( + local_ips = NetworkRange.filter_invalid_ranges(local_ips, "Invalid local IP found: ") + inaccessible_subnets = NetworkRange.filter_invalid_ranges( inaccessible_subnets, "Invalid segmentation scan target: " ) @@ -125,18 +129,6 @@ def _get_segmentation_check_targets( return ips_to_scan -def _filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]: - valid_ranges = [] - for target_range in ranges: - try: - NetworkRange.validate_range(target_range) - except InvalidNetworkRangeError as e: - logger.error(f"{error_msg} {e}") - continue - valid_ranges.append(target_range) - return valid_ranges - - def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]: return [NetworkRange.get_range_obj(subnet) for subnet in subnets] diff --git a/monkey/tests/unit_tests/common/network/test_network_range.py b/monkey/tests/unit_tests/common/network/test_network_range.py new file mode 100644 index 000000000..0abb793d1 --- /dev/null +++ b/monkey/tests/unit_tests/common/network/test_network_range.py @@ -0,0 +1,35 @@ +from common.network.network_range import NetworkRange + + +def test_range_filtering(): + invalid_ranges = [ + # Invalid IP segment + "172.60.999.109", + "172.60.-1.109", + "172.60.999.109 - 172.60.1.109", + "172.60.999.109/32", + "172.60.999.109/24", + # Invalid CIDR + "172.60.1.109/33", + "172.60.1.109/-1", + # Typos + "172.60.9.109 -t 172.60.1.109", + "172.60..9.109", + "172.60,9.109", + " 172.60 .9.109 ", + ] + + valid_ranges = [ + " 172.60.9.109 ", + "172.60.9.109 - 172.60.1.109", + "172.60.9.109- 172.60.1.109", + "0.0.0.0", + "localhost", + ] + + invalid_ranges.extend(valid_ranges) + + remaining = NetworkRange.filter_invalid_ranges(invalid_ranges, "Test error:") + for _range in remaining: + assert _range in valid_ranges + assert len(remaining) == len(valid_ranges) diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index af01a7372..4f3e49b64 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -6,7 +6,6 @@ from common.network.network_range import InvalidNetworkRangeError from infection_monkey.network.scan_target_generator import ( NetworkAddress, NetworkInterface, - _filter_invalid_ranges, compile_scan_target_list, ) @@ -450,40 +449,6 @@ def test_invalid_inputs(): assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets -def test_range_filtering(): - invalid_ranges = [ - # Invalid IP segment - "172.60.999.109", - "172.60.-1.109", - "172.60.999.109 - 172.60.1.109", - "172.60.999.109/32", - "172.60.999.109/24", - # Invalid CIDR - "172.60.1.109/33", - "172.60.1.109/-1", - # Typos - "172.60.9.109 -t 172.60.1.109", - "172.60..9.109", - "172.60,9.109", - " 172.60 .9.109 ", - ] - - valid_ranges = [ - " 172.60.9.109 ", - "172.60.9.109 - 172.60.1.109", - "172.60.9.109- 172.60.1.109", - "0.0.0.0", - "localhost", - ] - - invalid_ranges.extend(valid_ranges) - - remaining = _filter_invalid_ranges(invalid_ranges, "Test error:") - for _range in remaining: - assert _range in valid_ranges - assert len(remaining) == len(valid_ranges) - - def test_invalid_blocklisted_ip(): local_network_interfaces = [NetworkInterface("172.60.145.109", "/30")] From ed16826b879faef7af344b9d000d3a530acfefb9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 16 Dec 2021 09:08:39 -0500 Subject: [PATCH 18/18] Agent: Sort scan targets by IP --- .../network/scan_target_generator.py | 3 ++- .../network/test_scan_target_generator.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/scan_target_generator.py b/monkey/infection_monkey/network/scan_target_generator.py index 734cc90c6..6cec82223 100644 --- a/monkey/infection_monkey/network/scan_target_generator.py +++ b/monkey/infection_monkey/network/scan_target_generator.py @@ -1,5 +1,6 @@ import itertools import logging +import socket from collections import namedtuple from typing import List @@ -32,7 +33,7 @@ def compile_scan_target_list( scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces) scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips) scan_targets = _remove_redundant_targets(scan_targets) - scan_targets.sort() + scan_targets.sort(key=lambda network_address: socket.inet_aton(network_address.ip)) return scan_targets diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py index 4f3e49b64..03febe44c 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_scan_target_generator.py @@ -466,3 +466,18 @@ def test_invalid_blocklisted_ip(): blocklisted_ips=blocklisted, enable_local_network_scan=False, ) + + +def test_sorted_scan_targets(): + expected_results = [f"10.1.0.{i}" for i in range(0, 255)] + expected_results.extend([f"10.2.0.{i}" for i in range(0, 255)]) + expected_results.extend([f"10.10.0.{i}" for i in range(0, 255)]) + expected_results.extend([f"10.20.0.{i}" for i in range(0, 255)]) + + scan_targets = compile_scan_target_list( + [], ["10.1.0.0/24", "10.10.0.0/24", "10.20.0.0/24", "10.2.0.0/24"], [], [], False + ) + + actual_results = [network_address.ip for network_address in scan_targets] + + assert expected_results == actual_results