Merge pull request #650 from guardicore/feature/exploitation_redundancy_fix

Redundant exploitations fix
This commit is contained in:
VakarisZ 2020-05-25 18:35:10 +03:00 committed by GitHub
commit ed276895a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 449 additions and 164 deletions

View File

@ -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_. this information in the GCP Console `Compute Engine/VM Instances` under _External IP_.
#### Running in command line #### 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` `monkey\envs\monkey_zoo\blackbox>python -m pytest -s --island=35.207.152.72:5000 test_blackbox.py`

View File

@ -4,8 +4,23 @@ import pytest
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--island", action="store", default="", parser.addoption("--island", action="store", default="",
help="Specify the Monkey Island address (host+port).") 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): def island(request):
return request.config.getoption("--island") 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")

View File

@ -90,9 +90,10 @@ class MonkeyIslandRequests(object):
@_Decorators.refresh_jwt_token @_Decorators.refresh_jwt_token
def patch(self, url, data: Dict): def patch(self, url, data: Dict):
return requests.patch(self.addr + url, # noqa: DUO123 return requests.patch(self.addr + url, # noqa: DUO123
data=data, data=data,
headers=self.get_jwt_header(), headers=self.get_jwt_header(),
verify=False) verify=False
)
@_Decorators.refresh_jwt_token @_Decorators.refresh_jwt_token
def delete(self, url): def delete(self, url):

View File

