forked from p15670423/monkey
Merge remote-tracking branch 'upstream/develop' into enhancement/mitre-ui-review-fixes
This commit is contained in:
commit
372e05eb13
|
@ -0,0 +1,8 @@
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class Analyzer(object, metaclass=ABCMeta):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def analyze_test_results(self):
|
||||||
|
raise NotImplementedError()
|
|
@ -1,7 +1,8 @@
|
||||||
|
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||||
|
|
||||||
|
|
||||||
class CommunicationAnalyzer(object):
|
class CommunicationAnalyzer(Analyzer):
|
||||||
|
|
||||||
def __init__(self, island_client, machine_ips):
|
def __init__(self, island_client, machine_ips):
|
||||||
self.island_client = island_client
|
self.island_client = island_client
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
|
|
||||||
|
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||||
|
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||||
|
|
||||||
|
REPORT_URLS = [
|
||||||
|
"api/report/security",
|
||||||
|
"api/attack/report",
|
||||||
|
"api/report/zero_trust/findings",
|
||||||
|
"api/report/zero_trust/principles",
|
||||||
|
"api/report/zero_trust/pillars"
|
||||||
|
]
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceAnalyzer(Analyzer):
|
||||||
|
|
||||||
|
def __init__(self, island_client: MonkeyIslandClient, break_if_took_too_long=False):
|
||||||
|
self.break_if_took_too_long = break_if_took_too_long
|
||||||
|
self.island_client = island_client
|
||||||
|
|
||||||
|
def analyze_test_results(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()
|
||||||
|
report_resource_to_response_time = {}
|
||||||
|
for url in REPORT_URLS:
|
||||||
|
report_resource_to_response_time[url] = self.island_client.get_elapsed_for_get_request(url)
|
||||||
|
|
||||||
|
# Calculate total time and check each page
|
||||||
|
single_page_time_less_then_max = True
|
||||||
|
total_time = timedelta()
|
||||||
|
for page, elapsed in report_resource_to_response_time.items():
|
||||||
|
logger.info(f"page {page} took {str(elapsed)}")
|
||||||
|
total_time += elapsed
|
||||||
|
if elapsed > MAX_ALLOWED_SINGLE_PAGE_TIME:
|
||||||
|
single_page_time_less_then_max = False
|
||||||
|
|
||||||
|
total_time_less_then_max = total_time < MAX_ALLOWED_TOTAL_TIME
|
||||||
|
|
||||||
|
logger.info(f"total time is {str(total_time)}")
|
||||||
|
|
||||||
|
performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max
|
||||||
|
|
||||||
|
if self.break_if_took_too_long and not performance_is_good_enough:
|
||||||
|
logger.warning(
|
||||||
|
"Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
|
||||||
|
"investigating. Type 'p timings' and 'p total_time' to see performance information."
|
||||||
|
)
|
||||||
|
breakpoint()
|
||||||
|
|
||||||
|
return performance_is_good_enough
|
|
@ -1,3 +1,4 @@
|
||||||
|
from datetime import timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@ -85,3 +86,23 @@ class MonkeyIslandClient(object):
|
||||||
def is_all_monkeys_dead(self):
|
def is_all_monkeys_dead(self):
|
||||||
query = {'dead': False}
|
query = {'dead': False}
|
||||||
return len(self.find_monkeys_in_db(query)) == 0
|
return len(self.find_monkeys_in_db(query)) == 0
|
||||||
|
|
||||||
|
def clear_caches(self):
|
||||||
|
"""
|
||||||
|
Tries to clear caches.
|
||||||
|
:raises: If error (by error code), raises the error
|
||||||
|
:return: The response
|
||||||
|
"""
|
||||||
|
response = self.requests.get("api/test/clear_caches")
|
||||||
|
response.raise_for_status()
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_elapsed_for_get_request(self, url):
|
||||||
|
response = self.requests.get(url)
|
||||||
|
if response.ok:
|
||||||
|
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
|
||||||
|
return response.elapsed
|
||||||
|
else:
|
||||||
|
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
|
||||||
|
# instead of raising for status, mark failed responses as maxtime
|
||||||
|
return timedelta.max()
|
||||||
|
|
|
@ -61,6 +61,14 @@ class MonkeyIslandRequests(object):
|
||||||
headers=self.get_jwt_header(),
|
headers=self.get_jwt_header(),
|
||||||
verify=False)
|
verify=False)
|
||||||
|
|
||||||
|
@_Decorators.refresh_jwt_token
|
||||||
|
def delete(self, url):
|
||||||
|
return requests.delete( # noqa: DOU123
|
||||||
|
self.addr + url,
|
||||||
|
headers=self.get_jwt_header(),
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
|
||||||
@_Decorators.refresh_jwt_token
|
@_Decorators.refresh_jwt_token
|
||||||
def get_jwt_header(self):
|
def get_jwt_header(self):
|
||||||
return {"Authorization": "JWT " + self.token}
|
return {"Authorization": "JWT " + self.token}
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
{
|
||||||
|
"basic": {
|
||||||
|
"credentials": {
|
||||||
|
"exploit_password_list": [
|
||||||
|
"Password1!",
|
||||||
|
"12345678",
|
||||||
|
"^NgDvY59~8"
|
||||||
|
],
|
||||||
|
"exploit_user_list": [
|
||||||
|
"Administrator",
|
||||||
|
"m0nk3y",
|
||||||
|
"user"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"should_exploit": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basic_network": {
|
||||||
|
"general": {
|
||||||
|
"blocked_ips": [],
|
||||||
|
"depth": 2,
|
||||||
|
"local_network_scan": false,
|
||||||
|
"subnet_scan_list": [
|
||||||
|
"10.2.2.2",
|
||||||
|
"10.2.2.4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_analysis": {
|
||||||
|
"inaccessible_subnets": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cnc": {
|
||||||
|
"servers": {
|
||||||
|
"command_servers": [
|
||||||
|
"10.2.2.251:5000"
|
||||||
|
],
|
||||||
|
"current_server": "10.2.2.251:5000",
|
||||||
|
"internet_services": [
|
||||||
|
"monkey.guardicore.com",
|
||||||
|
"www.google.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exploits": {
|
||||||
|
"general": {
|
||||||
|
"exploiter_classes": [
|
||||||
|
"SSHExploiter",
|
||||||
|
"MSSQLExploiter",
|
||||||
|
"ElasticGroovyExploiter",
|
||||||
|
"HadoopExploiter"
|
||||||
|
],
|
||||||
|
"skip_exploit_if_file_exist": false
|
||||||
|
},
|
||||||
|
"ms08_067": {
|
||||||
|
"ms08_067_exploit_attempts": 5,
|
||||||
|
"remote_user_pass": "Password1!",
|
||||||
|
"user_to_add": "Monkey_IUSER_SUPPORT"
|
||||||
|
},
|
||||||
|
"rdp_grinder": {
|
||||||
|
"rdp_use_vbs_download": true
|
||||||
|
},
|
||||||
|
"sambacry": {
|
||||||
|
"sambacry_folder_paths_to_guess": [
|
||||||
|
"/",
|
||||||
|
"/mnt",
|
||||||
|
"/tmp",
|
||||||
|
"/storage",
|
||||||
|
"/export",
|
||||||
|
"/share",
|
||||||
|
"/shares",
|
||||||
|
"/home"
|
||||||
|
],
|
||||||
|
"sambacry_shares_not_to_check": [
|
||||||
|
"IPC$",
|
||||||
|
"print$"
|
||||||
|
],
|
||||||
|
"sambacry_trigger_timeout": 5
|
||||||
|
},
|
||||||
|
"smb_service": {
|
||||||
|
"smb_download_timeout": 300,
|
||||||
|
"smb_service_name": "InfectionMonkey"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"classes": {
|
||||||
|
"finger_classes": [
|
||||||
|
"SMBFinger",
|
||||||
|
"SSHFinger",
|
||||||
|
"PingScanner",
|
||||||
|
"HTTPFinger",
|
||||||
|
"MySQLFinger",
|
||||||
|
"MSSQLFinger",
|
||||||
|
"ElasticFinger"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dropper": {
|
||||||
|
"dropper_date_reference_path_linux": "/bin/sh",
|
||||||
|
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
|
||||||
|
"dropper_set_date": true,
|
||||||
|
"dropper_target_path_linux": "/tmp/monkey",
|
||||||
|
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
|
||||||
|
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
|
||||||
|
"dropper_try_move_first": true
|
||||||
|
},
|
||||||
|
"exploits": {
|
||||||
|
"exploit_lm_hash_list": [],
|
||||||
|
"exploit_ntlm_hash_list": [],
|
||||||
|
"exploit_ssh_keys": []
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"keep_tunnel_open_time": 1,
|
||||||
|
"monkey_dir_name": "monkey_dir",
|
||||||
|
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
|
||||||
|
},
|
||||||
|
"kill_file": {
|
||||||
|
"kill_file_path_linux": "/var/run/monkey.not",
|
||||||
|
"kill_file_path_windows": "%windir%\\monkey.not"
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"dropper_log_path_linux": "/tmp/user-1562",
|
||||||
|
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
|
||||||
|
"monkey_log_path_linux": "/tmp/user-1563",
|
||||||
|
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
||||||
|
"send_log_to_server": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"monkey": {
|
||||||
|
"behaviour": {
|
||||||
|
"PBA_linux_filename": "",
|
||||||
|
"PBA_windows_filename": "",
|
||||||
|
"custom_PBA_linux_cmd": "",
|
||||||
|
"custom_PBA_windows_cmd": "",
|
||||||
|
"self_delete_in_cleanup": true,
|
||||||
|
"serialize_config": false,
|
||||||
|
"use_file_logging": true
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"alive": true,
|
||||||
|
"post_breach_actions": []
|
||||||
|
},
|
||||||
|
"life_cycle": {
|
||||||
|
"max_iterations": 1,
|
||||||
|
"retry_failed_explotation": true,
|
||||||
|
"timeout_between_iterations": 100,
|
||||||
|
"victims_max_exploit": 7,
|
||||||
|
"victims_max_find": 30
|
||||||
|
},
|
||||||
|
"system_info": {
|
||||||
|
"collect_system_info": true,
|
||||||
|
"extract_azure_creds": false,
|
||||||
|
"should_use_mimikatz": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"ping_scanner": {
|
||||||
|
"ping_scan_timeout": 500
|
||||||
|
},
|
||||||
|
"tcp_scanner": {
|
||||||
|
"HTTP_PORTS": [
|
||||||
|
80,
|
||||||
|
8080,
|
||||||
|
443,
|
||||||
|
8008,
|
||||||
|
7001
|
||||||
|
],
|
||||||
|
"tcp_scan_get_banner": true,
|
||||||
|
"tcp_scan_interval": 0,
|
||||||
|
"tcp_scan_timeout": 1000,
|
||||||
|
"tcp_target_ports": [
|
||||||
|
22,
|
||||||
|
2222,
|
||||||
|
445,
|
||||||
|
135,
|
||||||
|
3389,
|
||||||
|
80,
|
||||||
|
8080,
|
||||||
|
443,
|
||||||
|
8008,
|
||||||
|
3306,
|
||||||
|
9200,
|
||||||
|
7001
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,8 @@
|
||||||
{
|
{
|
||||||
"basic": {
|
"basic": {
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"exploit_password_list": [
|
"exploit_password_list": [],
|
||||||
"Password1!",
|
"exploit_user_list": []
|
||||||
"1234",
|
|
||||||
"password",
|
|
||||||
"12345678"
|
|
||||||
],
|
|
||||||
"exploit_user_list": [
|
|
||||||
"Administrator",
|
|
||||||
"root",
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"should_exploit": true
|
"should_exploit": true
|
||||||
|
@ -46,18 +37,9 @@
|
||||||
"exploits": {
|
"exploits": {
|
||||||
"general": {
|
"general": {
|
||||||
"exploiter_classes": [
|
"exploiter_classes": [
|
||||||
"SmbExploiter",
|
"Struts2Exploiter"
|
||||||
"WmiExploiter",
|
|
||||||
"SSHExploiter",
|
|
||||||
"ShellShockExploiter",
|
|
||||||
"SambaCryExploiter",
|
|
||||||
"ElasticGroovyExploiter",
|
|
||||||
"Struts2Exploiter",
|
|
||||||
"WebLogicExploiter",
|
|
||||||
"HadoopExploiter",
|
|
||||||
"VSFTPDExploiter"
|
|
||||||
],
|
],
|
||||||
"skip_exploit_if_file_exist": false
|
"skip_exploit_if_file_exist": true
|
||||||
},
|
},
|
||||||
"ms08_067": {
|
"ms08_067": {
|
||||||
"ms08_067_exploit_attempts": 5,
|
"ms08_067_exploit_attempts": 5,
|
||||||
|
@ -149,19 +131,19 @@
|
||||||
"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": 30,
|
||||||
"victims_max_exploit": 7,
|
"victims_max_exploit": 7,
|
||||||
"victims_max_find": 30
|
"victims_max_find": 30
|
||||||
},
|
},
|
||||||
"system_info": {
|
"system_info": {
|
||||||
"collect_system_info": true,
|
"collect_system_info": false,
|
||||||
"extract_azure_creds": true,
|
"extract_azure_creds": false,
|
||||||
"should_use_mimikatz": true
|
"should_use_mimikatz": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
"ping_scanner": {
|
"ping_scanner": {
|
||||||
"ping_scan_timeout": 1000
|
"ping_scan_timeout": 100
|
||||||
},
|
},
|
||||||
"tcp_scanner": {
|
"tcp_scanner": {
|
||||||
"HTTP_PORTS": [
|
"HTTP_PORTS": [
|
||||||
|
@ -173,7 +155,7 @@
|
||||||
],
|
],
|
||||||
"tcp_scan_get_banner": true,
|
"tcp_scan_get_banner": true,
|
||||||
"tcp_scan_interval": 0,
|
"tcp_scan_interval": 0,
|
||||||
"tcp_scan_timeout": 3000,
|
"tcp_scan_timeout": 300,
|
||||||
"tcp_target_ports": [
|
"tcp_target_ports": [
|
||||||
22,
|
22,
|
||||||
2222,
|
2222,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
import pytest
|
import pytest
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
||||||
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
||||||
|
@ -58,12 +59,30 @@ class TestMonkeyBlackbox(object):
|
||||||
config_parser = IslandConfigParser(conf_filename)
|
config_parser = IslandConfigParser(conf_filename)
|
||||||
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())
|
analyzer = CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())
|
||||||
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
||||||
BasicTest(test_name,
|
BasicTest(
|
||||||
|
name=test_name,
|
||||||
|
island_client=island_client,
|
||||||
|
config_parser=config_parser,
|
||||||
|
analyzers=[analyzer],
|
||||||
|
timeout=timeout_in_seconds,
|
||||||
|
post_exec_analyzers=[],
|
||||||
|
log_handler=log_handler).run()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_performance_test(island_client, conf_filename, test_name, timeout_in_seconds):
|
||||||
|
config_parser = IslandConfigParser(conf_filename)
|
||||||
|
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
||||||
|
BasicTest(
|
||||||
|
name=test_name,
|
||||||
|
island_client=island_client,
|
||||||
|
config_parser=config_parser,
|
||||||
|
analyzers=[CommunicationAnalyzer(island_client, config_parser.get_ips_of_targets())],
|
||||||
|
timeout=timeout_in_seconds,
|
||||||
|
post_exec_analyzers=[PerformanceAnalyzer(
|
||||||
island_client,
|
island_client,
|
||||||
config_parser,
|
break_if_took_too_long=False
|
||||||
[analyzer],
|
)],
|
||||||
timeout_in_seconds,
|
log_handler=log_handler).run()
|
||||||
log_handler).run()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_log_dir_path():
|
def get_log_dir_path():
|
||||||
|
@ -108,3 +127,17 @@ class TestMonkeyBlackbox(object):
|
||||||
|
|
||||||
def test_wmi_pth(self, island_client):
|
def test_wmi_pth(self, island_client):
|
||||||
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_PTH.conf", "WMI_PTH")
|
TestMonkeyBlackbox.run_basic_test(island_client, "WMI_PTH.conf", "WMI_PTH")
|
||||||
|
|
||||||
|
def test_performance(self, island_client):
|
||||||
|
"""
|
||||||
|
This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test
|
||||||
|
for a total of 8 machines including the Monkey Island.
|
||||||
|
|
||||||
|
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(
|
||||||
|
island_client,
|
||||||
|
"PERFORMANCE.conf",
|
||||||
|
"test_report_performance",
|
||||||
|
timeout_in_seconds=10*60)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -14,11 +13,12 @@ LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
class BasicTest(object):
|
class BasicTest(object):
|
||||||
|
|
||||||
def __init__(self, name, island_client, config_parser, analyzers, timeout, log_handler):
|
def __init__(self, name, island_client, config_parser, analyzers, timeout, post_exec_analyzers, log_handler):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.island_client = island_client
|
self.island_client = island_client
|
||||||
self.config_parser = config_parser
|
self.config_parser = config_parser
|
||||||
self.analyzers = analyzers
|
self.analyzers = analyzers
|
||||||
|
self.post_exec_analyzers = post_exec_analyzers
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.log_handler = log_handler
|
self.log_handler = log_handler
|
||||||
|
|
||||||
|
@ -32,13 +32,13 @@ class BasicTest(object):
|
||||||
self.island_client.kill_all_monkeys()
|
self.island_client.kill_all_monkeys()
|
||||||
self.wait_until_monkeys_die()
|
self.wait_until_monkeys_die()
|
||||||
self.wait_for_monkey_process_to_finish()
|
self.wait_for_monkey_process_to_finish()
|
||||||
|
self.test_post_exec_analyzers()
|
||||||
self.parse_logs()
|
self.parse_logs()
|
||||||
self.island_client.reset_env()
|
self.island_client.reset_env()
|
||||||
|
|
||||||
def print_test_starting_info(self):
|
def print_test_starting_info(self):
|
||||||
LOGGER.info("Started {} test".format(self.name))
|
LOGGER.info("Started {} test".format(self.name))
|
||||||
LOGGER.info("Machines participating in test:")
|
LOGGER.info("Machines participating in test: " + ", ".join(self.config_parser.get_ips_of_targets()))
|
||||||
LOGGER.info(" ".join(self.config_parser.get_ips_of_targets()))
|
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
def test_until_timeout(self):
|
def test_until_timeout(self):
|
||||||
|
@ -62,10 +62,8 @@ class BasicTest(object):
|
||||||
timer.get_time_taken()))
|
timer.get_time_taken()))
|
||||||
|
|
||||||
def all_analyzers_pass(self):
|
def all_analyzers_pass(self):
|
||||||
for analyzer in self.analyzers:
|
analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers]
|
||||||
if not analyzer.analyze_test_results():
|
return all(analyzers_results)
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_analyzer_logs(self):
|
def get_analyzer_logs(self):
|
||||||
log = ""
|
log = ""
|
||||||
|
@ -94,4 +92,9 @@ class BasicTest(object):
|
||||||
If we try to launch monkey during that time window monkey will fail to start, that's
|
If we try to launch monkey during that time window monkey will fail to start, that's
|
||||||
why test needs to wait a bit even after all monkeys are dead.
|
why test needs to wait a bit even after all monkeys are dead.
|
||||||
"""
|
"""
|
||||||
|
LOGGER.debug("Waiting for Monkey process to close...")
|
||||||
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)
|
sleep(TIME_FOR_MONKEY_PROCESS_TO_FINISH)
|
||||||
|
|
||||||
|
def test_post_exec_analyzers(self):
|
||||||
|
post_exec_analyzers_results = [analyzer.analyze_test_results() for analyzer in self.post_exec_analyzers]
|
||||||
|
assert all(post_exec_analyzers_results)
|
||||||
|
|
|
@ -25,6 +25,7 @@ from monkey_island.cc.resources.root import Root
|
||||||
from monkey_island.cc.resources.telemetry import Telemetry
|
from monkey_island.cc.resources.telemetry import Telemetry
|
||||||
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
||||||
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
||||||
|
from monkey_island.cc.resources.test.clear_caches import ClearCaches
|
||||||
from monkey_island.cc.resources.version_update import VersionUpdate
|
from monkey_island.cc.resources.version_update import VersionUpdate
|
||||||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||||
|
@ -116,6 +117,7 @@ def init_api_resources(api):
|
||||||
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
||||||
|
|
||||||
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(LogTest, '/api/test/log')
|
api.add_resource(LogTest, '/api/test/log')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""
|
"""
|
||||||
This package contains resources used by blackbox tests
|
This package contains resources used by blackbox tests
|
||||||
to analize test results, download logs and so on.
|
to analyze test results, download logs and so on.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import flask_restful
|
||||||
|
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||||
|
from monkey_island.cc.services.reporting.report import ReportService
|
||||||
|
|
||||||
|
NOT_ALL_REPORTS_DELETED = "Not all reports have been cleared from the DB!"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ClearCaches(flask_restful.Resource):
|
||||||
|
"""
|
||||||
|
Used for timing tests - we want to get actual execution time of functions in BlackBox without caching - so we use this
|
||||||
|
to clear the caches.
|
||||||
|
:note: DO NOT CALL THIS IN PRODUCTION CODE as this will slow down the user experience.
|
||||||
|
"""
|
||||||
|
@jwt_required()
|
||||||
|
def get(self, **kw):
|
||||||
|
try:
|
||||||
|
logger.warning("Trying to clear caches! Make sure this is not production")
|
||||||
|
ReportService.delete_saved_report_if_exists()
|
||||||
|
AttackReportService.delete_saved_report_if_exists()
|
||||||
|
# TODO: Monkey.clear_caches(), clear LRU caches of function in the Monkey object
|
||||||
|
except RuntimeError as e:
|
||||||
|
logger.exception(e)
|
||||||
|
flask_restful.abort(500, error_info=str(e))
|
||||||
|
|
||||||
|
if ReportService.is_report_generated() or AttackReportService.is_report_generated():
|
||||||
|
logger.error(NOT_ALL_REPORTS_DELETED)
|
||||||
|
flask_restful.abort(500, error_info=NOT_ALL_REPORTS_DELETED)
|
||||||
|
|
||||||
|
return {"success": "true"}
|
|
@ -103,3 +103,9 @@ class AttackReportService:
|
||||||
"""
|
"""
|
||||||
generated_report = mongo.db.attack_report.find_one({})
|
generated_report = mongo.db.attack_report.find_one({})
|
||||||
return generated_report is not None
|
return generated_report is not None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_saved_report_if_exists():
|
||||||
|
delete_result = mongo.db.attack_report.delete_many({})
|
||||||
|
if mongo.db.attack_report.count_documents({}) != 0:
|
||||||
|
raise RuntimeError("Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
|
||||||
|
from monkey_island.cc.services.reporting.report import ReportService
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
|
|
||||||
|
@ -23,4 +24,6 @@ class T1003(AttackTechnique):
|
||||||
else:
|
else:
|
||||||
status = ScanStatus.UNSCANNED.value
|
status = ScanStatus.UNSCANNED.value
|
||||||
data.update(T1003.get_message_and_status(status))
|
data.update(T1003.get_message_and_status(status))
|
||||||
|
data['stolen_creds'] = ReportService.get_stolen_creds()
|
||||||
|
data['stolen_creds'].extend(ReportService.get_ssh_keys())
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -11,7 +11,7 @@ class T1016(AttackTechnique):
|
||||||
scanned_msg = ""
|
scanned_msg = ""
|
||||||
used_msg = "Monkey gathered network configurations on systems in the network."
|
used_msg = "Monkey gathered network configurations on systems in the network."
|
||||||
|
|
||||||
query = [{'$match': {'telem_category': 'system_info'}},
|
query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}},
|
||||||
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
|
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
|
||||||
'networks': '$data.network_info.networks',
|
'networks': '$data.network_info.networks',
|
||||||
'netstat': '$data.network_info.netstat'}},
|
'netstat': '$data.network_info.netstat'}},
|
||||||
|
|
|
@ -11,7 +11,7 @@ class T1082(AttackTechnique):
|
||||||
scanned_msg = ""
|
scanned_msg = ""
|
||||||
used_msg = "Monkey gathered system info from machines in the network."
|
used_msg = "Monkey gathered system info from machines in the network."
|
||||||
|
|
||||||
query = [{'$match': {'telem_category': 'system_info'}},
|
query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}},
|
||||||
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
|
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
|
||||||
'aws': '$data.aws',
|
'aws': '$data.aws',
|
||||||
'netstat': '$data.network_info.netstat',
|
'netstat': '$data.network_info.netstat',
|
||||||
|
|
|
@ -773,6 +773,16 @@ class ReportService:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_saved_report_if_exists():
|
||||||
|
"""
|
||||||
|
This function clears the saved report from the DB.
|
||||||
|
:raises RuntimeError if deletion failed
|
||||||
|
"""
|
||||||
|
delete_result = mongo.db.report.delete_many({})
|
||||||
|
if mongo.db.report.count_documents({}) != 0:
|
||||||
|
raise RuntimeError("Report cache not cleared. DeleteResult: " + delete_result.raw_result)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode_dot_char_before_mongo_insert(report_dict):
|
def decode_dot_char_before_mongo_insert(report_dict):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from monkey_island.cc.models import Monkey
|
||||||
|
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
|
||||||
|
SystemInfoTelemetryDispatcher
|
||||||
|
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnvironmentTelemetryProcessing(IslandTestCase):
|
||||||
|
def test_process_environment_telemetry(self):
|
||||||
|
self.fail_if_not_testing_env()
|
||||||
|
self.clean_monkey_db()
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
monkey_guid = str(uuid.uuid4())
|
||||||
|
a_monkey = Monkey(guid=monkey_guid)
|
||||||
|
a_monkey.save()
|
||||||
|
dispatcher = SystemInfoTelemetryDispatcher()
|
||||||
|
|
||||||
|
on_premise = "On Premise"
|
||||||
|
telem_json = {
|
||||||
|
"data": {
|
||||||
|
"collectors": {
|
||||||
|
"EnvironmentCollector": {"environment": on_premise},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"monkey_guid": monkey_guid
|
||||||
|
}
|
||||||
|
dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
|
||||||
|
|
||||||
|
self.assertEqual(Monkey.get_single_monkey_by_guid(monkey_guid).environment, on_premise)
|
|
@ -9,7 +9,10 @@ export function renderMachine(val) {
|
||||||
/* Function takes data gathered from system info collector and creates a
|
/* Function takes data gathered from system info collector and creates a
|
||||||
string representation of machine from that data. */
|
string representation of machine from that data. */
|
||||||
export function renderMachineFromSystemData(data) {
|
export function renderMachineFromSystemData(data) {
|
||||||
let machineStr = data['hostname'] + ' ( ';
|
let machineStr = '';
|
||||||
|
if (typeof data['hostname'] !== 'undefined') {
|
||||||
|
machineStr = data['hostname'] + ' ( ';
|
||||||
|
}
|
||||||
data['ips'].forEach(function (ipInfo) {
|
data['ips'].forEach(function (ipInfo) {
|
||||||
if (typeof ipInfo === 'object') {
|
if (typeof ipInfo === 'object') {
|
||||||
machineStr += ipInfo['addr'] + ', ';
|
machineStr += ipInfo['addr'] + ', ';
|
||||||
|
@ -17,8 +20,12 @@ export function renderMachineFromSystemData(data) {
|
||||||
machineStr += ipInfo + ', ';
|
machineStr += ipInfo + ', ';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (typeof data['hostname'] !== 'undefined') {
|
||||||
|
return machineStr.slice(0, -2) + ' )';
|
||||||
|
} else {
|
||||||
// Replaces " ," with " )" to finish a list of IP's
|
// Replaces " ," with " )" to finish a list of IP's
|
||||||
return machineStr.slice(0, -2) + ' )'
|
return machineStr.slice(0, -2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Formats telemetry data that contains _id.machine and _id.usage fields into columns
|
/* Formats telemetry data that contains _id.machine and _id.usage fields into columns
|
||||||
|
|
|
@ -17,7 +17,7 @@ class T1003 extends React.Component {
|
||||||
<br/>
|
<br/>
|
||||||
{this.props.data.status === ScanStatus.USED ?
|
{this.props.data.status === ScanStatus.USED ?
|
||||||
<StolenPasswordsComponent
|
<StolenPasswordsComponent
|
||||||
data={this.props.reportData.glance.stolen_creds.concat(this.props.reportData.glance.ssh_keys)}/>
|
data={this.props.data.stolen_creds}/>
|
||||||
: ''}
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,13 +12,14 @@ class T1082 extends React.Component {
|
||||||
static getSystemInfoColumns() {
|
static getSystemInfoColumns() {
|
||||||
return ([{
|
return ([{
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{ Header: 'Machine',
|
||||||
Header: 'Machine',
|
|
||||||
id: 'machine',
|
id: 'machine',
|
||||||
accessor: x => renderMachineFromSystemData(x.machine),
|
accessor: x => renderMachineFromSystemData(x.machine),
|
||||||
style: {'whiteSpace': 'unset'}
|
style: {'whiteSpace': 'unset'}},
|
||||||
},
|
{ Header: 'Gathered info',
|
||||||
{Header: 'Gathered info', id: 'info', accessor: x => renderUsageFields(x.collections), style: {'whiteSpace': 'unset'}}
|
id: 'info',
|
||||||
|
accessor: x => renderUsageFields(x.collections),
|
||||||
|
style: {'whiteSpace': 'unset'}}
|
||||||
]
|
]
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,14 @@ class MapPageComponent extends AuthComponent {
|
||||||
killPressed: false,
|
killPressed: false,
|
||||||
showKillDialog: false,
|
showKillDialog: false,
|
||||||
telemetry: [],
|
telemetry: [],
|
||||||
telemetryLastTimestamp: null
|
telemetryLastTimestamp: null,
|
||||||
|
isScrolledUp: false,
|
||||||
|
telemetryLines: 0,
|
||||||
|
telemetryCurrentLine: 0
|
||||||
};
|
};
|
||||||
|
this.telemConsole = React.createRef();
|
||||||
|
this.handleScroll = this.handleScroll.bind(this);
|
||||||
|
this.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
events = {
|
events = {
|
||||||
|
@ -67,6 +73,12 @@ class MapPageComponent extends AuthComponent {
|
||||||
telemetryLastTimestamp: res['timestamp']
|
telemetryLastTimestamp: res['timestamp']
|
||||||
});
|
});
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
|
|
||||||
|
let telemConsoleRef = this.telemConsole.current;
|
||||||
|
if (!this.state.isScrolledUp) {
|
||||||
|
telemConsoleRef.scrollTop = telemConsoleRef.scrollHeight - telemConsoleRef.clientHeight;
|
||||||
|
this.scrollTop = telemConsoleRef.scrollTop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -138,9 +150,22 @@ class MapPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleScroll(e) {
|
||||||
|
let element = e.target;
|
||||||
|
|
||||||
|
let telemetryStyle = window.getComputedStyle(element);
|
||||||
|
let telemetryLineHeight = parseInt((telemetryStyle.lineHeight).replace('px', ''));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isScrolledUp: (element.scrollTop < this.scrollTop),
|
||||||
|
telemetryCurrentLine: Math.trunc(element.scrollTop/telemetryLineHeight)+1,
|
||||||
|
telemetryLines: Math.trunc(element.scrollHeight/telemetryLineHeight)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderTelemetryConsole() {
|
renderTelemetryConsole() {
|
||||||
return (
|
return (
|
||||||
<div className="telemetry-console">
|
<div className="telemetry-console" onScroll={this.handleScroll} ref={this.telemConsole}>
|
||||||
{
|
{
|
||||||
this.state.telemetry.map(this.renderTelemetryEntry)
|
this.state.telemetry.map(this.renderTelemetryEntry)
|
||||||
}
|
}
|
||||||
|
@ -148,6 +173,14 @@ class MapPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderTelemetryLineCount() {
|
||||||
|
return (
|
||||||
|
<div className="telemetry-lines">
|
||||||
|
<b>[{this.state.telemetryCurrentLine}/{this.state.telemetryLines}]</b>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -170,6 +203,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
<div style={{height: '80vh'}}>
|
<div style={{height: '80vh'}}>
|
||||||
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
|
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
|
||||||
</div>
|
</div>
|
||||||
|
{this.renderTelemetryLineCount()}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4}>
|
<Col xs={4}>
|
||||||
<input className="form-control input-block"
|
<input className="form-control input-block"
|
||||||
|
|
|
@ -324,6 +324,17 @@ body {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.telemetry-lines {
|
||||||
|
z-index: 3;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 103px;
|
||||||
|
right: 20px;
|
||||||
|
background: #000000cc;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.map-legend {
|
.map-legend {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
@ -582,4 +593,3 @@ body {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue