diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md index b31fbdcab..dbdd54b41 100644 --- a/envs/monkey_zoo/blackbox/README.md +++ b/envs/monkey_zoo/blackbox/README.md @@ -10,7 +10,13 @@ In order to execute the entire test suite, you must know the external IP of the this information in the GCP Console `Compute Engine/VM Instances` under _External IP_. #### Running in command line -Run the following command: +Blackbox tests have following parameters: +- `--island=IP` Sets island's IP +- `--no-gcp` (Optional) Use for no interaction with the cloud (local test). +- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries, +instead will just test performance of endpoints in already present island state. + +Example run command: `monkey\envs\monkey_zoo\blackbox>python -m pytest -s --island=35.207.152.72:5000 test_blackbox.py` diff --git a/envs/monkey_zoo/blackbox/conftest.py b/envs/monkey_zoo/blackbox/conftest.py index 13aabf5b6..4909bcbc7 100644 --- a/envs/monkey_zoo/blackbox/conftest.py +++ b/envs/monkey_zoo/blackbox/conftest.py @@ -4,8 +4,23 @@ import pytest def pytest_addoption(parser): parser.addoption("--island", action="store", default="", help="Specify the Monkey Island address (host+port).") + parser.addoption("--no-gcp", action="store_true", default=False, + help="Use for no interaction with the cloud.") + parser.addoption("--quick-performance-tests", action="store_true", default=False, + help="If enabled performance tests won't reset island and won't send telemetries, " + "instead will just test performance of already present island state.") -@pytest.fixture(scope='module') +@pytest.fixture(scope='session') def island(request): return request.config.getoption("--island") + + +@pytest.fixture(scope='session') +def no_gcp(request): + return request.config.getoption("--no-gcp") + + +@pytest.fixture(scope='session') +def quick_performance_tests(request): + return request.config.getoption("--quick-performance-tests") diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index babc9c7a0..98acb5f7f 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -90,9 +90,10 @@ class MonkeyIslandRequests(object): @_Decorators.refresh_jwt_token def patch(self, url, data: Dict): return requests.patch(self.addr + url, # noqa: DUO123 - data=data, - headers=self.get_jwt_header(), - verify=False) + data=data, + headers=self.get_jwt_header(), + verify=False + ) @_Decorators.refresh_jwt_token def delete(self, url): diff --git a/envs/monkey_zoo/blackbox/island_configs/PERFORMANCE.conf b/envs/monkey_zoo/blackbox/island_configs/PERFORMANCE.conf index ebe3d8814..23d5ce379 100644 --- a/envs/monkey_zoo/blackbox/island_configs/PERFORMANCE.conf +++ b/envs/monkey_zoo/blackbox/island_configs/PERFORMANCE.conf @@ -2,14 +2,15 @@ "basic": { "credentials": { "exploit_password_list": [ - "Password1!", - "12345678", - "^NgDvY59~8" + "Xk8VDTsC", + "^NgDvY59~8", + "Ivrrw5zEzs", + "3Q=(Ge(+&w]*", + "`))jU7L(w}", + "t67TC5ZDmz" ], "exploit_user_list": [ - "Administrator", - "m0nk3y", - "user" + "m0nk3y" ] }, "general": { @@ -23,11 +24,38 @@ "local_network_scan": false, "subnet_scan_list": [ "10.2.2.2", - "10.2.2.4" + "10.2.2.3", + "10.2.2.4", + "10.2.2.5", + "10.2.2.8", + "10.2.2.9", + "10.2.1.10", + "10.2.0.11", + "10.2.0.12", + "10.2.2.11", + "10.2.2.12", + "10.2.2.14", + "10.2.2.15", + "10.2.2.16", + "10.2.2.18", + "10.2.2.19", + "10.2.2.20", + "10.2.2.21", + "10.2.2.23", + "10.2.2.24" ] }, "network_analysis": { - "inaccessible_subnets": [] + "inaccessible_subnets": [ + "10.2.2.0/30", + "10.2.2.8/30", + "10.2.2.24/32", + "10.2.2.23/32", + "10.2.2.21/32", + "10.2.2.19/32", + "10.2.2.18/32", + "10.2.2.17/32" + ] } }, "cnc": { @@ -45,10 +73,17 @@ "exploits": { "general": { "exploiter_classes": [ + "SmbExploiter", + "WmiExploiter", "SSHExploiter", - "MSSQLExploiter", + "ShellShockExploiter", + "SambaCryExploiter", "ElasticGroovyExploiter", - "HadoopExploiter" + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter", + "VSFTPDExploiter", + "MSSQLExploiter" ], "skip_exploit_if_file_exist": false }, @@ -57,9 +92,6 @@ "remote_user_pass": "Password1!", "user_to_add": "Monkey_IUSER_SUPPORT" }, - "rdp_grinder": { - "rdp_use_vbs_download": true - }, "sambacry": { "sambacry_folder_paths_to_guess": [ "/", @@ -109,7 +141,7 @@ "exploit_ssh_keys": [] }, "general": { - "keep_tunnel_open_time": 1, + "keep_tunnel_open_time": 60, "monkey_dir_name": "monkey_dir", "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}" }, @@ -123,6 +155,9 @@ "monkey_log_path_linux": "/tmp/user-1563", "monkey_log_path_windows": "%temp%\\~df1563.tmp", "send_log_to_server": true + }, + "testing": { + "export_monkey_telems": true } }, "monkey": { @@ -137,24 +172,32 @@ }, "general": { "alive": true, - "post_breach_actions": [] + "post_breach_actions": [ + "CommunicateAsNewUser" + ] }, "life_cycle": { "max_iterations": 1, "retry_failed_explotation": true, "timeout_between_iterations": 100, - "victims_max_exploit": 7, - "victims_max_find": 30 + "victims_max_exploit": 15, + "victims_max_find": 100 }, "system_info": { "collect_system_info": true, - "extract_azure_creds": false, - "should_use_mimikatz": true + "extract_azure_creds": true, + "should_use_mimikatz": true, + "system_info_collectors_classes": [ + "EnvironmentCollector", + "AwsCollector", + "HostnameCollector", + "ProcessListCollector" + ] } }, "network": { "ping_scanner": { - "ping_scan_timeout": 500 + "ping_scan_timeout": 1000 }, "tcp_scanner": { "HTTP_PORTS": [ @@ -166,7 +209,7 @@ ], "tcp_scan_get_banner": true, "tcp_scan_interval": 0, - "tcp_scan_timeout": 1000, + "tcp_scan_timeout": 3000, "tcp_target_ports": [ 22, 2222, @@ -179,7 +222,8 @@ 8008, 3306, 9200, - 7001 + 7001, + 8088 ] } } diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 2408d79be..88f5c12a8 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -27,15 +27,16 @@ LOGGER = logging.getLogger(__name__) @pytest.fixture(autouse=True, scope='session') -def GCPHandler(request): - GCPHandler = gcp_machine_handlers.GCPHandler() - GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST)) - wait_machine_bootup() +def GCPHandler(request, no_gcp): + if not no_gcp: + GCPHandler = gcp_machine_handlers.GCPHandler() + GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST)) + wait_machine_bootup() - def fin(): - GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST)) + def fin(): + GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST)) - request.addfinalizer(fin) + request.addfinalizer(fin) @pytest.fixture(autouse=True, scope='session') @@ -49,9 +50,10 @@ def wait_machine_bootup(): @pytest.fixture(scope='class') -def island_client(island): +def island_client(island, quick_performance_tests): island_client_object = MonkeyIslandClient(island) - island_client_object.reset_env() + if not quick_performance_tests: + island_client_object.reset_env() yield island_client_object @@ -130,7 +132,7 @@ class TestMonkeyBlackbox(object): def test_wmi_pth(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH") - def test_report_generation_performance(self, island_client): + def test_report_generation_performance(self, island_client, quick_performance_tests): """ This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test for a total of 8 machines including the Monkey Island. @@ -138,22 +140,30 @@ class TestMonkeyBlackbox(object): Is has 2 analyzers - the regular one which checks all the Monkeys and the Timing one which checks how long the report took to execute """ - TestMonkeyBlackbox.run_performance_test(ReportGenerationTest, - island_client, - "PERFORMANCE.conf", - timeout_in_seconds=10*60) + if not quick_performance_tests: + TestMonkeyBlackbox.run_performance_test(ReportGenerationTest, + island_client, + "PERFORMANCE.conf", + timeout_in_seconds=10*60) + else: + LOGGER.error("This test doesn't support 'quick_performance_tests' option.") + assert False - def test_map_generation_performance(self, island_client): - TestMonkeyBlackbox.run_performance_test(MapGenerationTest, - island_client, - "PERFORMANCE.conf", - timeout_in_seconds=10*60) + def test_map_generation_performance(self, island_client, quick_performance_tests): + if not quick_performance_tests: + TestMonkeyBlackbox.run_performance_test(MapGenerationTest, + island_client, + "PERFORMANCE.conf", + timeout_in_seconds=10*60) + else: + LOGGER.error("This test doesn't support 'quick_performance_tests' option.") + assert False - def test_report_generation_from_fake_telemetries(self, island_client): - ReportGenerationFromTelemetryTest(island_client).run() + def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests): + ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - def test_map_generation_from_fake_telemetries(self, island_client): - MapGenerationFromTelemetryTest(island_client).run() + def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests): + MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - def test_telem_performance(self, island_client): - TelemetryPerformanceTest(island_client).test_telemetry_performance() + def test_telem_performance(self, island_client, quick_performance_tests): + TelemetryPerformanceTest(island_client, quick_performance_tests).test_telemetry_performance() diff --git a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py index 798f490af..b4f8f35c8 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py @@ -17,9 +17,6 @@ class EndpointPerformanceTest(BasicTest): self.island_client = island_client def run(self) -> bool: - if not self.island_client.is_all_monkeys_dead(): - raise RuntimeError("Can't test report times since not all Monkeys have died.") - # Collect timings for all pages self.island_client.clear_caches() endpoint_timings = {} diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py index c5344d8f7..1b31a8962 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py @@ -17,7 +17,7 @@ class MapGenerationFromTelemetryTest(PerformanceTest): TEST_NAME = "Map generation from fake telemetries test" - def __init__(self, island_client, break_on_timeout=False): + def __init__(self, island_client, quick_performance_test: bool, break_on_timeout=False): self.island_client = island_client performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, @@ -25,7 +25,8 @@ class MapGenerationFromTelemetryTest(PerformanceTest): break_on_timeout=break_on_timeout) self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME, self.island_client, - performance_config) + performance_config, + quick_performance_test) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py index cdb4f08ac..4e708ed9d 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py @@ -23,6 +23,8 @@ class PerformanceTestWorkflow(BasicTest): self.island_client.kill_all_monkeys() self.exploitation_test.wait_until_monkeys_die() self.exploitation_test.wait_for_monkey_process_to_finish() + if not self.island_client.is_all_monkeys_dead(): + raise RuntimeError("Can't test report times since not all Monkeys have died.") performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client) try: if not self.island_client.is_all_monkeys_dead(): diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py index a08bbda70..abc2b35c2 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py @@ -21,7 +21,7 @@ class ReportGenerationFromTelemetryTest(PerformanceTest): TEST_NAME = "Map generation from fake telemetries test" - def __init__(self, island_client, break_on_timeout=False): + def __init__(self, island_client, quick_performance_test, break_on_timeout=False): self.island_client = island_client performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, @@ -29,7 +29,8 @@ class ReportGenerationFromTelemetryTest(PerformanceTest): break_on_timeout=break_on_timeout) self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME, self.island_client, - performance_config) + performance_config, + quick_performance_test) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py index 89cdf5cad..1d140e396 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py @@ -10,9 +10,9 @@ class FakeMonkey: self.original_guid = guid self.fake_ip_generator = fake_ip_generator self.on_island = on_island - self.fake_guid = str(random.randint(1000000000000, 9999999999999)) + self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102 self.fake_ips = fake_ip_generator.generate_fake_ips_for_real_ips(ips) def change_fake_data(self): self.fake_ips = self.fake_ip_generator.generate_fake_ips_for_real_ips(self.original_ips) - self.fake_guid = str(random.randint(1000000000000, 9999999999999)) + self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102 diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py index 4de77e41a..699876cce 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py @@ -18,8 +18,9 @@ MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=60) class TelemetryPerformanceTest: - def __init__(self, island_client: MonkeyIslandClient): + def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool): self.island_client = island_client + self.quick_performance_test = quick_performance_test def test_telemetry_performance(self): LOGGER.info("Starting telemetry performance test.") @@ -36,6 +37,8 @@ class TelemetryPerformanceTest: telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(telemetry) test_config = PerformanceTestConfig(MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME) PerformanceAnalyzer(test_config, telemetry_parse_times).analyze_test_results() + if not self.quick_performance_test: + self.island_client.reset_env() def get_telemetry_time(self, telemetry): content = telemetry['content'] diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py index b5acf4a9e..e8bef33d8 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py @@ -6,15 +6,18 @@ from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test impor class TelemetryPerformanceTestWorkflow(BasicTest): - def __init__(self, name, island_client, performance_config: PerformanceTestConfig): + def __init__(self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test): self.name = name self.island_client = island_client self.performance_config = performance_config + self.quick_performance_test = quick_performance_test def run(self): try: - TelemetryPerformanceTest(island_client=self.island_client).test_telemetry_performance() + if not self.quick_performance_test: + TelemetryPerformanceTest(island_client=self.island_client).test_telemetry_performance() performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client) assert performance_test.run() finally: - self.island_client.reset_env() + if not self.quick_performance_test: + self.island_client.reset_env() diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 633f406a5..927b5b6f3 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -15,10 +15,10 @@ class GCPHandler(object): self.zone = zone try: # pass the key file to gcp - subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) + subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) # noqa: DUO116 LOGGER.info("GCP Handler passed key") # set project - subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) + subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) # noqa: DUO116 LOGGER.info("GCP Handler set project") LOGGER.info("GCP Handler initialized successfully") except Exception as e: @@ -32,14 +32,14 @@ class GCPHandler(object): """ LOGGER.info("Setting up all GCP machines...") try: - subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) + subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116 LOGGER.info("GCP machines successfully started.") except Exception as e: LOGGER.error("GCP Handler failed to start GCP machines: %s" % e) def stop_machines(self, machine_list): try: - subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) + subprocess.call((GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116 LOGGER.info("GCP machines stopped successfully.") except Exception as e: LOGGER.error("GCP Handler failed to stop network machines: %s" % e) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 5c5b5a392..910b0abd7 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -13,6 +13,7 @@ GUID = str(uuid.getnode()) EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list", "exploit_ssh_keys"] +LOCAL_CONFIG_VARS = ["name", "id", "current_server", "max_depth"] HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden" @@ -22,14 +23,17 @@ class Configuration(object): for key, value in list(formatted_data.items()): if key.startswith('_'): continue - if key in ["name", "id", "current_server"]: + if key in LOCAL_CONFIG_VARS: continue if self._depth_from_commandline and key == "depth": + self.max_depth = value continue if hasattr(self, key): setattr(self, key, value) else: unknown_items.append(key) + if not self.max_depth: + self.max_depth = self.depth return unknown_items def from_json(self, json_data): @@ -135,6 +139,8 @@ class Configuration(object): # depth of propagation depth = 2 + max_depth = None + started_on_island = False current_server = "" # Configuration servers to try to connect to, in this order. @@ -232,6 +238,18 @@ class Configuration(object): cred_list.append(cred) return cred_list + @staticmethod + def hash_sensitive_data(sensitive_data): + """ + Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is + saved on client machines plain-text. + + :param sensitive_data: the data to hash. + :return: the hashed data. + """ + password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest() + return password_hashed + exploit_user_list = ['Administrator', 'root', 'user'] exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_lm_hash_list = [] @@ -259,23 +277,22 @@ class Configuration(object): extract_azure_creds = True + ########################### + # post breach actions + ########################### post_breach_actions = [] custom_PBA_linux_cmd = "" custom_PBA_windows_cmd = "" PBA_linux_filename = None PBA_windows_filename = None - @staticmethod - def hash_sensitive_data(sensitive_data): - """ - Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is - saved on client machines plain-text. + ########################### + # testing configuration + ########################### + export_monkey_telems = False - :param sensitive_data: the data to hash. - :return: the hashed data. - """ - password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest() - return password_hashed + def get_hop_distance_to_island(self): + return self.max_depth - self.depth WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 8b45bab2c..4eacc728b 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -15,6 +15,8 @@ from infection_monkey.transport.tcp import TcpProxy __author__ = 'hoffer' +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException + requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) @@ -321,3 +323,29 @@ class ControlClient(object): proxies=ControlClient.proxies) except requests.exceptions.RequestException: return False + + @staticmethod + def should_monkey_run(vulnerable_port: str) -> bool: + if vulnerable_port and \ + WormConfiguration.get_hop_distance_to_island() > 1 and \ + ControlClient.can_island_see_port(vulnerable_port) and \ + WormConfiguration.started_on_island: + raise PlannedShutdownException("Monkey shouldn't run on current machine " + "(it will be exploited later with more depth).") + return True + + @staticmethod + def can_island_see_port(port): + try: + url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}" + response = requests.get(url, verify=False) + response = json.loads(response.content.decode()) + return response['status'] == "port_visible" + except requests.exceptions.RequestException: + return False + + @staticmethod + def report_start_on_island(): + requests.post(f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", + data=json.dumps({'started_on_island': True}), + verify=False) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 55a359b60..fe6709003 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -44,6 +44,7 @@ class MonkeyDrops(object): arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-d', '--depth', type=int) arg_parser.add_argument('-l', '--location') + arg_parser.add_argument('-vp', '--vulnerable-port') self.monkey_args = args[1:] self.opts, _ = arg_parser.parse_known_args(args) @@ -115,7 +116,12 @@ class MonkeyDrops(object): LOG.warning("Cannot set reference date to destination file") monkey_options = \ - build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth) + build_monkey_commandline_explicitly(parent=self.opts.parent, + tunnel=self.opts.tunnel, + server=self.opts.server, + depth=self.opts.depth, + location=None, + vulnerable_port=self.opts.vulnerable_port) if OperatingSystem.Windows == SystemInfoCollector.get_os(): monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 05c6315c1..7b3fcabd3 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -73,7 +73,8 @@ class HadoopExploiter(WebRCE): def build_command(self, path, http_path): # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, + vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]) if 'linux' in self.host.os['type']: base_command = HADOOP_LINUX_COMMAND else: diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9d2aff5b0..36833c5bc 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -133,6 +133,7 @@ class MSSQLExploiter(HostExploiter): # Form monkey's launch command monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, + MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 4820d0f05..0d08d8a5a 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -329,7 +329,10 @@ class SambaCryExploiter(HostExploiter): return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") def get_monkey_commandline_file(self, location): - return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location))) + return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + SambaCryExploiter.SAMBA_PORT, + str(location))) @staticmethod def is_share_writable(smb_client, share): diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 718e10617..4c4c9eff0 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -144,7 +144,11 @@ class ShellShockExploiter(HostExploiter): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & ' + cmdline += build_monkey_commandline(self.host, + get_monkey_depth() - 1, + HTTPTools.get_port_from_url(url), + dropper_target_path_linux) + cmdline += ' & ' run_path = exploit + cmdline self.attack_page(url, header, run_path) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index f53e1ac38..86839c027 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -28,6 +28,7 @@ class SmbExploiter(HostExploiter): def __init__(self, host): super(SmbExploiter, self).__init__(host) + self.vulnerable_port = None def is_os_supported(self): if super(SmbExploiter, self).is_os_supported(): @@ -36,11 +37,13 @@ class SmbExploiter(HostExploiter): if not self.host.os.get('type'): is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) if is_smb_open: + self.vulnerable_port = 445 smb_finger = SMBFinger() smb_finger.get_host_fingerprint(self.host) else: is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139) if is_nb_open: + self.vulnerable_port = 139 self.host.os['type'] = 'windows' return self.host.os.get('type') in self._TARGET_OS_TYPE return False @@ -103,10 +106,13 @@ class SmbExploiter(HostExploiter): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ build_monkey_commandline(self.host, get_monkey_depth() - 1, + self.vulnerable_port, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=self.vulnerable_port) smb_conn = False for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 45d36d055..5e58f1f54 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -179,7 +179,9 @@ class SSHExploiter(HostExploiter): try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + cmdline += build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=SSH_PORT) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 1fc2fc4f1..e26f6ff01 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -41,29 +41,32 @@ def get_target_monkey_by_os(is_windows, is_32bit): return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) -def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None): +def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None, + vulnerable_port=None): cmdline = "" if parent is not None: - cmdline += " -p " + parent + cmdline += f" -p {parent}" if tunnel is not None: - cmdline += " -t " + tunnel + cmdline += f" -t {tunnel}" if server is not None: - cmdline += " -s " + server + cmdline += f" -s {server}" if depth is not None: - if depth < 0: + if int(depth) < 0: depth = 0 - cmdline += " -d %d" % depth + cmdline += f" -d {depth}" if location is not None: - cmdline += " -l %s" % location + cmdline += f" -l {location}" + if vulnerable_port is not None: + cmdline += f" -vp {vulnerable_port}" return cmdline -def build_monkey_commandline(target_host, depth, location=None): +def build_monkey_commandline(target_host, depth, vulnerable_port, location=None): from infection_monkey.config import GUID return build_monkey_commandline_explicitly( - GUID, target_host.default_tunnel, target_host.default_server, depth, location) + GUID, target_host.default_tunnel, target_host.default_server, depth, location, vulnerable_port) def get_monkey_depth(): diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index bfdeb68c8..af53e0450 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -73,6 +73,10 @@ class HTTPTools(object): lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd + @staticmethod + def get_port_from_url(url: str) -> int: + return urllib.parse.urlparse(url).port + class MonkeyHTTPServer(HTTPTools): def __init__(self, host): diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py new file mode 100644 index 000000000..5d7dd422d --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/test_helpers.py @@ -0,0 +1,28 @@ +import unittest + +from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly + + +class TestHelpers(unittest.TestCase): + + def test_build_monkey_commandline_explicitly(self): + test1 = " -p 101010 -t 10.10.101.10 -s 127.127.127.127:5000 -d 0 -l C:\\windows\\abc -vp 80" + result1 = build_monkey_commandline_explicitly(101010, + "10.10.101.10", + "127.127.127.127:5000", + 0, + "C:\\windows\\abc", + 80) + + test2 = " -p parent -s 127.127.127.127:5000 -d 0 -vp 80" + result2 = build_monkey_commandline_explicitly(parent="parent", + server="127.127.127.127:5000", + depth="0", + vulnerable_port="80") + + self.assertEqual(test1, result1) + self.assertEqual(test2, result2) + + +if __name__ == '__main__': + unittest.main() diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 1305e3946..6e06c8bcf 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -132,7 +132,9 @@ class VSFTPDExploiter(HostExploiter): T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() # Run monkey on the machine - parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) + parameters = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=FTP_PORT) run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters} # Set unlimited to memory diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 9f40f0934..3863d47e1 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -42,6 +42,8 @@ class WebRCE(HostExploiter): self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.skip_exist = self._config.skip_exploit_if_file_exist self.vulnerable_urls = [] + self.target_url = None + self.vulnerable_port = None def get_exploit_config(self): """ @@ -87,27 +89,30 @@ class WebRCE(HostExploiter): if not self.vulnerable_urls: return False + self.target_url = self.vulnerable_urls[0] + self.vulnerable_port = HTTPTools.get_port_from_url(self.target_url) + # Skip if monkey already exists and this option is given - if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.vulnerable_urls[0]): + if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.target_url): LOG.info("Host %s was already infected under the current configuration, done" % self.host) return True # Check for targets architecture (if it's 32 or 64 bit) - if not exploit_config['blind_exploit'] and not self.set_host_arch(self.vulnerable_urls[0]): + if not exploit_config['blind_exploit'] and not self.set_host_arch(self.target_url): return False # Upload the right monkey to target - data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands']) + data = self.upload_monkey(self.target_url, exploit_config['upload_commands']) if data is False: return False # Change permissions to transform monkey into executable file - if self.change_permissions(self.vulnerable_urls[0], data['path']) is False: + if self.change_permissions(self.target_url, data['path']) is False: return False # Execute remote monkey - if self.execute_remote_monkey(self.vulnerable_urls[0], data['path'], exploit_config['dropper']) is False: + if self.execute_remote_monkey(self.target_url, data['path'], exploit_config['dropper']) is False: return False return True @@ -403,10 +408,15 @@ class WebRCE(HostExploiter): default_path = self.get_default_dropper_path() if default_path is False: return False - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path) + monkey_cmd = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + self.vulnerable_port, + default_path) command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd} else: - monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) + monkey_cmd = build_monkey_commandline(self.host, + get_monkey_depth() - 1, + self.vulnerable_port) command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} try: LOG.info("Trying to execute monkey using command: {}".format(command)) @@ -489,3 +499,6 @@ class WebRCE(HostExploiter): except KeyError: LOG.debug("Target's machine type was not set. Using win-32 dropper path.") return self._config.dropper_target_path_win_32 + + def set_vulnerable_port_from_url(self, url): + self.vulnerable_port = HTTPTools.get_port_from_url(url) diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 8379b6d4f..08c588278 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -234,11 +234,15 @@ class Ms08_067_Exploiter(HostExploiter): # execute the remote dropper in case the path isn't final if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1, + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + SRVSVC_Exploit.TELNET_PORT, self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + vulnerable_port=SRVSVC_Exploit.TELNET_PORT) try: sock.send("start %s\r\n" % (cmdline,)) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index adaf524e2..2100c23b0 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -21,6 +21,7 @@ class WmiExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)' + VULNERABLE_PORT = 135 def __init__(self, host): super(WmiExploiter, self).__init__(host) @@ -94,11 +95,15 @@ class WmiExploiter(HostExploiter): # execute the remote dropper in case the path isn't final elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ - build_monkey_commandline( - self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + WmiExploiter.VULNERABLE_PORT, + self._config.dropper_target_path_win_32) else: cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ - build_monkey_commandline(self.host, get_monkey_depth() - 1) + build_monkey_commandline(self.host, + get_monkey_depth() - 1, + WmiExploiter.VULNERABLE_PORT) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c96599844..a31ea4d47 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -10,6 +10,7 @@ from infection_monkey.network.HostFinger import HostFinger from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.model import DELAY_DELETE_CMD @@ -26,12 +27,13 @@ from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.network.tools import get_interface_to_target +from infection_monkey.network.tools import get_interface_to_target, is_running_on_server from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from common.utils.attack_utils import ScanStatus, UsageEnum from common.version import get_version from infection_monkey.exploit.HostExploiter import HostExploiter +from monkey_island.cc.network_utils import remove_port_from_ip_string MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down" @@ -40,10 +42,6 @@ __author__ = 'itamar' LOG = logging.getLogger(__name__) -class PlannedShutdownException(Exception): - pass - - class InfectionMonkey(object): def __init__(self, args): self._keep_running = False @@ -74,7 +72,9 @@ class InfectionMonkey(object): arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-d', '--depth', type=int) + arg_parser.add_argument('-vp', '--vulnerable-port') self._opts, self._args = arg_parser.parse_known_args(self._args) + self.log_arguments() self._parent = self._opts.parent self._default_tunnel = self._opts.tunnel @@ -119,6 +119,10 @@ class InfectionMonkey(object): self.shutdown_by_not_alive_config() + if self.is_started_on_island(): + ControlClient.report_start_on_island() + ControlClient.should_monkey_run(self._opts.vulnerable_port) + if firewall.is_enabled(): firewall.add_firewall_rule() @@ -380,3 +384,11 @@ class InfectionMonkey(object): raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)) self._default_server = WormConfiguration.current_server LOG.debug("default server set to: %s" % self._default_server) + + def is_started_on_island(self): + island_ip = remove_port_from_ip_string(self._default_server) + return is_running_on_server(island_ip) and WormConfiguration.depth == WormConfiguration.max_depth + + def log_arguments(self): + arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()]) + LOG.info(f"Monkey started with arguments: {arg_string}") diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 5e95e20be..ef37fe827 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -7,7 +7,7 @@ import struct import time import re -from infection_monkey.network.info import get_routes +from infection_monkey.network.info import get_routes, local_ips from infection_monkey.pyinstaller_utils import get_binary_file_path from infection_monkey.utils.environment import is_64bit_python @@ -309,3 +309,7 @@ def get_interface_to_target(dst): paths.sort() ret = paths[-1][1] return ret[1] + + +def is_running_on_server(ip: str) -> bool: + return ip in local_ips() diff --git a/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py b/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py new file mode 100644 index 000000000..f0147e1e5 --- /dev/null +++ b/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py @@ -0,0 +1,2 @@ +class PlannedShutdownException(Exception): + pass diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 13aac018a..505220a78 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -16,10 +16,12 @@ from monkey_island.cc.resources.island_logs import IslandLog from monkey_island.cc.resources.monkey import Monkey from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration from monkey_island.cc.resources.island_configuration import IslandConfiguration +from monkey_island.cc.resources.monkey_control.started_on_island import StartedOnIsland from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node_states import NodeStates +from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.root import Root @@ -121,6 +123,8 @@ def init_api_resources(api): api.add_resource(AttackConfiguration, '/api/attack') api.add_resource(AttackReport, '/api/attack/report') api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') + api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/') + api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island') api.add_resource(MonkeyTest, '/api/test/monkey') api.add_resource(ClearCaches, '/api/test/clear_caches') diff --git a/monkey/monkey_island/cc/bootloader_server.py b/monkey/monkey_island/cc/bootloader_server.py index 3024b6a42..b1f7ec484 100644 --- a/monkey/monkey_island/cc/bootloader_server.py +++ b/monkey/monkey_island/cc/bootloader_server.py @@ -10,7 +10,7 @@ import pymongo from monkey_island.cc.environment import Environment # Disable "unverified certificate" warnings when sending requests to island -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # noqa: DUO131 logger = logging.getLogger(__name__) @@ -29,7 +29,9 @@ class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler): post_data = self.rfile.read(content_length).decode() island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0]) island_server_path = parse.urljoin(island_server_path, self.path[1:]) - r = requests.post(url=island_server_path, data=post_data, verify=False) + # The island server doesn't always have a correct SSL cert installed (By default it comes with a self signed one), + # that's why we're not verifying the cert in this request. + r = requests.post(url=island_server_path, data=post_data, verify=False) # noqa: DUO123 try: if r.status_code != 200: diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 50e3bdd7c..0bc52e54f 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list -from monkey_island.cc.utils import local_ip_addresses +from monkey_island.cc.network_utils import local_ip_addresses from monkey_island.cc.environment.environment import env from monkey_island.cc.database import is_db_server_up, get_db_version from monkey_island.cc.resources.monkey_download import MonkeyDownload diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index f658a3d06..ad10084d9 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -8,7 +8,7 @@ import ring from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.models.command_control_channel import CommandControlChannel -from monkey_island.cc.utils import local_ip_addresses +from monkey_island.cc.network_utils import local_ip_addresses from common.cloud import environment_names MAX_MONKEYS_AMOUNT_TO_CACHE = 100 diff --git a/monkey/monkey_island/cc/utils.py b/monkey/monkey_island/cc/network_utils.py similarity index 95% rename from monkey/monkey_island/cc/utils.py rename to monkey/monkey_island/cc/network_utils.py index 37af43745..903485723 100644 --- a/monkey/monkey_island/cc/utils.py +++ b/monkey/monkey_island/cc/network_utils.py @@ -1,12 +1,12 @@ +import array +import collections +import ipaddress import socket +import struct import sys from typing import List -import collections +from urllib.parse import urlparse -import array - -import struct -import ipaddress from netifaces import interfaces, ifaddresses, AF_INET from ring import lru @@ -86,3 +86,8 @@ def get_subnets(): ] ) return subnets + + +def remove_port_from_ip_string(ip_string: str) -> str: + url = urlparse("http://" + ip_string) + return str(url.hostname) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 41f5fa417..d63b21ffb 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -10,7 +10,7 @@ from monkey_island.cc.environment.environment import env from monkey_island.cc.models import Monkey from monkey_island.cc.resources.monkey_download import get_monkey_executable from monkey_island.cc.services.node import NodeService -from monkey_island.cc.utils import local_ip_addresses +from monkey_island.cc.network_utils import local_ip_addresses from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index dcdc5bc12..181ce94b2 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -58,6 +58,7 @@ class Monkey(flask_restful.Resource): return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) # Used by monkey. can't secure. + # Called on monkey wakeup to initialize local configuration @TestTelemStore.store_test_telem def post(self, **kw): monkey_json = json.loads(request.data) @@ -74,16 +75,11 @@ class Monkey(flask_restful.Resource): # if new monkey telem, change config according to "new monkeys" config. db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) - if not db_monkey: - # we pull it encrypted because we then decrypt it for the monkey in get - new_config = ConfigService.get_flat_config(False, False) - monkey_json['config'] = monkey_json.get('config', {}) - monkey_json['config'].update(new_config) - else: - db_config = db_monkey.get('config', {}) - if 'current_server' in db_config: - del db_config['current_server'] - monkey_json.get('config', {}).update(db_config) + + # Update monkey configuration + new_config = ConfigService.get_flat_config(False, False) + monkey_json['config'] = monkey_json.get('config', {}) + monkey_json['config'].update(new_config) # try to find new monkey parent parent = monkey_json.get('parent') diff --git a/monkey/monkey_island/cc/resources/monkey_control/__init__.py b/monkey/monkey_island/cc/resources/monkey_control/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py new file mode 100644 index 000000000..06e49b145 --- /dev/null +++ b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py @@ -0,0 +1,14 @@ +import flask_restful +from flask import request + +from monkey_island.cc.services.remote_port_check import check_tcp_port + + +class RemotePortCheck(flask_restful.Resource): + + # Used by monkey. can't secure. + def get(self, port): + if port and check_tcp_port(request.remote_addr, port): + return {"status": "port_visible"} + else: + return {"status": "port_invisible"} diff --git a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py new file mode 100644 index 000000000..542860f48 --- /dev/null +++ b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py @@ -0,0 +1,16 @@ +import json + +import flask_restful +from flask import request, make_response + +from monkey_island.cc.services.config import ConfigService + + +class StartedOnIsland(flask_restful.Resource): + + # Used by monkey. can't secure. + def post(self): + data = json.loads(request.data) + if data['started_on_island']: + ConfigService.set_started_on_island(True) + return make_response({}, 200) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 216329905..5b319072b 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -8,7 +8,7 @@ from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.database import Database from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle -from monkey_island.cc.utils import local_ip_addresses +from monkey_island.cc.network_utils import local_ip_addresses __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/test/utils/telem_store.py index 18ebfd244..ed15ae22e 100644 --- a/monkey/monkey_island/cc/resources/test/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/test/utils/telem_store.py @@ -19,6 +19,8 @@ logger = logging.getLogger(__name__) class TestTelemStore: + TELEMS_EXPORTED = False + @staticmethod def store_test_telem(f): @wraps(f) @@ -46,6 +48,7 @@ class TestTelemStore: for test_telem in TestTelem.objects(): with open(TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), 'w') as file: file.write(test_telem.to_json(indent=2)) + TestTelemStore.TELEMS_EXPORTED = True logger.info("Telemetries exported!") @staticmethod diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index e9ed3b0f6..6aef36174 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -7,7 +7,7 @@ import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo from monkey_island.cc.environment.environment import env -from monkey_island.cc.utils import local_ip_addresses +from monkey_island.cc.network_utils import local_ip_addresses from .config_schema import SCHEMA from monkey_island.cc.encryptor import encryptor @@ -74,6 +74,12 @@ class ConfigService: mongo.db.config.update({'name': 'newconfig'}, {"$set": {mongo_key: value}}) + @staticmethod + def append_to_config_array(config_key_as_arr, value): + mongo_key = ".".join(config_key_as_arr) + mongo.db.config.update({'name': 'newconfig'}, + {"$push": {mongo_key: value}}) + @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): config_json = ConfigService.get_config(is_initial_config, should_decrypt) @@ -311,3 +317,7 @@ class ConfigService: @staticmethod def is_test_telem_export_enabled(): return ConfigService.get_config_value(['internal', 'testing', 'export_monkey_telems']) + + @staticmethod + def set_started_on_island(value: bool): + ConfigService.set_config_value(['internal', 'general', 'started_on_island'], value) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index afc8591d3..b6668af38 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -564,6 +564,13 @@ SCHEMA = { "default": r"monkey_dir", "description": "Directory name for the directory which will contain all of the monkey files" }, + "started_on_island": { + "title": "Started on island", + "type": "boolean", + "default": False, + "description": "Was exploitation started from island" + "(did monkey with max depth ran on island)" + }, } }, "classes": { diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index e79cfe947..425937c7b 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -47,5 +47,5 @@ class InfectionLifecycle: # we want to skip and reply. if not is_report_being_generated() and not ReportService.is_latest_report_exists(): safe_generate_reports() - if ConfigService.is_test_telem_export_enabled(): + if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED: TestTelemStore.export_test_telems() diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 3206fef95..9c0921580 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -8,7 +8,7 @@ import monkey_island.cc.services.log from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.edge import EdgeService -from monkey_island.cc.utils import local_ip_addresses, is_local_ips +from monkey_island.cc.network_utils import local_ip_addresses, is_local_ips from monkey_island.cc import models from monkey_island.cc.services.utils.node_states import NodeStates diff --git a/monkey/monkey_island/cc/services/remote_port_check.py b/monkey/monkey_island/cc/services/remote_port_check.py new file mode 100644 index 000000000..d7d114bf8 --- /dev/null +++ b/monkey/monkey_island/cc/services/remote_port_check.py @@ -0,0 +1,19 @@ +import socket + + +DEFAULT_TIMEOUT = 5 # Seconds + + +def check_tcp_port(ip: str, port: str, timeout=DEFAULT_TIMEOUT) -> bool: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + + try: + sock.connect((ip, int(port))) + except socket.timeout: + return False + except socket.error: + return False + finally: + sock.close() + return True diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index f2c763d23..195eac3d6 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -16,7 +16,7 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.pth_report import PTHReportService from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_regular_report -from monkey_island.cc.utils import local_ip_addresses, get_subnets +from monkey_island.cc.network_utils import local_ip_addresses, get_subnets __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 5b842df0b..844724163 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -1,4 +1,5 @@ import logging +from ipaddress import ip_address from monkey_island.cc.encryptor import encryptor from monkey_island.cc.services import mimikatz_utils diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 172751a01..87fd1ace6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -30,7 +30,6 @@ class ConfigurePageComponent extends AuthComponent { lastAction: 'none', sections: [], selectedSection: 'attack', - monkeysRan: false, PBAwinFile: [], PBAlinuxFile: [], showAttackAlert: false @@ -70,7 +69,11 @@ class ConfigurePageComponent extends AuthComponent { cnc: {}, network: {}, exploits: {}, - internal: {} + internal: { + general: { + started_on_island: {'ui:widget': 'hidden'} + } + } }) } @@ -108,7 +111,6 @@ class ConfigurePageComponent extends AuthComponent { selectedSection: 'attack' }) }); - this.updateMonkeysRunning(); }; updateConfig = () => { @@ -359,14 +361,6 @@ class ConfigurePageComponent extends AuthComponent { event.target.value = null; }; - updateMonkeysRunning = () => { - this.authFetch('/api') - .then(res => res.json()) - .then(res => { - this.setState({monkeysRan: res['completed_steps']['run_monkey']}); - }); - }; - PBAwindows = () => { return () }; - renderConfigWontChangeWarning = () => { - return (
- {this.state.monkeysRan ? -
- - Changed configuration will only apply to new infections. - "Start over" to run again with different configuration. -
- : '' - } -
) - }; - renderBasicNetworkWarning = () => { if (this.state.selectedSection === 'basic_network') { return (
@@ -514,7 +495,6 @@ class ConfigurePageComponent extends AuthComponent { {this.renderAttackAlertModal()}

Monkey Configuration

{this.renderNav()} - {this.renderConfigWontChangeWarning()} {content}