@ -2,14 +2,15 @@
"basic": { "basic": {
"credentials": { "credentials": {
"exploit_password_list": [ "exploit_password_list": [
"Password1!", "Xk8VDTsC",
"12345678", "^NgDvY59~8",
"^NgDvY59~8" "Ivrrw5zEzs",
"3Q=(Ge(+&w]*",
"`))jU7L(w}",
"t67TC5ZDmz"
], ],
"exploit_user_list": [ "exploit_user_list": [
"Administrator", "m0nk3y"
"m0nk3y",
"user"
] ]
}, },
"general": { "general": {
@ -23,11 +24,38 @@
"local_network_scan": false, "local_network_scan": false,
"subnet_scan_list": [ "subnet_scan_list": [
"10.2.2.2", "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": { "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": { "cnc": {
@ -45,10 +73,17 @@
"exploits": { "exploits": {
"general": { "general": {
"exploiter_classes": [ "exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter", "SSHExploiter",
"MSSQLExploiter", "ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter", "ElasticGroovyExploiter",
"HadoopExploiter" "Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter",
"MSSQLExploiter"
], ],
"skip_exploit_if_file_exist": false "skip_exploit_if_file_exist": false
}, },
@ -57,9 +92,6 @@
"remote_user_pass": "Password1!", "remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT" "user_to_add": "Monkey_IUSER_SUPPORT"
}, },
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": { "sambacry": {
"sambacry_folder_paths_to_guess": [ "sambacry_folder_paths_to_guess": [
"/", "/",
@ -109,7 +141,7 @@
"exploit_ssh_keys": [] "exploit_ssh_keys": []
}, },
"general": { "general": {
"keep_tunnel_open_time": 1, "keep_tunnel_open_time": 60,
"monkey_dir_name": "monkey_dir", "monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}" "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
}, },
@ -123,6 +155,9 @@
"monkey_log_path_linux": "/tmp/user-1563", "monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp", "monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true "send_log_to_server": true
},
"testing": {
"export_monkey_telems": true
} }
}, },
"monkey": { "monkey": {
@ -137,24 +172,32 @@
}, },
"general": { "general": {
"alive": true, "alive": true,
"post_breach_actions": [] "post_breach_actions": [
"CommunicateAsNewUser"
]
}, },
"life_cycle": { "life_cycle": {
"max_iterations": 1, "max_iterations": 1,
"retry_failed_explotation": true, "retry_failed_explotation": true,
"timeout_between_iterations": 100, "timeout_between_iterations": 100,
"victims_max_exploit": 7, "victims_max_exploit": 15,
"victims_max_find": 30 "victims_max_find": 100
}, },
"system_info": { "system_info": {
"collect_system_info": true, "collect_system_info": true,
"extract_azure_creds": false, "extract_azure_creds": true,
"should_use_mimikatz": true "should_use_mimikatz": true,
"system_info_collectors_classes": [
"EnvironmentCollector",
"AwsCollector",
"HostnameCollector",
"ProcessListCollector"
]
} }
}, },
"network": { "network": {
"ping_scanner": { "ping_scanner": {
"ping_scan_timeout": 500 "ping_scan_timeout": 1000
}, },
"tcp_scanner": { "tcp_scanner": {
"HTTP_PORTS": [ "HTTP_PORTS": [
@ -166,7 +209,7 @@
], ],
"tcp_scan_get_banner": true, "tcp_scan_get_banner": true,
"tcp_scan_interval": 0, "tcp_scan_interval": 0,
"tcp_scan_timeout": 1000, "tcp_scan_timeout": 3000,
"tcp_target_ports": [ "tcp_target_ports": [
22, 22,
2222, 2222,
@ -179,7 +222,8 @@
8008, 8008,
3306, 3306,
9200, 9200,
7001 7001,
8088
] ]
} }
} }

View File

@ -27,15 +27,16 @@ LOGGER = logging.getLogger(__name__)
@pytest.fixture(autouse=True, scope='session') @pytest.fixture(autouse=True, scope='session')
def GCPHandler(request): def GCPHandler(request, no_gcp):
GCPHandler = gcp_machine_handlers.GCPHandler() if not no_gcp:
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST)) GCPHandler = gcp_machine_handlers.GCPHandler()
wait_machine_bootup() GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
wait_machine_bootup()
def fin(): def fin():
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST)) GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
request.addfinalizer(fin) request.addfinalizer(fin)
@pytest.fixture(autouse=True, scope='session') @pytest.fixture(autouse=True, scope='session')
@ -49,9 +50,10 @@ def wait_machine_bootup():
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def island_client(island): def island_client(island, quick_performance_tests):
island_client_object = MonkeyIslandClient(island) island_client_object = MonkeyIslandClient(island)
island_client_object.reset_env() if not quick_performance_tests:
island_client_object.reset_env()
yield island_client_object yield island_client_object
@ -130,7 +132,7 @@ class TestMonkeyBlackbox(object):
def test_wmi_pth(self, island_client): def test_wmi_pth(self, island_client):
TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_PTH.conf", "WMI_PTH") 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 This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
for a total of 8 machines including the Monkey Island. 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 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 and the Timing one which checks how long the report took to execute
""" """
TestMonkeyBlackbox.run_performance_test(ReportGenerationTest, if not quick_performance_tests:
island_client, TestMonkeyBlackbox.run_performance_test(ReportGenerationTest,
"PERFORMANCE.conf", island_client,
timeout_in_seconds=10*60) "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): def test_map_generation_performance(self, island_client, quick_performance_tests):
TestMonkeyBlackbox.run_performance_test(MapGenerationTest, if not quick_performance_tests:
island_client, TestMonkeyBlackbox.run_performance_test(MapGenerationTest,
"PERFORMANCE.conf", island_client,
timeout_in_seconds=10*60) "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): def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
ReportGenerationFromTelemetryTest(island_client).run() ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
def test_map_generation_from_fake_telemetries(self, island_client): def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests):
MapGenerationFromTelemetryTest(island_client).run() MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run()
def test_telem_performance(self, island_client): def test_telem_performance(self, island_client, quick_performance_tests):
TelemetryPerformanceTest(island_client).test_telemetry_performance() TelemetryPerformanceTest(island_client, quick_performance_tests).test_telemetry_performance()

View File

@ -17,9 +17,6 @@ class EndpointPerformanceTest(BasicTest):
self.island_client = island_client self.island_client = island_client
def run(self) -> bool: 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 # Collect timings for all pages
self.island_client.clear_caches() self.island_client.clear_caches()
endpoint_timings = {} endpoint_timings = {}

View File

@ -17,7 +17,7 @@ class MapGenerationFromTelemetryTest(PerformanceTest):
TEST_NAME = "Map generation from fake telemetries test" 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 self.island_client = island_client
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
@ -25,7 +25,8 @@ class MapGenerationFromTelemetryTest(PerformanceTest):
break_on_timeout=break_on_timeout) break_on_timeout=break_on_timeout)
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME, self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME,
self.island_client, self.island_client,
performance_config) performance_config,
quick_performance_test)
def run(self): def run(self):
self.performance_test_workflow.run() self.performance_test_workflow.run()

