Merge pull request #650 from guardicore/feature/exploitation_redundancy_fix
Redundant exploitations fix
This commit is contained in:
commit
ed276895a8
|
@ -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`
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
class PlannedShutdownException(Exception):
|
||||
pass
|
|
@ -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/<string: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')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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"}
|
|
@ -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)
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (<FilePond
|
||||
server={{
|
||||
|
@ -464,19 +458,6 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
</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 = () => {
|
||||
if (this.state.selectedSection === 'basic_network') {
|
||||
return (<div className="alert alert-info">
|
||||
|
@ -514,7 +495,6 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
{this.renderAttackAlertModal()}
|
||||
<h1 className="page-title">Monkey Configuration</h1>
|
||||
{this.renderNav()}
|
||||
{this.renderConfigWontChangeWarning()}
|
||||
{content}
|
||||
<div className="text-center">
|
||||
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
|
||||
|
|
Loading…
Reference in New Issue