View File

@ -23,6 +23,8 @@ class PerformanceTestWorkflow(BasicTest):
self.island_client.kill_all_monkeys() self.island_client.kill_all_monkeys()
self.exploitation_test.wait_until_monkeys_die() self.exploitation_test.wait_until_monkeys_die()
self.exploitation_test.wait_for_monkey_process_to_finish() 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) performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
try: try:
if not self.island_client.is_all_monkeys_dead(): if not self.island_client.is_all_monkeys_dead():

View File

@ -21,7 +21,7 @@ class ReportGenerationFromTelemetryTest(PerformanceTest):
TEST_NAME = "Map generation from fake telemetries test" 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 self.island_client = island_client
performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
@ -29,7 +29,8 @@ class ReportGenerationFromTelemetryTest(PerformanceTest):
break_on_timeout=break_on_timeout) break_on_timeout=break_on_timeout)
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME, self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME,
self.island_client, self.island_client,
performance_config) performance_config,
quick_performance_test)
def run(self): def run(self):
self.performance_test_workflow.run() self.performance_test_workflow.run()

View File

@ -10,9 +10,9 @@ class FakeMonkey:
self.original_guid = guid self.original_guid = guid
self.fake_ip_generator = fake_ip_generator self.fake_ip_generator = fake_ip_generator
self.on_island = on_island 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) self.fake_ips = fake_ip_generator.generate_fake_ips_for_real_ips(ips)
def change_fake_data(self): def change_fake_data(self):
self.fake_ips = self.fake_ip_generator.generate_fake_ips_for_real_ips(self.original_ips) 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

View File

@ -18,8 +18,9 @@ MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=60)
class TelemetryPerformanceTest: class TelemetryPerformanceTest:
def __init__(self, island_client: MonkeyIslandClient): def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool):
self.island_client = island_client self.island_client = island_client
self.quick_performance_test = quick_performance_test
def test_telemetry_performance(self): def test_telemetry_performance(self):
LOGGER.info("Starting telemetry performance test.") LOGGER.info("Starting telemetry performance test.")
@ -36,6 +37,8 @@ class TelemetryPerformanceTest:
telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(telemetry) telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(telemetry)
test_config = PerformanceTestConfig(MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME) test_config = PerformanceTestConfig(MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME)
PerformanceAnalyzer(test_config, telemetry_parse_times).analyze_test_results() 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): def get_telemetry_time(self, telemetry):
content = telemetry['content'] content = telemetry['content']

View File

@ -6,15 +6,18 @@ from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test impor
class TelemetryPerformanceTestWorkflow(BasicTest): 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.name = name
self.island_client = island_client self.island_client = island_client
self.performance_config = performance_config self.performance_config = performance_config
self.quick_performance_test = quick_performance_test
def run(self): def run(self):
try: 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) performance_test = EndpointPerformanceTest(self.name, self.performance_config, self.island_client)
assert performance_test.run() assert performance_test.run()
finally: finally:
self.island_client.reset_env() if not self.quick_performance_test:
self.island_client.reset_env()

View File

@ -15,10 +15,10 @@ class GCPHandler(object):
self.zone = zone self.zone = zone
try: try:
# pass the key file to gcp # 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") LOGGER.info("GCP Handler passed key")
# set project # 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 set project")
LOGGER.info("GCP Handler initialized successfully") LOGGER.info("GCP Handler initialized successfully")
except Exception as e: except Exception as e:
@ -32,14 +32,14 @@ class GCPHandler(object):
""" """
LOGGER.info("Setting up all GCP machines...") LOGGER.info("Setting up all GCP machines...")
try: 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.") LOGGER.info("GCP machines successfully started.")
except Exception as e: except Exception as e:
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e) LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
def stop_machines(self, machine_list): def stop_machines(self, machine_list):
try: 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.") LOGGER.info("GCP machines stopped successfully.")
except Exception as e: except Exception as e:
LOGGER.error("GCP Handler failed to stop network machines: %s" % e) LOGGER.error("GCP Handler failed to stop network machines: %s" % e)

View File

@ -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') 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"] 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" HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
@ -22,14 +23,17 @@ class Configuration(object):
for key, value in list(formatted_data.items()): for key, value in list(formatted_data.items()):
if key.startswith('_'): if key.startswith('_'):
continue continue
if key in ["name", "id", "current_server"]: if key in LOCAL_CONFIG_VARS:
continue continue
if self._depth_from_commandline and key == "depth": if self._depth_from_commandline and key == "depth":
self.max_depth = value
continue continue
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
else: else:
unknown_items.append(key) unknown_items.append(key)
if not self.max_depth:
self.max_depth = self.depth
return unknown_items return unknown_items
def from_json(self, json_data): def from_json(self, json_data):
@ -135,6 +139,8 @@ class Configuration(object):
# depth of propagation # depth of propagation
depth = 2 depth = 2
max_depth = None
started_on_island = False
current_server = "" current_server = ""
# Configuration servers to try to connect to, in this order. # Configuration servers to try to connect to, in this order.
@ -232,6 +238,18 @@ class Configuration(object):
cred_list.append(cred) cred_list.append(cred)
return cred_list 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_user_list = ['Administrator', 'root', 'user']
exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_password_list = ["Password1!", "1234", "password", "12345678"]
exploit_lm_hash_list = [] exploit_lm_hash_list = []
@ -259,23 +277,22 @@ class Configuration(object):
extract_azure_creds = True extract_azure_creds = True
###########################
# post breach actions
###########################
post_breach_actions = [] post_breach_actions = []
custom_PBA_linux_cmd = "" custom_PBA_linux_cmd = ""
custom_PBA_windows_cmd = "" custom_PBA_windows_cmd = ""
PBA_linux_filename = None PBA_linux_filename = None
PBA_windows_filename = None PBA_windows_filename = None
@staticmethod ###########################
def hash_sensitive_data(sensitive_data): # testing configuration
""" ###########################
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is export_monkey_telems = False
saved on client machines plain-text.
:param sensitive_data: the data to hash. def get_hop_distance_to_island(self):
:return: the hashed data. return self.max_depth - self.depth
"""
password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest()
return password_hashed
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -15,6 +15,8 @@ from infection_monkey.transport.tcp import TcpProxy
__author__ = 'hoffer' __author__ = 'hoffer'
from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -321,3 +323,29 @@ class ControlClient(object):
proxies=ControlClient.proxies) proxies=ControlClient.proxies)
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
return False 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)

View File

@ -44,6 +44,7 @@ class MonkeyDrops(object):
arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-s', '--server')
arg_parser.add_argument('-d', '--depth', type=int) arg_parser.add_argument('-d', '--depth', type=int)
arg_parser.add_argument('-l', '--location') arg_parser.add_argument('-l', '--location')
arg_parser.add_argument('-vp', '--vulnerable-port')
self.monkey_args = args[1:] self.monkey_args = args[1:]
self.opts, _ = arg_parser.parse_known_args(args) self.opts, _ = arg_parser.parse_known_args(args)
@ -115,7 +116,12 @@ class MonkeyDrops(object):
LOG.warning("Cannot set reference date to destination file") LOG.warning("Cannot set reference date to destination file")
monkey_options = \ 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(): if OperatingSystem.Windows == SystemInfoCollector.get_os():
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options

View File

@ -73,7 +73,8 @@ class HadoopExploiter(WebRCE):
def build_command(self, path, http_path): def build_command(self, path, http_path):
# Build command to execute # 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']: if 'linux' in self.host.os['type']:
base_command = HADOOP_LINUX_COMMAND base_command = HADOOP_LINUX_COMMAND
else: else:

View File

@ -133,6 +133,7 @@ class MSSQLExploiter(HostExploiter):
# Form monkey's launch command # Form monkey's launch command
monkey_args = build_monkey_commandline(self.host, monkey_args = build_monkey_commandline(self.host,
get_monkey_depth() - 1, get_monkey_depth() - 1,
MSSQLExploiter.SQL_DEFAULT_TCP_PORT,
dst_path) dst_path)
suffix = ">>{}".format(self.payload_file_path) suffix = ">>{}".format(self.payload_file_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX

View File

@ -329,7 +329,10 @@ class SambaCryExploiter(HostExploiter):
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
def get_monkey_commandline_file(self, location): 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 @staticmethod
def is_share_writable(smb_client, share): def is_share_writable(smb_client, share):

View File

@ -144,7 +144,11 @@ class ShellShockExploiter(HostExploiter):
# run the monkey # run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) 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 run_path = exploit + cmdline
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)

View File

@ -28,6 +28,7 @@ class SmbExploiter(HostExploiter):
def __init__(self, host): def __init__(self, host):
super(SmbExploiter, self).__init__(host) super(SmbExploiter, self).__init__(host)
self.vulnerable_port = None
def is_os_supported(self): def is_os_supported(self):
if super(SmbExploiter, self).is_os_supported(): if super(SmbExploiter, self).is_os_supported():
@ -36,11 +37,13 @@ class SmbExploiter(HostExploiter):
if not self.host.os.get('type'): if not self.host.os.get('type'):
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
self.vulnerable_port = 445
smb_finger = SMBFinger() smb_finger = SMBFinger()
smb_finger.get_host_fingerprint(self.host) smb_finger.get_host_fingerprint(self.host)
else: else:
is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139) is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139)
if is_nb_open: if is_nb_open:
self.vulnerable_port = 139
self.host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return self.host.os.get('type') in self._TARGET_OS_TYPE return self.host.os.get('type') in self._TARGET_OS_TYPE
return False return False
@ -103,10 +106,13 @@ class SmbExploiter(HostExploiter):
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, build_monkey_commandline(self.host, get_monkey_depth() - 1,
self.vulnerable_port,
self._config.dropper_target_path_win_32) self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path} + \ 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 smb_conn = False
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():

View File

@ -179,7 +179,9 @@ class SSHExploiter(HostExploiter):
try: try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) 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 &" cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline) ssh.exec_command(cmdline)

View File

@ -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) 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 = "" cmdline = ""
if parent is not None: if parent is not None:
cmdline += " -p " + parent cmdline += f" -p {parent}"
if tunnel is not None: if tunnel is not None:
cmdline += " -t " + tunnel cmdline += f" -t {tunnel}"
if server is not None: if server is not None:
cmdline += " -s " + server cmdline += f" -s {server}"
if depth is not None: if depth is not None:
if depth < 0: if int(depth) < 0:
depth = 0 depth = 0
cmdline += " -d %d" % depth cmdline += f" -d {depth}"
if location is not None: 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 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 from infection_monkey.config import GUID
return build_monkey_commandline_explicitly( 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(): def get_monkey_depth():

View File

@ -73,6 +73,10 @@ class HTTPTools(object):
lock.acquire() lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd 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): class MonkeyHTTPServer(HTTPTools):
def __init__(self, host): def __init__(self, host):

View File

@ -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()

View File

@ -132,7 +132,9 @@ class VSFTPDExploiter(HostExploiter):
T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send()
# Run monkey on the machine # 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} run_monkey = RUN_MONKEY % {'monkey_path': monkey_path, 'monkey_type': MONKEY_ARG, 'parameters': parameters}
# Set unlimited to memory # Set unlimited to memory

View File

@ -42,6 +42,8 @@ class WebRCE(HostExploiter):
self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
self.vulnerable_urls = [] self.vulnerable_urls = []
self.target_url = None
self.vulnerable_port = None
def get_exploit_config(self): def get_exploit_config(self):
""" """
@ -87,27 +89,30 @@ class WebRCE(HostExploiter):
if not self.vulnerable_urls: if not self.vulnerable_urls:
return False 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 # 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) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True return True
# Check for targets architecture (if it's 32 or 64 bit) # 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 return False
# Upload the right monkey to target # 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: if data is False:
return False return False
# Change permissions to transform monkey into executable file # 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 return False
# Execute remote monkey # 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 False
return True return True
@ -403,10 +408,15 @@ class WebRCE(HostExploiter):
default_path = self.get_default_dropper_path() default_path = self.get_default_dropper_path()
if default_path is False: if default_path is False:
return 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} command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
else: 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} command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
try: try:
LOG.info("Trying to execute monkey using command: {}".format(command)) LOG.info("Trying to execute monkey using command: {}".format(command))
@ -489,3 +499,6 @@ class WebRCE(HostExploiter):
except KeyError: except KeyError:
LOG.debug("Target's machine type was not set. Using win-32 dropper path.") LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
return self._config.dropper_target_path_win_32 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)

View File

@ -234,11 +234,15 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ 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) self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ 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: try:
sock.send("start %s\r\n" % (cmdline,)) sock.send("start %s\r\n" % (cmdline,))

View File

@ -21,6 +21,7 @@ class WmiExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)' _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)'
VULNERABLE_PORT = 135
def __init__(self, host): def __init__(self, host):
super(WmiExploiter, self).__init__(host) super(WmiExploiter, self).__init__(host)
@ -94,11 +95,15 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline( build_monkey_commandline(self.host,
self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32) get_monkey_depth() - 1,
WmiExploiter.VULNERABLE_PORT,
self._config.dropper_target_path_win_32)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ 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 # execute the remote monkey
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,

View File

@ -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_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.monkey_log_path import get_monkey_log_path
from infection_monkey.utils.environment import is_windows_os 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.config import WormConfiguration
from infection_monkey.control import ControlClient from infection_monkey.control import ControlClient
from infection_monkey.model import DELAY_DELETE_CMD 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.telemetry.tunnel_telem import TunnelTelem
from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach 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.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version from common.version import get_version
from infection_monkey.exploit.HostExploiter import HostExploiter 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" MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
@ -40,10 +42,6 @@ __author__ = 'itamar'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class PlannedShutdownException(Exception):
pass
class InfectionMonkey(object): class InfectionMonkey(object):
def __init__(self, args): def __init__(self, args):
self._keep_running = False self._keep_running = False
@ -74,7 +72,9 @@ class InfectionMonkey(object):
arg_parser.add_argument('-t', '--tunnel') arg_parser.add_argument('-t', '--tunnel')
arg_parser.add_argument('-s', '--server') arg_parser.add_argument('-s', '--server')
arg_parser.add_argument('-d', '--depth', type=int) 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._opts, self._args = arg_parser.parse_known_args(self._args)
self.log_arguments()
self._parent = self._opts.parent self._parent = self._opts.parent
self._default_tunnel = self._opts.tunnel self._default_tunnel = self._opts.tunnel
@ -119,6 +119,10 @@ class InfectionMonkey(object):
self.shutdown_by_not_alive_config() 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(): if firewall.is_enabled():
firewall.add_firewall_rule() 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)) raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
self._default_server = WormConfiguration.current_server self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_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}")

View File

@ -7,7 +7,7 @@ import struct
import time import time
import re 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.pyinstaller_utils import get_binary_file_path
from infection_monkey.utils.environment import is_64bit_python from infection_monkey.utils.environment import is_64bit_python
@ -309,3 +309,7 @@ def get_interface_to_target(dst):
paths.sort() paths.sort()
ret = paths[-1][1] ret = paths[-1][1]
return ret[1] return ret[1]
def is_running_on_server(ip: str) -> bool:
return ip in local_ips()

View File

@ -0,0 +1,2 @@
class PlannedShutdownException(Exception):
pass

View File

@ -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 import Monkey
from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration
from monkey_island.cc.resources.island_configuration import IslandConfiguration 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.monkey_download import MonkeyDownload
from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.netmap import NetMap
from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node import Node
from monkey_island.cc.resources.node_states import NodeStates 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.remote_run import RemoteRun
from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.reporting.report import Report
from monkey_island.cc.resources.root import Root 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(AttackConfiguration, '/api/attack')
api.add_resource(AttackReport, '/api/attack/report') api.add_resource(AttackReport, '/api/attack/report')
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/<string:port>')
api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island')
api.add_resource(MonkeyTest, '/api/test/monkey') api.add_resource(MonkeyTest, '/api/test/monkey')
api.add_resource(ClearCaches, '/api/test/clear_caches') api.add_resource(ClearCaches, '/api/test/clear_caches')

View File

@ -10,7 +10,7 @@ import pymongo
from monkey_island.cc.environment import Environment from monkey_island.cc.environment import Environment
# Disable "unverified certificate" warnings when sending requests to island # 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__) logger = logging.getLogger(__name__)
@ -29,7 +29,9 @@ class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
post_data = self.rfile.read(content_length).decode() post_data = self.rfile.read(content_length).decode()
island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0]) island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
island_server_path = parse.urljoin(island_server_path, self.path[1:]) 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: try:
if r.status_code != 200: if r.status_code != 200:

View File

@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
from monkey_island.cc.app import init_app from monkey_island.cc.app import init_app
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list 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.environment.environment import env
from monkey_island.cc.database import is_db_server_up, get_db_version from monkey_island.cc.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.monkey_download import MonkeyDownload

View File

@ -8,7 +8,7 @@ import ring
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document 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.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel 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 from common.cloud import environment_names
MAX_MONKEYS_AMOUNT_TO_CACHE = 100 MAX_MONKEYS_AMOUNT_TO_CACHE = 100

View File

@ -1,12 +1,12 @@
import array
import collections
import ipaddress
import socket import socket
import struct
import sys import sys
from typing import List 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 netifaces import interfaces, ifaddresses, AF_INET
from ring import lru from ring import lru
@ -86,3 +86,8 @@ def get_subnets():
] ]
) )
return subnets return subnets
def remove_port_from_ip_string(ip_string: str) -> str:
url = urlparse("http://" + ip_string)
return str(url.hostname)

View File

@ -10,7 +10,7 @@ from monkey_island.cc.environment.environment import env
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.resources.monkey_download import get_monkey_executable from monkey_island.cc.resources.monkey_download import get_monkey_executable
from monkey_island.cc.services.node import NodeService 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 from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
__author__ = 'Barak' __author__ = 'Barak'

View File

@ -58,6 +58,7 @@ class Monkey(flask_restful.Resource):
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
# Used by monkey. can't secure. # Used by monkey. can't secure.
# Called on monkey wakeup to initialize local configuration
@TestTelemStore.store_test_telem @TestTelemStore.store_test_telem
def post(self, **kw): def post(self, **kw):
monkey_json = json.loads(request.data) 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. # if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) 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 # Update monkey configuration
new_config = ConfigService.get_flat_config(False, False) new_config = ConfigService.get_flat_config(False, False)
monkey_json['config'] = monkey_json.get('config', {}) monkey_json['config'] = monkey_json.get('config', {})
monkey_json['config'].update(new_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)
# try to find new monkey parent # try to find new monkey parent
parent = monkey_json.get('parent') parent = monkey_json.get('parent')

View File

@ -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"}

View File

@ -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)

View File

@ -8,7 +8,7 @@ from monkey_island.cc.auth import jwt_required
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.services.database import Database from monkey_island.cc.services.database import Database
from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle 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' __author__ = 'Barak'

View File

@ -19,6 +19,8 @@ logger = logging.getLogger(__name__)
class TestTelemStore: class TestTelemStore:
TELEMS_EXPORTED = False
@staticmethod @staticmethod
def store_test_telem(f): def store_test_telem(f):
@wraps(f) @wraps(f)
@ -46,6 +48,7 @@ class TestTelemStore:
for test_telem in TestTelem.objects(): for test_telem in TestTelem.objects():
with open(TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), 'w') as file: 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)) file.write(test_telem.to_json(indent=2))
TestTelemStore.TELEMS_EXPORTED = True
logger.info("Telemetries exported!") logger.info("Telemetries exported!")
@staticmethod @staticmethod

View File

@ -7,7 +7,7 @@ import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.environment.environment import env 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 .config_schema import SCHEMA
from monkey_island.cc.encryptor import encryptor from monkey_island.cc.encryptor import encryptor
@ -74,6 +74,12 @@ class ConfigService:
mongo.db.config.update({'name': 'newconfig'}, mongo.db.config.update({'name': 'newconfig'},
{"$set": {mongo_key: value}}) {"$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 @staticmethod
def get_flat_config(is_initial_config=False, should_decrypt=True): def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config, should_decrypt) config_json = ConfigService.get_config(is_initial_config, should_decrypt)
@ -311,3 +317,7 @@ class ConfigService:
@staticmethod @staticmethod
def is_test_telem_export_enabled(): def is_test_telem_export_enabled():
return ConfigService.get_config_value(['internal', 'testing', 'export_monkey_telems']) 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)

View File

@ -564,6 +564,13 @@ SCHEMA = {
"default": r"monkey_dir", "default": r"monkey_dir",
"description": "Directory name for the directory which will contain all of the monkey files" "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": { "classes": {

View File

@ -47,5 +47,5 @@ class InfectionLifecycle:
# we want to skip and reply. # we want to skip and reply.
if not is_report_being_generated() and not ReportService.is_latest_report_exists(): if not is_report_being_generated() and not ReportService.is_latest_report_exists():
safe_generate_reports() 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() TestTelemStore.export_test_telems()

View File

@ -8,7 +8,7 @@ import monkey_island.cc.services.log
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.services.edge import EdgeService 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 import models
from monkey_island.cc.services.utils.node_states import NodeStates from monkey_island.cc.services.utils.node_states import NodeStates

View File

@ -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

View File

@ -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.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager 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.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" __author__ = "itay.mizeretz"

View File

@ -1,4 +1,5 @@
import logging import logging
from ipaddress import ip_address
from monkey_island.cc.encryptor import encryptor from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.services import mimikatz_utils from monkey_island.cc.services import mimikatz_utils

View File

@ -30,7 +30,6 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none', lastAction: 'none',
sections: [], sections: [],
selectedSection: 'attack', selectedSection: 'attack',
monkeysRan: false,
PBAwinFile: [], PBAwinFile: [],
PBAlinuxFile: [], PBAlinuxFile: [],
showAttackAlert: false showAttackAlert: false
@ -70,7 +69,11 @@ class ConfigurePageComponent extends AuthComponent {
cnc: {}, cnc: {},
network: {}, network: {},
exploits: {}, exploits: {},
internal: {} internal: {
general: {
started_on_island: {'ui:widget': 'hidden'}
}
}
}) })
} }
@ -108,7 +111,6 @@ class ConfigurePageComponent extends AuthComponent {
selectedSection: 'attack' selectedSection: 'attack'
}) })
}); });
this.updateMonkeysRunning();
}; };
updateConfig = () => { updateConfig = () => {
@ -359,14 +361,6 @@ class ConfigurePageComponent extends AuthComponent {
event.target.value = null; event.target.value = null;
}; };
updateMonkeysRunning = () => {
this.authFetch('/api')
.then(res => res.json())
.then(res => {
this.setState({monkeysRan: res['completed_steps']['run_monkey']});
});
};
PBAwindows = () => { PBAwindows = () => {
return (<FilePond return (<FilePond
server={{ server={{
@ -464,19 +458,6 @@ class ConfigurePageComponent extends AuthComponent {
</div>) </div>)
}; };
renderConfigWontChangeWarning = () => {
return (<div>
{this.state.monkeysRan ?
<div className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
Changed configuration will only apply to new infections.
"Start over" to run again with different configuration.
</div>
: ''
}
</div>)
};
renderBasicNetworkWarning = () => { renderBasicNetworkWarning = () => {
if (this.state.selectedSection === 'basic_network') { if (this.state.selectedSection === 'basic_network') {
return (<div className="alert alert-info"> return (<div className="alert alert-info">
@ -514,7 +495,6 @@ class ConfigurePageComponent extends AuthComponent {
{this.renderAttackAlertModal()} {this.renderAttackAlertModal()}
<h1 className="page-title">Monkey Configuration</h1> <h1 className="page-title">Monkey Configuration</h1>
{this.renderNav()} {this.renderNav()}
{this.renderConfigWontChangeWarning()}
{content} {content}
<div className="text-center"> <div className="text-center">
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}> <button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>